]> git.saurik.com Git - apple/security.git/blob - codesign_wrapper/check_entitlements.c
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / codesign_wrapper / check_entitlements.c
1 /*
2 * check_entitlements.c
3 *
4 *
5 * Created by Conrad Sauerwald on 7/9/08.
6 * Copyright 2008 __MyCompanyName__. All rights reserved.
7 *
8 */
9
10 #include <paths.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <assert.h>
19 #include <signal.h>
20 #include <sys/stat.h>
21 #include <sys/socket.h>
22 #include <getopt.h>
23 #include <stdbool.h>
24 #include <limits.h>
25
26 #define DEBUG_ASSERT_PRODUCTION_CODE 0
27 #include <AssertMacros.h>
28
29 #include <CoreFoundation/CoreFoundation.h>
30
31 #define log(format, args...) \
32 fprintf(stderr, "codesign_wrapper " format "\n", ##args);
33 #define cflog(format, args...) do { \
34 CFStringRef logstr = CFStringCreateWithFormat(NULL, NULL, CFSTR(format), ##args); \
35 if (logstr) { CFShow(logstr); CFRelease(logstr); } \
36 } while(0); \
37
38 enum { CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171 };
39
40 typedef struct {
41 uint32_t type;
42 uint32_t offset;
43 } cs_blob_index;
44
45 CFDataRef
46 extract_entitlements_blob(const uint8_t *data, size_t length)
47 {
48 CFDataRef entitlements = NULL;
49 cs_blob_index *csbi = (cs_blob_index *)data;
50
51 require(data && length, out);
52 require(csbi->type == ntohl(CSMAGIC_EMBEDDED_ENTITLEMENTS), out);
53 require(length == ntohl(csbi->offset), out);
54 entitlements = CFDataCreate(kCFAllocatorDefault,
55 (uint8_t*)(data + sizeof(cs_blob_index)),
56 (CFIndex)(length - sizeof(cs_blob_index)));
57 out:
58 return entitlements;
59 }
60
61 typedef struct {
62 bool valid_application_identifier;
63 bool valid_keychain_access_group;
64 bool illegal_entitlement;
65 } filter_whitelist_ctx;
66
67 void
68 filter_entitlement(const void *key, const void *value,
69 filter_whitelist_ctx *ctx)
70 {
71 if (CFEqual(key, CFSTR("application-identifier"))) {
72 // value isn't string
73 if (CFGetTypeID(value) != CFStringGetTypeID()) {
74 cflog("ERR: Illegal entitlement value %@ for key %@", value, key);
75 return;
76 }
77
78 // log it for posterity
79 cflog("NOTICE: application-identifier := '%@'", value);
80
81 // - put in an application-identifier that is messed up: <char-string>.<char-string-repeat>.
82 // split ident by periods and make sure the first two are not the same
83 // - put in an application-identifier they're not allowed to have: but we have no way to tell, although "apple" is illegal
84 // is apple, superseded by doesn't have at least 2 components split by a period
85
86 CFArrayRef identifier_pieces = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, value, CFSTR(".")); /* No separators in the string returns array with that string; string == sep returns two empty strings */
87 if (!identifier_pieces || (CFArrayGetCount(identifier_pieces) < 2)) {
88 cflog("ERR: Malformed identifier %@ := %@", key, value);
89 return;
90 }
91
92 // doubled-up identifier
93 if (CFEqual(CFArrayGetValueAtIndex(identifier_pieces, 0),
94 CFArrayGetValueAtIndex(identifier_pieces, 1))) {
95 cflog("ERR: Malformed identifier %@ := %@", key, value);
96 return;
97 }
98
99 // incomplete identifier: "blabla."
100 if (CFEqual(CFArrayGetValueAtIndex(identifier_pieces, 1), CFSTR(""))) {
101 cflog("ERR: Malformed identifier %@ := %@", key, value);
102 return;
103 }
104
105 ctx->valid_application_identifier = true;
106 return;
107 }
108
109 if (CFEqual(key, CFSTR("keychain-access-groups"))) {
110 // if there is one, false until proven correct
111 ctx->valid_keychain_access_group = false;
112
113 // log it for posterity - we're not expecting people to use it yet
114 cflog("NOTICE: keychain-access-groups := %@", value);
115
116 // - put in keychain-access-groups containing "apple"
117 if (CFGetTypeID(value) == CFStringGetTypeID()) {
118 if (CFEqual(CFSTR("apple"), value) ||
119 (CFStringFind(value, CFSTR("*"), 0).location != kCFNotFound)) {
120 cflog("ERR: Illegal keychain access group value %@ for key %@", value, key);
121 return;
122 }
123 } else if (CFGetTypeID(value) == CFArrayGetTypeID()) {
124 CFIndex i, count = CFArrayGetCount(value);
125 for (i=0; i<count; i++) {
126 CFStringRef val = (CFStringRef)CFArrayGetValueAtIndex(value, i);
127 if (CFGetTypeID(val) != CFStringGetTypeID()) {
128 cflog("ERR: Illegal value in keychain access groups array %@", val);
129 return;
130 }
131 if (CFEqual(CFSTR("apple"), val) ||
132 (CFStringFind(val, CFSTR("*"), 0).location != kCFNotFound)) {
133 cflog("ERR: Illegal keychain access group value %@ for key %@", value, key);
134 return;
135 }
136 }
137 } else {
138 cflog("ERR: Illegal entitlement value %@ for key %@", value, key);
139 return;
140 }
141
142 ctx->valid_keychain_access_group = true;
143 return;
144 }
145
146 // - double check there's no "get-task-allow"
147 // - nothing else should be allowed
148 cflog("ERR: Illegal entitlement key '%@' := '%@'", key, value);
149 ctx->illegal_entitlement = true;
150 }
151
152 bool
153 filter_entitlements(CFDictionaryRef entitlements)
154 {
155 if (!entitlements)
156 return true;
157
158 // did not put in an application-identifier: that keeps us from identifying the app securely
159 filter_whitelist_ctx ctx = { /* valid_application_identifier */ false,
160 /* valid_keychain_access_group */ true,
161 /* illegal_entitlement */ false };
162 CFDictionaryApplyFunction(entitlements,
163 (CFDictionaryApplierFunction)filter_entitlement, &ctx);
164 return (ctx.valid_application_identifier && ctx.valid_keychain_access_group &&
165 !ctx.illegal_entitlement);
166 }
167
168 static pid_t kill_child = -1;
169 void child_timeout(int sig)
170 {
171 if (kill_child != -1) {
172 kill(kill_child, sig);
173 kill_child = -1;
174 }
175 }
176
177 pid_t fork_child(void (*pre_exec)(void *arg), void *pre_exec_arg,
178 const char * const argv[])
179 {
180 unsigned delay = 1, maxDelay = 60;
181 for (;;) {
182 pid_t pid;
183 switch (pid = fork()) {
184 case -1: /* fork failed */
185 switch (errno) {
186 case EINTR:
187 continue; /* no problem */
188 case EAGAIN:
189 if (delay < maxDelay) {
190 sleep(delay);
191 delay *= 2;
192 continue;
193 }
194 /* fall through */
195 default:
196 perror("fork");
197 return -1;
198 }
199 assert(-1); /* unreached */
200
201 case 0: /* child */
202 if (pre_exec)
203 pre_exec(pre_exec_arg);
204 execv(argv[0], (char * const *)argv);
205 perror("execv");
206 _exit(1);
207
208 default: /* parent */
209 return pid;
210 break;
211 }
212 break;
213 }
214 return -1;
215 }
216
217
218 int fork_child_timeout(void (*pre_exec)(), char *pre_exec_arg,
219 const char * const argv[], int timeout)
220 {
221 int exit_status = -1;
222 pid_t child_pid = fork_child(pre_exec, pre_exec_arg, argv);
223 if (timeout) {
224 kill_child = child_pid;
225 alarm(timeout);
226 }
227 while (1) {
228 int err = wait4(child_pid, &exit_status, 0, NULL);
229 if (err == -1) {
230 perror("wait4");
231 if (errno == EINTR)
232 continue;
233 }
234 if (err == child_pid) {
235 if (WIFSIGNALED(exit_status)) {
236 log("child %d received signal %d", child_pid, WTERMSIG(exit_status));
237 kill(child_pid, SIGHUP);
238 return -2;
239 }
240 if (WIFEXITED(exit_status))
241 return WEXITSTATUS(exit_status);
242 return -1;
243 }
244 }
245 }
246
247
248 void dup_io(int arg[])
249 {
250 dup2(arg[0], arg[1]);
251 close(arg[0]);
252 }
253
254
255 int fork_child_timeout_output(int child_fd, int *parent_fd, const char * const argv[], int timeout)
256 {
257 int output[2];
258 if (socketpair(AF_UNIX, SOCK_STREAM, 0, output))
259 return -1;
260 fcntl(output[1], F_SETFD, 1); /* close in child */
261 int redirect_child[] = { output[0], child_fd };
262 int err = fork_child_timeout(dup_io, (void*)redirect_child, argv, timeout);
263 if (!err) {
264 close(output[0]); /* close the child side in the parent */
265 *parent_fd = output[1];
266 }
267 return err;
268 }
269
270 ssize_t read_fd(int fd, void **buffer)
271 {
272 int err = -1;
273 size_t capacity = 1024;
274 char * data = malloc(capacity);
275 size_t size = 0;
276 while (1) {
277 int bytes_left = capacity - size;
278 int bytes_read = read(fd, data + size, bytes_left);
279 if (bytes_read >= 0) {
280 size += bytes_read;
281 if (capacity == size) {
282 capacity *= 2;
283 data = realloc(data, capacity);
284 if (!data) {
285 err = -1;
286 break;
287 }
288 continue;
289 }
290 err = 0;
291 } else
292 err = -1;
293 break;
294 }
295 if (0 == size) {
296 if (data)
297 free(data);
298 return err;
299 }
300
301 *buffer = data;
302 return size;
303 }
304
305 static char * codesign_binary = "/usr/bin/codesign";
306
307 int
308 main(int argc, char *argv[])
309 {
310 #if 0
311 if (argc == 1) {
312 CFArrayRef empty_array = CFArrayCreate(NULL, NULL, 0, NULL);
313 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault,
314 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
315
316 // empty
317 require(!filter_entitlements(dict), fail_test);
318
319 CFDictionarySetValue(dict, CFSTR("get-task-allow"), kCFBooleanTrue);
320
321 // no get-task-allow allowed
322 require(!filter_entitlements(dict), fail_test);
323 CFDictionaryRemoveValue(dict, CFSTR("get-task-allow"));
324
325 CFDictionarySetValue(dict, CFSTR("application-identifier"), empty_array);
326 require(!filter_entitlements(dict), fail_test);
327
328 CFDictionarySetValue(dict, CFSTR("application-identifier"), CFSTR("apple"));
329 require(!filter_entitlements(dict), fail_test);
330 CFDictionarySetValue(dict, CFSTR("application-identifier"), CFSTR("AJ$K#GK$.AJ$K#GK$.hoi"));
331 require(!filter_entitlements(dict), fail_test);
332 CFDictionarySetValue(dict, CFSTR("application-identifier"), CFSTR("AJ$K#GK$."));
333 require(!filter_entitlements(dict), fail_test);
334 CFDictionarySetValue(dict, CFSTR("application-identifier"), CFSTR("AJ$K#GK$.hoi"));
335 require(filter_entitlements(dict), fail_test);
336
337 CFDictionarySetValue(dict, CFSTR("keychain-access-groups"), CFSTR("apple"));
338 require(!filter_entitlements(dict), fail_test);
339 const void *ary[] = { CFSTR("test"), CFSTR("apple") };
340 CFArrayRef ka_array = CFArrayCreate(NULL, ary, sizeof(ary)/sizeof(*ary), NULL);
341 CFDictionarySetValue(dict, CFSTR("keychain-access-groups"), ka_array);
342 require(!filter_entitlements(dict), fail_test);
343 CFDictionarySetValue(dict, CFSTR("keychain-access-groups"), CFSTR("AJ$K#GK$.joh"));
344 require(filter_entitlements(dict), fail_test);
345 CFDictionarySetValue(dict, CFSTR("this-should-not"), CFSTR("be-there"));
346 require(!filter_entitlements(dict), fail_test);
347
348 exit(0);
349 fail_test:
350 fprintf(stderr, "failed internal test\n");
351 exit(1);
352 }
353 #endif
354
355 if (argc != 2) {
356 fprintf(stderr, "usage: %s <file>\n", argv[0]);
357 exit(1);
358 }
359
360 do {
361
362 fprintf(stderr, "NOTICE: check_entitlements on %s", argv[1]);
363
364 int exit_status;
365 const char * const extract_entitlements[] =
366 { codesign_binary, "--display", "--entitlements", "-", argv[1], NULL };
367 int entitlements_fd;
368 if ((exit_status = fork_child_timeout_output(STDOUT_FILENO, &entitlements_fd,
369 extract_entitlements, 0))) {
370 fprintf(stderr, "ERR: failed to extract entitlements: %d\n", exit_status);
371 break;
372 }
373
374 void *entitlements = NULL;
375 size_t entitlements_size = read_fd(entitlements_fd, &entitlements);
376 if (entitlements_size == -1)
377 break;
378 close(entitlements_fd);
379
380 if (entitlements && entitlements_size) {
381 CFDataRef ent = extract_entitlements_blob(entitlements, entitlements_size);
382 free(entitlements);
383 require(ent, out);
384 CFPropertyListRef entitlements_dict =
385 CFPropertyListCreateFromXMLData(kCFAllocatorDefault,
386 ent, kCFPropertyListImmutable, NULL);
387 CFRelease(ent);
388 require(entitlements_dict, out);
389 if (!filter_entitlements(entitlements_dict)) {
390 fprintf(stderr, "ERR: bad entitlements\n");
391 exit(1);
392 }
393 exit(0);
394 } else {
395 fprintf(stderr, "ERR: no entitlements!\n");
396 }
397 } while (0);
398 out:
399 return 1;
400 }