X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/codesign_wrapper/check_entitlements.c diff --git a/codesign_wrapper/check_entitlements.c b/codesign_wrapper/check_entitlements.c new file mode 100644 index 00000000..0ab4da76 --- /dev/null +++ b/codesign_wrapper/check_entitlements.c @@ -0,0 +1,400 @@ +/* + * check_entitlements.c + * + * + * Created by Conrad Sauerwald on 7/9/08. + * Copyright 2008 __MyCompanyName__. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_ASSERT_PRODUCTION_CODE 0 +#include + +#include + +#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"))) { + // value isn't string + if (CFGetTypeID(value) != CFStringGetTypeID()) { + cflog("ERR: Illegal entitlement value %@ for key %@", value, key); + return; + } + + // log it for posterity + cflog("NOTICE: application-identifier := '%@'", value); + + // - put in an application-identifier that is messed up: .. + // split ident by periods and make sure the first two are not the same + // - put in an application-identifier they're not allowed to have: but we have no way to tell, although "apple" is illegal + // is apple, superseded by doesn't have at least 2 components split by a period + + CFArrayRef identifier_pieces = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, value, CFSTR(".")); /* No separators in the string returns array with that string; string == sep returns two empty strings */ + if (!identifier_pieces || (CFArrayGetCount(identifier_pieces) < 2)) { + cflog("ERR: Malformed identifier %@ := %@", key, value); + return; + } + + // doubled-up identifier + if (CFEqual(CFArrayGetValueAtIndex(identifier_pieces, 0), + CFArrayGetValueAtIndex(identifier_pieces, 1))) { + cflog("ERR: Malformed identifier %@ := %@", key, value); + return; + } + + // incomplete identifier: "blabla." + 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"))) { + // if there is one, false until proven correct + ctx->valid_keychain_access_group = false; + + // log it for posterity - we're not expecting people to use it yet + cflog("NOTICE: keychain-access-groups := %@", value); + + // - put in keychain-access-groups containing "apple" + 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; ivalid_keychain_access_group = true; + return; + } + + // - double check there's no "get-task-allow" + // - nothing else should be allowed + cflog("ERR: Illegal entitlement key '%@' := '%@'", key, value); + ctx->illegal_entitlement = true; +} + +bool +filter_entitlements(CFDictionaryRef entitlements) +{ + if (!entitlements) + return true; + + // did not put in an application-identifier: that keeps us from identifying the app securely + filter_whitelist_ctx ctx = { /* valid_application_identifier */ false, + /* valid_keychain_access_group */ true, + /* illegal_entitlement */ 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: /* fork failed */ + switch (errno) { + case EINTR: + continue; /* no problem */ + case EAGAIN: + if (delay < maxDelay) { + sleep(delay); + delay *= 2; + continue; + } + /* fall through */ + default: + perror("fork"); + return -1; + } + assert(-1); /* unreached */ + + case 0: /* child */ + if (pre_exec) + pre_exec(pre_exec_arg); + execv(argv[0], (char * const *)argv); + perror("execv"); + _exit(1); + + default: /* parent */ + 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); /* close in child */ + int redirect_child[] = { output[0], child_fd }; + int err = fork_child_timeout(dup_io, (void*)redirect_child, argv, timeout); + if (!err) { + close(output[0]); /* close the child side in the parent */ + *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); + + // empty + require(!filter_entitlements(dict), fail_test); + + CFDictionarySetValue(dict, CFSTR("get-task-allow"), kCFBooleanTrue); + + // no get-task-allow allowed + 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 \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; +} \ No newline at end of file