]>
Commit | Line | Data |
---|---|---|
316670eb A |
1 | #include "tests.h" |
2 | ||
3 | #include <errno.h> | |
4 | #include <fcntl.h> | |
5 | #include <stdlib.h> | |
6 | #include <sys/mount.h> | |
7 | #include <sys/wait.h> | |
8 | ||
9 | #include <IOKit/IOKitLib.h> | |
10 | #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h> | |
11 | #include <Kernel/sys/content_protection.h> | |
12 | ||
13 | /* Note that this test (due to the need to lock/unlock the device on demand, and the | |
14 | need to manipulate the passcode) has the unfortunate effect of link xnu_quick_test | |
15 | to the IOKit Framework. */ | |
16 | ||
17 | /* TODO: Change the test to use a single cleanup label. */ | |
18 | ||
19 | #define CPT_IO_SIZE 4096 | |
20 | #define CPT_AKS_BUF_SIZE 256 | |
21 | #define CPT_MAX_PASS_LEN 64 | |
22 | ||
23 | #define GET_PROT_CLASS(fd) fcntl((fd), F_GETPROTECTIONCLASS) | |
24 | #define SET_PROT_CLASS(fd, prot_class) fcntl((fd), F_SETPROTECTIONCLASS, (prot_class)) | |
25 | ||
26 | #define PRINT_LOCK_FAIL printf("%s, line %d: failed to lock the device.\n", cpt_fail_header, __LINE__); | |
27 | #define PRINT_UNLOCK_FAIL printf("%s, line %d: failed to unlock the device.\n", cpt_fail_header, __LINE__); | |
28 | ||
29 | extern char g_target_path[PATH_MAX]; | |
30 | ||
31 | char * cpt_fail_header = "Content protection test failed"; | |
32 | char * keystorectl_path = "/usr/local/bin/keystorectl"; | |
33 | ||
34 | /* Shamelessly ripped from keystorectl routines; a wrapper for invoking the AKS user client. */ | |
35 | int apple_key_store(uint32_t command, | |
36 | uint64_t * inputs, | |
37 | uint32_t input_count, | |
38 | void * input_structs, | |
39 | size_t input_struct_count, | |
40 | uint64_t * outputs, | |
41 | uint32_t * output_count) | |
42 | { | |
43 | int result = -1; | |
44 | io_connect_t connection = IO_OBJECT_NULL; | |
45 | io_registry_entry_t apple_key_bag_service = IO_OBJECT_NULL; | |
46 | kern_return_t k_result = KERN_FAILURE; | |
47 | IOReturn io_result = IO_OBJECT_NULL; | |
48 | ||
49 | apple_key_bag_service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kAppleKeyStoreServiceName)); | |
50 | ||
51 | if (apple_key_bag_service == IO_OBJECT_NULL) | |
52 | { | |
53 | printf("FAILURE: failed to match kAppleKeyStoreServiceName.\n"); | |
54 | goto end; | |
55 | } | |
56 | ||
57 | k_result = IOServiceOpen(apple_key_bag_service, mach_task_self(), 0, &connection); | |
58 | ||
59 | if (k_result != KERN_SUCCESS) | |
60 | { | |
61 | printf("FAILURE: failed to open AppleKeyStore.\n"); | |
62 | goto end; | |
63 | } | |
64 | ||
65 | k_result = IOConnectCallMethod(connection, kAppleKeyStoreUserClientOpen, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL); | |
66 | ||
67 | if (k_result != KERN_SUCCESS) | |
68 | { | |
69 | printf("FAILURE: call to AppleKeyStore method kAppleKeyStoreUserClientOpen failed.\n"); | |
70 | goto close; | |
71 | } | |
72 | ||
73 | io_result = IOConnectCallMethod(connection, command, inputs, input_count, input_structs, input_struct_count, outputs, output_count, NULL, NULL); | |
74 | ||
75 | if (io_result != kIOReturnSuccess) | |
76 | { | |
77 | printf("FAILURE: call to AppleKeyStore method %d failed.\n", command); | |
78 | goto close; | |
79 | } | |
80 | ||
81 | result = 0; | |
82 | ||
83 | close: | |
84 | IOServiceClose(apple_key_bag_service); | |
85 | ||
86 | end: | |
87 | return(result); | |
88 | } | |
89 | ||
90 | #ifndef KEYBAG_ENTITLEMENTS | |
91 | /* Just a wrapper around forking to exec keystorectl for commands requiring entitlements. */ | |
92 | int keystorectl(char * const command[]) | |
93 | { | |
94 | int child_result = -1; | |
95 | int result = -1; | |
96 | pid_t child = -1; | |
97 | ||
98 | child = fork(); | |
99 | ||
100 | if (child == -1) | |
101 | { | |
102 | printf("FAILURE: failed to fork.\n"); | |
103 | goto end; | |
104 | } | |
105 | else if (child == 0) | |
106 | { | |
107 | /* TODO: This keeps keystorectl from bombarding us with key state changes, but | |
108 | there must be a better way of doing this; killing stderr is a bit nasty, | |
109 | and if keystorectl fails, we want all the information we can get. */ | |
110 | fclose(stderr); | |
111 | fclose(stdin); | |
112 | execv(keystorectl_path, command); | |
113 | printf("FAILURE: child failed to execv keystorectl, errno = %s.\n", | |
114 | strerror(errno)); | |
115 | exit(EXIT_FAILURE); | |
116 | } | |
117 | ||
118 | if ((waitpid(child, &child_result, 0) != child) || WEXITSTATUS(child_result)) | |
119 | { | |
120 | printf("FAILURE: keystorectl failed.\n"); | |
121 | result = -1; | |
122 | } | |
123 | else | |
124 | { | |
125 | result = 0; | |
126 | } | |
127 | ||
128 | end: | |
129 | return(result); | |
130 | } | |
131 | #endif /* KEYBAG_ENTITLEMENTS */ | |
132 | ||
133 | /* Code based on Mobile Key Bag; specifically MKBDeviceSupportsContentProtection | |
134 | and MKBDeviceFormattedForContentProtection. */ | |
135 | /* We want to verify that we support content protection, and that | |
136 | we are formatted for it. */ | |
137 | int supports_content_prot() | |
138 | { | |
139 | int local_result = -1; | |
140 | int result = -1; | |
141 | uint32_t buffer_size = 1; | |
142 | char buffer[buffer_size]; | |
143 | io_registry_entry_t defaults = IO_OBJECT_NULL; | |
144 | kern_return_t k_result = KERN_FAILURE; | |
145 | struct statfs statfs_results; | |
146 | ||
147 | defaults = IORegistryEntryFromPath(kIOMasterPortDefault, kIODeviceTreePlane ":/defaults"); | |
148 | ||
149 | if (defaults == IO_OBJECT_NULL) | |
150 | { | |
151 | printf("FAILURE: failed to find defaults registry entry.\n"); | |
152 | goto end; | |
153 | } | |
154 | ||
155 | k_result = IORegistryEntryGetProperty(defaults, "content-protect", buffer, &buffer_size); | |
156 | ||
157 | if (k_result != KERN_SUCCESS) | |
158 | { /* This isn't a failure; it means the entry doesn't exist, so we assume CP | |
159 | is unsupported. */ | |
160 | result = 0; | |
161 | goto end; | |
162 | } | |
163 | ||
164 | /* At this point, we SUPPORT content protection... but are we formatted for it? */ | |
165 | /* This is ugly; we should be testing the file system we'll be testing in, not | |
166 | just /tmp/. */ | |
167 | local_result = statfs(g_target_path, &statfs_results); | |
168 | ||
169 | if (local_result == -1) | |
170 | { | |
171 | printf("FAILURE: failed to statfs the test directory, errno = %s.\n", | |
172 | strerror(errno)); | |
173 | } | |
174 | else if (statfs_results.f_flags & MNT_CPROTECT) | |
175 | { | |
176 | result = 1; | |
177 | } | |
178 | else | |
179 | { /* This isn't a failure, it means the filesystem isn't formatted for CP. */ | |
180 | result = 0; | |
181 | } | |
182 | ||
183 | end: | |
184 | return(result); | |
185 | } | |
186 | ||
187 | #if 0 | |
188 | int device_lock_state() | |
189 | { | |
190 | /* TODO: Actually implement this. */ | |
191 | /* We fail if a passcode already exists, and the methods being used to lock/unlock | |
192 | the device in this test appear to be synchronous... do we need this function? */ | |
193 | int result = -1; | |
194 | ||
195 | return(result); | |
196 | } | |
197 | #endif | |
198 | ||
199 | int lock_device() | |
200 | { | |
201 | int result = -1; | |
202 | ||
203 | #ifdef KEYBAG_ENTITLEMENTS | |
204 | /* If we're entitled, we can lock the device ourselves. */ | |
205 | uint64_t inputs[] = {device_keybag_handle}; | |
206 | uint32_t input_count = (sizeof(inputs) / sizeof(*inputs)); | |
207 | result = apple_key_store(kAppleKeyStoreKeyBagLock, inputs, input_count, NULL, 0, NULL, NULL); | |
208 | #else | |
209 | /* If we aren't entitled, we'll need to use keystorectl to lock the device. */ | |
210 | /* keystorectl seems to have a bus error (though it locks successfully) unless | |
211 | lock is passed an argument, so we'll also pass it the empty string. */ | |
212 | char * const keystorectl_args[] = {keystorectl_path, "lock", "", NULL}; | |
213 | result = keystorectl(keystorectl_args); | |
214 | #endif /* KEYBAG_ENTITLEMENTS */ | |
215 | ||
216 | return(result); | |
217 | } | |
218 | ||
219 | int unlock_device(char * passcode) | |
220 | { | |
221 | int result = -1; | |
222 | ||
223 | #ifdef KEYBAG_ENTITLEMENTS | |
224 | /* If we're entitled, we can unlock the device ourselves. */ | |
225 | uint64_t inputs[] = {device_keybag_handle}; | |
226 | uint32_t input_count = (sizeof(inputs) / sizeof(*inputs)); | |
227 | size_t input_struct_count = 0; | |
228 | ||
229 | if ((passcode == NULL) || ((input_struct_count = strnlen(passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN)) | |
230 | { | |
231 | passcode = ""; | |
232 | input_struct_count = 0; | |
233 | } | |
234 | ||
235 | result = apple_key_store(kAppleKeyStoreKeyBagUnlock, inputs, input_count, passcode, input_struct_count, NULL, NULL); | |
236 | #else | |
237 | /* If we aren't entitled, we'll need to use keystorectl to unlock the device. */ | |
238 | if ((passcode == NULL) || (strnlen(passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)) | |
239 | { | |
240 | passcode = ""; | |
241 | } | |
242 | ||
243 | char * const keystorectl_args[] = {keystorectl_path, "unlock", passcode, NULL}; | |
244 | result = keystorectl(keystorectl_args); | |
245 | #endif /* KEYBAG_ENTITLEMENTS */ | |
246 | ||
247 | return(result); | |
248 | } | |
249 | ||
250 | int set_passcode(char * new_passcode, char * old_passcode) | |
251 | { | |
252 | int result = -1; | |
253 | ||
254 | #ifdef KEYBAG_ENTITLEMENTS | |
255 | /* If we're entitled, we can set the passcode ourselves. */ | |
256 | uint64_t inputs[] = {device_keybag_handle}; | |
257 | uint32_t input_count = (sizeof(inputs) / sizeof(*inputs)); | |
258 | void * input_structs = NULL; | |
259 | size_t input_struct_count = 0; | |
260 | char buffer[CPT_AKS_BUF_SIZE]; | |
261 | char * buffer_ptr = buffer; | |
262 | uint32_t old_passcode_len = 0; | |
263 | uint32_t new_passcode_len = 0; | |
264 | ||
265 | if ((old_passcode == NULL) || ((old_passcode_len = strnlen(old_passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN)) | |
266 | { | |
267 | old_passcode = ""; | |
268 | old_passcode_len = 0; | |
269 | } | |
270 | ||
271 | if ((new_passcode == NULL) || ((new_passcode_len = strnlen(new_passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN)) | |
272 | { | |
273 | new_passcode = ""; | |
274 | new_passcode_len = 0; | |
275 | } | |
276 | ||
277 | *((uint32_t *) buffer_ptr) = ((uint32_t) 2); | |
278 | buffer_ptr += sizeof(uint32_t); | |
279 | *((uint32_t *) buffer_ptr) = old_passcode_len; | |
280 | buffer_ptr += sizeof(uint32_t); | |
281 | memcpy(buffer_ptr, old_passcode, old_passcode_len); | |
282 | buffer_ptr += ((old_passcode_len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1)); | |
283 | *((uint32_t *) buffer_ptr) = new_passcode_len; | |
284 | buffer_ptr += sizeof(uint32_t); | |
285 | memcpy(buffer_ptr, new_passcode, new_passcode_len); | |
286 | buffer_ptr += ((new_passcode_len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1)); | |
287 | input_structs = buffer; | |
288 | input_struct_count = (buffer_ptr - buffer); | |
289 | ||
290 | result = apple_key_store(kAppleKeyStoreKeyBagSetPasscode, inputs, input_count, input_structs, input_struct_count, NULL, NULL); | |
291 | #else | |
292 | /* If we aren't entitled, we'll need to use keystorectl to set the passcode. */ | |
293 | if ((old_passcode == NULL) || (strnlen(old_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)) | |
294 | { | |
295 | old_passcode = ""; | |
296 | } | |
297 | ||
298 | if ((new_passcode == NULL) || (strnlen(new_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)) | |
299 | { | |
300 | new_passcode = ""; | |
301 | } | |
302 | ||
303 | char * const keystorectl_args[] = {keystorectl_path, "change-password", old_passcode, new_passcode, NULL}; | |
304 | result = keystorectl(keystorectl_args); | |
305 | #endif /* KEYBAG_ENTITLEMENTS */ | |
306 | ||
307 | return(result); | |
308 | } | |
309 | ||
310 | int clear_passcode(char * passcode) | |
311 | { | |
312 | /* For the moment, this will set the passcode to the empty string (a known value); | |
313 | this will most likely need to change, or running this test may ruin everything(tm). */ | |
314 | int result = -1; | |
315 | ||
316 | result = set_passcode(NULL, passcode); | |
317 | ||
318 | return(result); | |
319 | } | |
320 | ||
321 | #if 0 | |
322 | /* Determines if we will try to test class C semanatics. */ | |
323 | int unlocked_since_boot() | |
324 | { | |
325 | /* TODO: Actually implement this. */ | |
326 | /* The actual semantics for CP mean that even with this primative, we would need | |
327 | set a passcode and then reboot the device in order to test this; this function | |
328 | will probably be rather worthless as a result. */ | |
329 | int result = 1; | |
330 | ||
331 | return(result); | |
332 | } | |
333 | #endif | |
334 | ||
335 | /* If the device has a passcode when we want to test it, things are going to go wrong. | |
336 | As such, we'll assume the device never has a passcode. | |
337 | No, not even then. | |
338 | Or we could just try "" to ""; it works. */ | |
339 | int has_passcode() | |
340 | { | |
341 | int result = -1; | |
342 | ||
343 | result = set_passcode(NULL, NULL); | |
344 | ||
345 | return(result); | |
346 | } | |
347 | ||
348 | int content_protection_test(void * argp) | |
349 | { | |
350 | #pragma unused (argp) | |
351 | int init_result = 0; | |
352 | int local_result = -1; | |
353 | int test_result = -1; | |
354 | int fd = -1; | |
355 | int dir_fd = -1; | |
356 | int subdir_fd = -1; | |
357 | int new_prot_class = -1; | |
358 | int old_prot_class = -1; | |
359 | int current_byte = 0; | |
360 | char filepath[PATH_MAX]; | |
361 | char dirpath[PATH_MAX]; | |
362 | char subdirpath[PATH_MAX]; | |
363 | char rd_buffer[CPT_IO_SIZE]; | |
364 | char wr_buffer[CPT_IO_SIZE]; | |
365 | char * passcode = "IAmASecurePassword"; | |
366 | ||
367 | /* Do some initial setup (names). */ | |
368 | bzero(filepath, PATH_MAX); | |
369 | bzero(dirpath, PATH_MAX); | |
370 | bzero(subdirpath, PATH_MAX); | |
371 | ||
372 | /* This is just easier than checking each result individually. */ | |
373 | init_result |= (strlcat(filepath, g_target_path, PATH_MAX) == PATH_MAX); | |
374 | init_result |= (strlcat(filepath, "/", PATH_MAX) == PATH_MAX); | |
375 | init_result |= (strlcpy(dirpath, filepath, PATH_MAX) == PATH_MAX); | |
376 | init_result |= (strlcat(filepath, "cpt_test_file", PATH_MAX) == PATH_MAX); | |
377 | init_result |= (strlcat(dirpath, "cpt_test_dir/", PATH_MAX) == PATH_MAX); | |
378 | init_result |= (strlcpy(subdirpath, dirpath, PATH_MAX) == PATH_MAX); | |
379 | init_result |= (strlcat(subdirpath, "cpt_test_subdir/", PATH_MAX) == PATH_MAX); | |
380 | ||
381 | if (init_result) | |
382 | { /* If any of the initialization failed, we're just going to fail now. */ | |
383 | printf("%s, line %d: failed to initialize test strings.\n", | |
384 | cpt_fail_header, __LINE__); | |
385 | goto end; | |
386 | } | |
387 | ||
388 | local_result = supports_content_prot(); | |
389 | ||
390 | if (local_result == -1) | |
391 | { | |
392 | printf("%s, line %d: failed to determine if content protection is supported.\n", | |
393 | cpt_fail_header, __LINE__); | |
394 | goto end; | |
395 | } | |
396 | else if (local_result == 0) | |
397 | { /* If we don't support content protection at the moment, pass the test. */ | |
398 | printf("This device does not support or is not formatted for content protection.\n"); | |
399 | test_result = 0; | |
400 | goto end; | |
401 | } | |
402 | ||
403 | /* If we support content protection, we'll need to be able to set the passcode. */ | |
404 | local_result = has_passcode(); | |
405 | ||
406 | if (local_result == -1) | |
407 | { | |
408 | printf("%s, line %d: the device appears to have a passcode.\n", | |
409 | cpt_fail_header, __LINE__); | |
410 | goto end; | |
411 | } | |
412 | ||
413 | if (set_passcode(passcode, NULL)) | |
414 | { | |
415 | printf("%s, line %d: failed to set a new passcode.\n", | |
416 | cpt_fail_header, __LINE__); | |
417 | goto end; | |
418 | } | |
419 | ||
39236c6e | 420 | fd = open(filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777); |
316670eb A |
421 | |
422 | if (fd == -1) | |
423 | { | |
424 | printf("%s, line %d: failed to create the test file, errno = %s.\n", | |
425 | cpt_fail_header, __LINE__, strerror(errno)); | |
426 | goto remove_passcode; | |
427 | } | |
428 | ||
429 | /* Ensure we can freely read and change protection classes when unlocked. */ | |
430 | for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_F; new_prot_class++) | |
431 | { | |
432 | old_prot_class = GET_PROT_CLASS(fd); | |
433 | ||
434 | if (old_prot_class == -1) | |
435 | { | |
436 | printf("%s, line %d: failed to get protection class when unlocked, errno = %s.\n", | |
437 | cpt_fail_header, __LINE__, strerror(errno)); | |
438 | goto cleanup_file; | |
439 | } | |
440 | ||
441 | if (SET_PROT_CLASS(fd, new_prot_class)) | |
442 | { | |
443 | printf("%s, line %d: failed to change protection class from %d to %d during unlock, errno = %s.\n", | |
444 | cpt_fail_header, __LINE__, old_prot_class, new_prot_class, strerror(errno)); | |
445 | goto cleanup_file; | |
446 | } | |
447 | } | |
448 | ||
39236c6e A |
449 | /* Query the filesystem for the default CP level (Is it C?) */ |
450 | #ifndef F_GETDEFAULTPROTLEVEL | |
451 | #define F_GETDEFAULTPROTLEVEL 79 | |
452 | #endif | |
453 | ||
454 | old_prot_class = fcntl(fd, F_GETDEFAULTPROTLEVEL); | |
455 | if (old_prot_class == -1) { | |
456 | printf("%s , line %d: failed to acquire default protection level for filesystem , errno = %s \n", | |
457 | cpt_fail_header, __LINE__, strerror(errno)); | |
458 | goto cleanup_file; | |
459 | } | |
460 | ||
461 | /* XXX: Do we want to do anything with the level? What should it be? */ | |
462 | ||
463 | ||
464 | /* | |
465 | * files are allowed to move into F, but not out of it. They can also only do so | |
466 | * when they do not have content. | |
467 | */ | |
468 | close (fd); | |
469 | unlink (filepath); | |
470 | ||
471 | ||
472 | /* re-create the file */ | |
473 | fd = open (filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC); | |
474 | if (fd == -1) { | |
475 | printf("%s, line %d: failed to create the test file, errno = %s.\n", | |
476 | cpt_fail_header, __LINE__, strerror(errno)); | |
316670eb A |
477 | goto cleanup_file; |
478 | } | |
479 | ||
480 | /* Try making a class A file while locked. */ | |
481 | if (lock_device()) | |
482 | { | |
483 | PRINT_LOCK_FAIL; | |
484 | goto cleanup_file; | |
485 | } | |
486 | ||
487 | if (!SET_PROT_CLASS(fd, PROTECTION_CLASS_A)) | |
488 | { | |
489 | printf("%s, line %d: was able to change protection class from D to A when locked.\n", | |
490 | cpt_fail_header, __LINE__); | |
491 | goto cleanup_file; | |
492 | } | |
493 | ||
494 | if (unlock_device(passcode)) | |
495 | { | |
496 | PRINT_UNLOCK_FAIL; | |
497 | goto cleanup_file; | |
498 | } | |
499 | ||
500 | /* Attempt opening/IO to a class A file while unlocked. */ | |
501 | if (SET_PROT_CLASS(fd, PROTECTION_CLASS_A)) | |
502 | { | |
503 | printf("%s, line %d: failed to change protection class from D to A when unlocked, errno = %s.\n", | |
504 | cpt_fail_header, __LINE__, strerror(errno)); | |
505 | goto cleanup_file; | |
506 | } | |
507 | ||
508 | close(fd); | |
509 | fd = open(filepath, O_RDWR | O_CLOEXEC); | |
510 | ||
511 | if (fd == -1) | |
512 | { | |
513 | printf("%s, line %d: failed to open a class A file when unlocked, errno = %s.\n", | |
514 | cpt_fail_header, __LINE__, strerror(errno)); | |
515 | goto remove_file; | |
516 | } | |
517 | ||
518 | /* TODO: Write specific data we can check for. | |
519 | If we're going to do that, the write scheme should be deliberately ugly. */ | |
520 | current_byte = 0; | |
521 | ||
522 | while (current_byte < CPT_IO_SIZE) | |
523 | { | |
524 | local_result = pwrite(fd, &wr_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte); | |
525 | ||
526 | if (local_result == -1) | |
527 | { | |
528 | printf("%s, line %d: failed to write to class A file when unlocked, errno = %s.\n", | |
529 | cpt_fail_header, __LINE__, strerror(errno)); | |
530 | goto cleanup_file; | |
531 | } | |
532 | ||
533 | current_byte += local_result; | |
534 | } | |
535 | ||
536 | current_byte = 0; | |
537 | ||
538 | while (current_byte < CPT_IO_SIZE) | |
539 | { | |
540 | local_result = pread(fd, &rd_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte); | |
541 | ||
542 | if (local_result == -1) | |
543 | { | |
544 | printf("%s, line %d: failed to read from class A file when unlocked, errno = %s.\n", | |
545 | cpt_fail_header, __LINE__, strerror(errno)); | |
546 | goto cleanup_file; | |
547 | } | |
548 | ||
549 | current_byte += local_result; | |
550 | } | |
551 | ||
552 | /* Again, but now while locked; and try to change the file class as well. */ | |
553 | if (lock_device()) | |
554 | { | |
555 | PRINT_LOCK_FAIL; | |
556 | goto cleanup_file; | |
557 | } | |
558 | ||
559 | if (pread(fd, rd_buffer, CPT_IO_SIZE, 0) > 0) | |
560 | { | |
561 | printf("%s, line %d: was able to read from a class A file when locked.\n", | |
562 | cpt_fail_header, __LINE__); | |
563 | goto cleanup_file; | |
564 | } | |
565 | ||
566 | if (pwrite(fd, wr_buffer, CPT_IO_SIZE, 0) > 0) | |
567 | { | |
568 | printf("%s, line %d: was able to write to a class A file when locked.\n", | |
569 | cpt_fail_header, __LINE__); | |
570 | goto cleanup_file; | |
571 | } | |
572 | ||
573 | if (!SET_PROT_CLASS(fd, PROTECTION_CLASS_D)) | |
574 | { | |
575 | printf("%s, line %d: was able to change protection class from A to D when locked.\n", | |
576 | cpt_fail_header, __LINE__); | |
577 | goto cleanup_file; | |
578 | } | |
579 | ||
580 | /* Try to open and truncate the file. */ | |
581 | close(fd); | |
582 | fd = open(filepath, O_RDWR | O_TRUNC | O_CLOEXEC); | |
583 | ||
584 | if (fd != -1) | |
585 | { | |
586 | printf("%s, line %d: was able to open and truncate a class A file when locked.\n", | |
587 | cpt_fail_header, __LINE__); | |
588 | goto cleanup_file; | |
589 | } | |
590 | ||
591 | /* Try to open the file */ | |
592 | fd = open(filepath, O_RDWR | O_CLOEXEC); | |
593 | ||
594 | if (fd != -1) | |
595 | { | |
596 | printf("%s, line %d: was able to open a class A file when locked.\n", | |
597 | cpt_fail_header, __LINE__); | |
598 | goto cleanup_file; | |
599 | } | |
600 | ||
601 | /* What about class B files? */ | |
602 | if (unlock_device(passcode)) | |
603 | { | |
604 | PRINT_UNLOCK_FAIL; | |
605 | goto cleanup_file; | |
606 | } | |
607 | ||
608 | fd = open(filepath, O_RDWR | O_CLOEXEC); | |
609 | ||
610 | if (fd == -1) | |
611 | { | |
612 | printf("%s, line %d: was unable to open a class A file when unlocked.\n", | |
613 | cpt_fail_header, __LINE__); | |
614 | goto cleanup_file; | |
615 | } | |
616 | ||
617 | if (SET_PROT_CLASS(fd, PROTECTION_CLASS_D)) | |
618 | { | |
619 | printf("%s, line %d: failed to change protection class from A to D when unlocked, errno = %s.\n", | |
620 | cpt_fail_header, __LINE__, strerror(errno)); | |
621 | goto cleanup_file; | |
622 | } | |
623 | ||
624 | if (lock_device()) | |
625 | { | |
626 | PRINT_LOCK_FAIL; | |
627 | goto cleanup_file; | |
628 | } | |
629 | ||
630 | /* Can we create a class B file while locked? */ | |
631 | if (SET_PROT_CLASS(fd, PROTECTION_CLASS_B)) | |
632 | { | |
633 | printf("%s, line %d: failed to change protection class from D to B when locked, errno = %s.\n", | |
634 | cpt_fail_header, __LINE__, strerror(errno)); | |
635 | goto cleanup_file; | |
636 | } | |
637 | ||
39236c6e A |
638 | if (GET_PROT_CLASS (fd) != PROTECTION_CLASS_B) { |
639 | printf("%s, line %d: Failed to switch to class B file \n", | |
640 | cpt_fail_header, __LINE__ ); | |
641 | goto cleanup_file; | |
642 | } | |
643 | ||
644 | ||
316670eb A |
645 | /* We should also be able to read/write to the file descriptor while it is open. */ |
646 | current_byte = 0; | |
647 | ||
648 | while (current_byte < CPT_IO_SIZE) | |
649 | { | |
650 | local_result = pwrite(fd, &wr_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte); | |
651 | ||
652 | if (local_result == -1) | |
653 | { | |
654 | printf("%s, line %d: failed to write to new class B file when locked, errno = %s.\n", | |
655 | cpt_fail_header, __LINE__, strerror(errno)); | |
656 | goto cleanup_file; | |
657 | } | |
658 | ||
659 | current_byte += local_result; | |
660 | } | |
661 | ||
662 | current_byte = 0; | |
663 | ||
664 | while (current_byte < CPT_IO_SIZE) | |
665 | { | |
666 | local_result = pread(fd, &rd_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte); | |
667 | ||
668 | if (local_result == -1) | |
669 | { | |
670 | printf("%s, line %d: failed to read from new class B file when locked, errno = %s.\n", | |
671 | cpt_fail_header, __LINE__, strerror(errno)); | |
672 | goto cleanup_file; | |
673 | } | |
674 | ||
675 | current_byte += local_result; | |
676 | } | |
677 | ||
678 | /* We should not be able to open a class B file under lock. */ | |
679 | close(fd); | |
680 | fd = open(filepath, O_RDWR | O_CLOEXEC); | |
681 | ||
682 | if (fd != -1) | |
683 | { | |
684 | printf("%s, line %d: was able to open a class B file when locked.\n", | |
685 | cpt_fail_header, __LINE__); | |
686 | goto cleanup_file; | |
687 | } | |
688 | ||
689 | unlink(filepath); | |
690 | ||
691 | /* We still need to test directory semantics. */ | |
692 | if (mkdir(dirpath, 0x0777) == -1) | |
693 | { | |
694 | printf("%s, line %d: failed to create a new directory when locked, errno = %s.\n", | |
695 | cpt_fail_header, __LINE__, strerror(errno)); | |
696 | goto remove_passcode; | |
697 | } | |
698 | ||
699 | /* The newly created directory should not have a protection class. */ | |
700 | dir_fd = open(dirpath, O_RDONLY | O_CLOEXEC); | |
701 | ||
702 | if (dir_fd == -1) | |
703 | { | |
704 | printf("%s, line %d: failed to open an unclassed directory when locked, errno = %s.\n", | |
705 | cpt_fail_header, __LINE__, strerror(errno)); | |
706 | goto remove_dir; | |
707 | } | |
708 | ||
39236c6e | 709 | if ((GET_PROT_CLASS(dir_fd) != PROTECTION_CLASS_D) && (GET_PROT_CLASS(dir_fd) != PROTECTION_CLASS_DIR_NONE)) |
316670eb | 710 | { |
39236c6e | 711 | printf("%s, line %d: newly created directory had a non-D and non-NONE protection class.\n", |
316670eb A |
712 | cpt_fail_header, __LINE__); |
713 | goto cleanup_dir; | |
714 | } | |
715 | ||
716 | if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_A)) | |
717 | { | |
718 | printf("%s, line %d: was unable to change a directory from class D to class A during lock.\n", | |
719 | cpt_fail_header, __LINE__); | |
720 | goto cleanup_dir; | |
721 | } | |
722 | ||
723 | if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_D)) | |
724 | { | |
725 | printf("%s, line %d: failed to change a directory from class A to class D during lock, errno = %s.\n", | |
726 | cpt_fail_header, __LINE__, strerror(errno)); | |
727 | goto cleanup_dir; | |
728 | } | |
729 | ||
730 | /* Do all files created in the directory properly inherit the directory's protection class? */ | |
731 | if ((strlcpy(filepath, dirpath, PATH_MAX) == PATH_MAX) || (strlcat(filepath, "cpt_test_file", PATH_MAX) == PATH_MAX)) | |
732 | { | |
733 | printf("%s, line %d: failed to construct the path for a file in the directory.\n", | |
734 | cpt_fail_header, __LINE__); | |
735 | goto cleanup_dir; | |
736 | } | |
737 | ||
738 | if (unlock_device(passcode)) | |
739 | { | |
740 | PRINT_UNLOCK_FAIL; | |
741 | goto cleanup_dir; | |
742 | } | |
743 | ||
39236c6e | 744 | for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_D; new_prot_class++) |
316670eb | 745 | { |
39236c6e | 746 | int getclass_dir; |
316670eb A |
747 | old_prot_class = GET_PROT_CLASS(dir_fd); |
748 | ||
749 | if (old_prot_class == -1) | |
750 | { | |
751 | printf("%s, line %d: failed to get the protection class for the directory, errno = %s.\n", | |
752 | cpt_fail_header, __LINE__, strerror(errno)); | |
753 | goto cleanup_dir; | |
754 | } | |
755 | ||
756 | if (SET_PROT_CLASS(dir_fd, new_prot_class)) | |
757 | { | |
758 | printf("%s, line %d: failed to change the protection class for the directory from %d to %d, errno = %s.\n", | |
759 | cpt_fail_header, __LINE__, old_prot_class, new_prot_class, strerror(errno)); | |
760 | goto cleanup_dir; | |
761 | } | |
762 | ||
39236c6e A |
763 | getclass_dir = GET_PROT_CLASS(dir_fd); |
764 | if (getclass_dir != new_prot_class) { | |
765 | printf("%s, line %d: failed to get the new protection class for the directory %d (got %d) \n", | |
766 | cpt_fail_header, __LINE__, new_prot_class, getclass_dir); | |
767 | goto cleanup_dir; | |
768 | ||
769 | } | |
770 | ||
771 | fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777); | |
316670eb A |
772 | |
773 | if (fd == -1) | |
774 | { | |
775 | printf("%s, line %d: failed to create a file in a class %d directory when unlocked, errno = %s.\n", | |
776 | cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); | |
777 | goto cleanup_dir; | |
778 | } | |
779 | ||
780 | local_result = GET_PROT_CLASS(fd); | |
781 | ||
782 | if (local_result == -1) | |
783 | { | |
784 | printf("%s, line %d: failed to get the new file's protection class, errno = %s.\n", | |
785 | cpt_fail_header, __LINE__, strerror(errno)); | |
786 | goto cleanup_file; | |
787 | } | |
788 | else if (local_result != new_prot_class) | |
789 | { | |
39236c6e A |
790 | |
791 | printf("%s, line %d: new file (%d) did not inherit the directory's protection class (%d) .\n", | |
792 | cpt_fail_header, __LINE__, local_result, new_prot_class); | |
316670eb A |
793 | goto cleanup_file; |
794 | } | |
795 | ||
796 | close(fd); | |
797 | unlink(filepath); | |
798 | } | |
799 | ||
800 | /* Do we disallow creation of a class F directory? */ | |
801 | if (!SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_F)) | |
802 | { | |
803 | printf("%s, line %d: creation of a class F directory did not fail as expected.\n", | |
804 | cpt_fail_header, __LINE__); | |
805 | goto cleanup_dir; | |
806 | } | |
807 | ||
808 | /* And are class A and class B semantics followed for when we create these files during lock? */ | |
809 | if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_A)) | |
810 | { | |
811 | printf("%s, line %d: failed to change directory class from F to A when unlocked, errno = %s.\n", | |
812 | cpt_fail_header, __LINE__, strerror(errno)); | |
813 | goto cleanup_dir; | |
814 | } | |
815 | ||
816 | if (lock_device()) | |
817 | { | |
818 | PRINT_LOCK_FAIL; | |
819 | goto cleanup_dir; | |
820 | } | |
821 | ||
39236c6e | 822 | fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777); |
316670eb A |
823 | |
824 | if (fd != -1) | |
825 | { | |
826 | printf("%s, line %d: was able to create a new file in a class A directory when locked.\n", | |
827 | cpt_fail_header, __LINE__, strerror(errno)); | |
828 | goto cleanup_file; | |
829 | } | |
830 | ||
831 | if (unlock_device(passcode)) | |
832 | { | |
833 | PRINT_UNLOCK_FAIL; | |
834 | goto cleanup_dir; | |
835 | } | |
836 | ||
837 | if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_B)) | |
838 | { | |
839 | printf("%s, line %d: failed to change directory class from A to B when unlocked, errno = %s.\n", | |
840 | cpt_fail_header, __LINE__, strerror(errno)); | |
841 | goto cleanup_dir; | |
842 | } | |
843 | ||
844 | if (lock_device()) | |
845 | { | |
846 | PRINT_LOCK_FAIL; | |
847 | goto cleanup_dir; | |
848 | } | |
849 | ||
39236c6e | 850 | fd = open(filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777); |
316670eb A |
851 | |
852 | if (fd == -1) | |
853 | { | |
854 | printf("%s, line %d: failed to create new file in class B directory when locked, errno = %s.\n", | |
855 | cpt_fail_header, __LINE__, strerror(errno)); | |
856 | goto cleanup_dir; | |
857 | } | |
858 | ||
859 | local_result = GET_PROT_CLASS(fd); | |
860 | ||
861 | if (local_result == -1) | |
862 | { | |
863 | printf("%s, line %d: failed to get protection class for a new file when locked, errno = %s.\n", | |
864 | cpt_fail_header, __LINE__, strerror(errno)); | |
865 | goto cleanup_file; | |
866 | } | |
867 | else if (local_result != PROTECTION_CLASS_B) | |
868 | { | |
869 | printf("%s, line %d: new file in class B directory did not inherit protection class.\n", | |
870 | cpt_fail_header, __LINE__, strerror(errno)); | |
871 | goto cleanup_file; | |
872 | } | |
873 | ||
874 | /* What happens when we try to create new subdirectories? */ | |
875 | if (unlock_device(passcode)) | |
876 | { | |
877 | PRINT_UNLOCK_FAIL; | |
878 | goto cleanup_file; | |
879 | } | |
880 | ||
39236c6e | 881 | for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_D; new_prot_class++) |
316670eb A |
882 | { |
883 | if (SET_PROT_CLASS(dir_fd, new_prot_class)) | |
884 | { | |
885 | printf("%s, line %d: failed to change directory to class %d, errno = %s.\n", | |
886 | cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); | |
887 | goto cleanup_file; | |
888 | } | |
889 | ||
890 | local_result = mkdir(subdirpath, 0x0777); | |
891 | ||
892 | if (local_result == -1) | |
893 | { | |
894 | printf("%s, line %d: failed to create subdirectory in class %d directory, errno = %s.\n", | |
895 | cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); | |
896 | goto cleanup_file; | |
897 | } | |
898 | ||
899 | subdir_fd = open(subdirpath, O_RDONLY | O_CLOEXEC); | |
900 | ||
901 | if (subdir_fd == -1) | |
902 | { | |
903 | printf("%s, line %d: failed to open subdirectory in class %d directory, errno = %s.\n", | |
904 | cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); | |
905 | goto remove_subdir; | |
906 | } | |
907 | ||
908 | local_result = GET_PROT_CLASS(subdir_fd); | |
909 | ||
910 | if (local_result == -1) | |
911 | { | |
912 | printf("%s, line %d: failed to get class of new subdirectory of class %d directory, errno = %s.\n", | |
913 | cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); | |
914 | goto cleanup_subdir; | |
915 | } | |
916 | else if (local_result != new_prot_class) | |
917 | { | |
918 | printf("%s, line %d: new subdirectory had different class than class %d parent.\n", | |
919 | cpt_fail_header, __LINE__, new_prot_class); | |
920 | goto cleanup_subdir; | |
921 | } | |
922 | ||
923 | close(subdir_fd); | |
924 | rmdir(subdirpath); | |
925 | } | |
926 | ||
927 | /* If we've made it this far, the test was successful. */ | |
928 | test_result = 0; | |
929 | ||
930 | cleanup_subdir: | |
931 | close(subdir_fd); | |
932 | ||
933 | remove_subdir: | |
934 | rmdir(subdirpath); | |
935 | ||
936 | cleanup_file: | |
937 | close(fd); | |
938 | ||
939 | remove_file: | |
940 | unlink(filepath); | |
941 | ||
942 | cleanup_dir: | |
943 | close(dir_fd); | |
944 | ||
945 | remove_dir: | |
946 | rmdir(dirpath); | |
947 | ||
948 | remove_passcode: | |
949 | /* Try to unlock the device (no ramifications if it isn't locked when we try) and remove the passcode. */ | |
950 | if (unlock_device(passcode)) | |
951 | { | |
952 | printf("WARNING: failed to unlock the device.\n"); | |
953 | } | |
954 | ||
955 | if (clear_passcode(passcode)) | |
956 | { | |
957 | printf("WARNING: failed to clear the passcode.\n"); | |
958 | } | |
959 | ||
960 | end: | |
961 | return(test_result); | |
962 | } | |
963 |