check_entitlements.c [plain text]
#include <paths.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <getopt.h>
#include <stdbool.h>
#include <limits.h>
#define DEBUG_ASSERT_PRODUCTION_CODE 0
#include <AssertMacros.h>
#include <CoreFoundation/CoreFoundation.h>
#define log(format, args...) \
fprintf(stderr, "codesign_wrapper " format "\n", ##args);
#define cflog(format, args...) do { \
CFStringRef logstr = CFStringCreateWithFormat(NULL, NULL, CFSTR(format), ##args); \
if (logstr) { CFShow(logstr); CFRelease(logstr); } \
} while(0); \
enum { CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171 };
typedef struct {
uint32_t type;
uint32_t offset;
} cs_blob_index;
CFDataRef
extract_entitlements_blob(const uint8_t *data, size_t length)
{
CFDataRef entitlements = NULL;
cs_blob_index *csbi = (cs_blob_index *)data;
require(data && length, out);
require(csbi->type == ntohl(CSMAGIC_EMBEDDED_ENTITLEMENTS), out);
require(length == ntohl(csbi->offset), out);
entitlements = CFDataCreate(kCFAllocatorDefault,
(uint8_t*)(data + sizeof(cs_blob_index)),
(CFIndex)(length - sizeof(cs_blob_index)));
out:
return entitlements;
}
typedef struct {
bool valid_application_identifier;
bool valid_keychain_access_group;
bool illegal_entitlement;
} filter_whitelist_ctx;
void
filter_entitlement(const void *key, const void *value,
filter_whitelist_ctx *ctx)
{
if (CFEqual(key, CFSTR("application-identifier"))) {
if (CFGetTypeID(value) != CFStringGetTypeID()) {
cflog("ERR: Illegal entitlement value %@ for key %@", value, key);
return;
}
cflog("NOTICE: application-identifier := '%@'", value);
CFArrayRef identifier_pieces = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, value, CFSTR("."));
if (!identifier_pieces || (CFArrayGetCount(identifier_pieces) < 2)) {
cflog("ERR: Malformed identifier %@ := %@", key, value);
return;
}
if (CFEqual(CFArrayGetValueAtIndex(identifier_pieces, 0),
CFArrayGetValueAtIndex(identifier_pieces, 1))) {
cflog("ERR: Malformed identifier %@ := %@", key, value);
return;
}
if (CFEqual(CFArrayGetValueAtIndex(identifier_pieces, 1), CFSTR(""))) {
cflog("ERR: Malformed identifier %@ := %@", key, value);
return;
}
ctx->valid_application_identifier = true;
return;
}
if (CFEqual(key, CFSTR("keychain-access-groups"))) {
ctx->valid_keychain_access_group = false;
cflog("NOTICE: keychain-access-groups := %@", value);
if (CFGetTypeID(value) == CFStringGetTypeID()) {
if (CFEqual(CFSTR("apple"), value) ||
(CFStringFind(value, CFSTR("*"), 0).location != kCFNotFound)) {
cflog("ERR: Illegal keychain access group value %@ for key %@", value, key);
return;
}
} else if (CFGetTypeID(value) == CFArrayGetTypeID()) {
CFIndex i, count = CFArrayGetCount(value);
for (i=0; i<count; i++) {
CFStringRef val = (CFStringRef)CFArrayGetValueAtIndex(value, i);
if (CFGetTypeID(val) != CFStringGetTypeID()) {
cflog("ERR: Illegal value in keychain access groups array %@", val);
return;
}
if (CFEqual(CFSTR("apple"), val) ||
(CFStringFind(val, CFSTR("*"), 0).location != kCFNotFound)) {
cflog("ERR: Illegal keychain access group value %@ for key %@", value, key);
return;
}
}
} else {
cflog("ERR: Illegal entitlement value %@ for key %@", value, key);
return;
}
ctx->valid_keychain_access_group = true;
return;
}
cflog("ERR: Illegal entitlement key '%@' := '%@'", key, value);
ctx->illegal_entitlement = true;
}
bool
filter_entitlements(CFDictionaryRef entitlements)
{
if (!entitlements)
return true;
filter_whitelist_ctx ctx = { false,
true,
false };
CFDictionaryApplyFunction(entitlements,
(CFDictionaryApplierFunction)filter_entitlement, &ctx);
return (ctx.valid_application_identifier && ctx.valid_keychain_access_group &&
!ctx.illegal_entitlement);
}
static pid_t kill_child = -1;
void child_timeout(int sig)
{
if (kill_child != -1) {
kill(kill_child, sig);
kill_child = -1;
}
}
pid_t fork_child(void (*pre_exec)(void *arg), void *pre_exec_arg,
const char * const argv[])
{
unsigned delay = 1, maxDelay = 60;
for (;;) {
pid_t pid;
switch (pid = fork()) {
case -1:
switch (errno) {
case EINTR:
continue;
case EAGAIN:
if (delay < maxDelay) {
sleep(delay);
delay *= 2;
continue;
}
default:
perror("fork");
return -1;
}
assert(-1);
case 0:
if (pre_exec)
pre_exec(pre_exec_arg);
execv(argv[0], (char * const *)argv);
perror("execv");
_exit(1);
default:
return pid;
break;
}
break;
}
return -1;
}
int fork_child_timeout(void (*pre_exec)(), char *pre_exec_arg,
const char * const argv[], int timeout)
{
int exit_status = -1;
pid_t child_pid = fork_child(pre_exec, pre_exec_arg, argv);
if (timeout) {
kill_child = child_pid;
alarm(timeout);
}
while (1) {
int err = wait4(child_pid, &exit_status, 0, NULL);
if (err == -1) {
perror("wait4");
if (errno == EINTR)
continue;
}
if (err == child_pid) {
if (WIFSIGNALED(exit_status)) {
log("child %d received signal %d", child_pid, WTERMSIG(exit_status));
kill(child_pid, SIGHUP);
return -2;
}
if (WIFEXITED(exit_status))
return WEXITSTATUS(exit_status);
return -1;
}
}
}
void dup_io(int arg[])
{
dup2(arg[0], arg[1]);
close(arg[0]);
}
int fork_child_timeout_output(int child_fd, int *parent_fd, const char * const argv[], int timeout)
{
int output[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, output))
return -1;
fcntl(output[1], F_SETFD, 1);
int redirect_child[] = { output[0], child_fd };
int err = fork_child_timeout(dup_io, (void*)redirect_child, argv, timeout);
if (!err) {
close(output[0]);
*parent_fd = output[1];
}
return err;
}
ssize_t read_fd(int fd, void **buffer)
{
int err = -1;
size_t capacity = 1024;
char * data = malloc(capacity);
size_t size = 0;
while (1) {
int bytes_left = capacity - size;
int bytes_read = read(fd, data + size, bytes_left);
if (bytes_read >= 0) {
size += bytes_read;
if (capacity == size) {
capacity *= 2;
data = realloc(data, capacity);
if (!data) {
err = -1;
break;
}
continue;
}
err = 0;
} else
err = -1;
break;
}
if (0 == size) {
if (data)
free(data);
return err;
}
*buffer = data;
return size;
}
static char * codesign_binary = "/usr/bin/codesign";
int
main(int argc, char *argv[])
{
#if 0
if (argc == 1) {
CFArrayRef empty_array = CFArrayCreate(NULL, NULL, 0, NULL);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault,
0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
require(!filter_entitlements(dict), fail_test);
CFDictionarySetValue(dict, CFSTR("get-task-allow"), kCFBooleanTrue);
require(!filter_entitlements(dict), fail_test);
CFDictionaryRemoveValue(dict, CFSTR("get-task-allow"));
CFDictionarySetValue(dict, CFSTR("application-identifier"), empty_array);
require(!filter_entitlements(dict), fail_test);
CFDictionarySetValue(dict, CFSTR("application-identifier"), CFSTR("apple"));
require(!filter_entitlements(dict), fail_test);
CFDictionarySetValue(dict, CFSTR("application-identifier"), CFSTR("AJ$K#GK$.AJ$K#GK$.hoi"));
require(!filter_entitlements(dict), fail_test);
CFDictionarySetValue(dict, CFSTR("application-identifier"), CFSTR("AJ$K#GK$."));
require(!filter_entitlements(dict), fail_test);
CFDictionarySetValue(dict, CFSTR("application-identifier"), CFSTR("AJ$K#GK$.hoi"));
require(filter_entitlements(dict), fail_test);
CFDictionarySetValue(dict, CFSTR("keychain-access-groups"), CFSTR("apple"));
require(!filter_entitlements(dict), fail_test);
const void *ary[] = { CFSTR("test"), CFSTR("apple") };
CFArrayRef ka_array = CFArrayCreate(NULL, ary, sizeof(ary)/sizeof(*ary), NULL);
CFDictionarySetValue(dict, CFSTR("keychain-access-groups"), ka_array);
require(!filter_entitlements(dict), fail_test);
CFDictionarySetValue(dict, CFSTR("keychain-access-groups"), CFSTR("AJ$K#GK$.joh"));
require(filter_entitlements(dict), fail_test);
CFDictionarySetValue(dict, CFSTR("this-should-not"), CFSTR("be-there"));
require(!filter_entitlements(dict), fail_test);
exit(0);
fail_test:
fprintf(stderr, "failed internal test\n");
exit(1);
}
#endif
if (argc != 2) {
fprintf(stderr, "usage: %s <file>\n", argv[0]);
exit(1);
}
do {
fprintf(stderr, "NOTICE: check_entitlements on %s", argv[1]);
int exit_status;
const char * const extract_entitlements[] =
{ codesign_binary, "--display", "--entitlements", "-", argv[1], NULL };
int entitlements_fd;
if ((exit_status = fork_child_timeout_output(STDOUT_FILENO, &entitlements_fd,
extract_entitlements, 0))) {
fprintf(stderr, "ERR: failed to extract entitlements: %d\n", exit_status);
break;
}
void *entitlements = NULL;
size_t entitlements_size = read_fd(entitlements_fd, &entitlements);
if (entitlements_size == -1)
break;
close(entitlements_fd);
if (entitlements && entitlements_size) {
CFDataRef ent = extract_entitlements_blob(entitlements, entitlements_size);
free(entitlements);
require(ent, out);
CFPropertyListRef entitlements_dict =
CFPropertyListCreateFromXMLData(kCFAllocatorDefault,
ent, kCFPropertyListImmutable, NULL);
CFRelease(ent);
require(entitlements_dict, out);
if (!filter_entitlements(entitlements_dict)) {
fprintf(stderr, "ERR: bad entitlements\n");
exit(1);
}
exit(0);
} else {
fprintf(stderr, "ERR: no entitlements!\n");
}
} while (0);
out:
return 1;
}