1 #include <darwintest.h>
2 #include <darwintest_utils.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <stdlib.h>
6 #include <sys/mount.h>
7 #include <sys/wait.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <string.h>
12
13 #include <IOKit/IOKitLib.h>
14 #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
15 #include <Kernel/sys/content_protection.h>
16
17 #define CPT_IO_SIZE 4096
18 #define CPT_AKS_BUF_SIZE 256
19 #define CPT_MAX_PASS_LEN 64
20
21 #define GET_PROT_CLASS(fd) \
22 fcntl((fd), F_GETPROTECTIONCLASS)
23
24 #define SET_PROT_CLASS(fd, prot_class) \
25 fcntl((fd), F_SETPROTECTIONCLASS, (prot_class))
26
27 #define KEYSTORECTL_PATH "/usr/local/bin/keystorectl"
28 #define KEYBAGDTEST_PATH "/usr/local/bin/keybagdTest"
29 #define TEMP_DIR_TEMPLATE "/tmp/data_protection_test.XXXXXXXX"
30 #define TEST_PASSCODE "IAmASecurePassword"
31
32 int g_fd = -1;
33 int g_dir_fd = -1;
34 int g_subdir_fd = -1;
35 int g_passcode_set = 0;
36
37 char g_test_tempdir[PATH_MAX] = TEMP_DIR_TEMPLATE;
38 char g_filepath[PATH_MAX] = "";
39 char g_dirpath[PATH_MAX] = "";
40 char g_subdirpath[PATH_MAX] = "";
41
42 int apple_key_store(
43 uint32_t command,
44 uint64_t * inputs,
45 uint32_t input_count,
46 void * input_structs,
47 size_t input_struct_count,
48 uint64_t * outputs,
49 uint32_t * output_count
50 );
51 int spawn_proc(char * const command[]);
52 int supports_content_prot(void);
53 char* dp_class_num_to_string(int num);
54 int lock_device(void);
55 int unlock_device(char * passcode);
56 int set_passcode(char * new_passcode, char * old_passcode);
57 int clear_passcode(char * passcode);
58 int has_passcode(void);
59 void setup(void);
60 void cleanup(void);
61
62 T_DECL(data_protection,
63 "Verify behavior of the various data protection classes") {
64 int local_result = -1;
65 int new_prot_class = -1;
66 int old_prot_class = -1;
67 int current_byte = 0;
68 char rd_buffer[CPT_IO_SIZE];
69 char wr_buffer[CPT_IO_SIZE];
70
71 #if TARGET_OS_OSX && defined(__arm64__)
72 T_SKIP("ASM support not yet implemented (rdar://61591896)");
73 #endif /* TARGET_OS_OSX && defined(__arm64__) */
74
75 setup();
76
77 /*
78 * Ensure we can freely read and change
79 * protection classes when unlocked.
80 */
81 for (
82 new_prot_class = PROTECTION_CLASS_A;
83 new_prot_class <= PROTECTION_CLASS_F;
84 new_prot_class++
85 ) {
86 T_ASSERT_NE(
87 old_prot_class = GET_PROT_CLASS(g_fd),
88 -1,
89 "Get protection class when locked"
90 );
91 T_WITH_ERRNO;
92 T_ASSERT_NE(
93 SET_PROT_CLASS(g_fd, new_prot_class),
94 -1,
95 "Should be able to change protection "
96 "from %s to %s while unlocked",
97 dp_class_num_to_string(old_prot_class),
98 dp_class_num_to_string(new_prot_class)
99 );
100 }
101
102 /* Query the filesystem for the default CP level (Is it C?) */
103 #ifndef F_GETDEFAULTPROTLEVEL
104 #define F_GETDEFAULTPROTLEVEL 79
105 #endif
106
107 T_WITH_ERRNO;
108 T_ASSERT_NE(
109 old_prot_class = fcntl(g_fd, F_GETDEFAULTPROTLEVEL),
110 -1,
111 "Get default protection level for filesystem"
112 );
113
114 /* XXX: Do we want to do anything with the level? What should it be? */
115
116 /*
117 * files are allowed to move into F, but not out of it. They can also
118 * only do so when they do not have content.
119 */
120 close(g_fd);
121 unlink(g_filepath);
122
123 /* re-create the file */
124 T_WITH_ERRNO;
125 T_ASSERT_GE(
126 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC),
127 0,
128 "Recreate test file"
129 );
130
131 /* Try making a class A file while locked. */
132 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
133
134 T_WITH_ERRNO;
135 T_ASSERT_EQ(
136 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_A),
137 -1,
138 "Should not be able to change protection "
139 "from class D to class A when locked"
140 );
141 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
142
143 /* Attempt opening/IO to a class A file while unlocked. */
144 T_WITH_ERRNO;
145 T_ASSERT_EQ(
146 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_A),
147 0,
148 "Should be able to change protection "
149 "from class D to class A when unlocked"
150 );
151
152 close(g_fd);
153
154 T_WITH_ERRNO;
155 T_ASSERT_GE(
156 g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
157 0,
158 "Should be able to open a class A file when unlocked");
159
160 /*
161 * TODO: Write specific data we can check for. If we're going to do
162 * that, the write scheme should be deliberately ugly.
163 */
164 current_byte = 0;
165
166 while (current_byte < CPT_IO_SIZE) {
167 local_result = pwrite(
168 g_fd,
169 &wr_buffer[current_byte],
170 CPT_IO_SIZE - current_byte,
171 current_byte
172 );
173
174 T_WITH_ERRNO;
175 T_ASSERT_NE(
176 local_result,
177 -1,
178 "Should be able to write to "
179 "a class A file when unlocked"
180 );
181
182 current_byte += local_result;
183 }
184
185 current_byte = 0;
186
187 while (current_byte < CPT_IO_SIZE) {
188 local_result = pread(
189 g_fd,
190 &rd_buffer[current_byte],
191 CPT_IO_SIZE - current_byte,
192 current_byte
193 );
194
195 T_WITH_ERRNO;
196 T_ASSERT_NE(
197 local_result,
198 -1,
199 "Should be able to read from "
200 "a class A file when unlocked"
201 );
202
203 current_byte += local_result;
204 }
205
206 /*
207 * Again, but now while locked; and try to change the file class
208 * as well.
209 */
210 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
211
212 T_ASSERT_LE(
213 pread(g_fd, rd_buffer, CPT_IO_SIZE, 0),
214 0,
215 "Should not be able to read from a class A file when locked"
216 );
217
218 T_ASSERT_LE(
219 pwrite(g_fd, wr_buffer, CPT_IO_SIZE, 0),
220 0,
221 "Should not be able to write to a class A file when locked"
222 );
223
224 T_ASSERT_EQ(
225 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_D),
226 -1,
227 "Should not be able to change protection "
228 "from class A to class D when locked"
229 );
230
231 /* Try to open and truncate the file. */
232 close(g_fd);
233
234 T_ASSERT_EQ(
235 g_fd = open(g_filepath, O_RDWR | O_TRUNC | O_CLOEXEC),
236 -1,
237 "Should not be able to open and truncate "
238 "a class A file when locked"
239 );
240
241 /* Try to open the file */
242 T_ASSERT_EQ(
243 g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
244 -1,
245 "Should not be able to open a class A file when locked"
246 );
247
248 /* What about class B files? */
249 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
250
251 T_ASSERT_GE(
252 g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
253 0,
254 "Should be able to open a class A file when unlocked"
255 );
256
257 T_WITH_ERRNO;
258 T_ASSERT_EQ(
259 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_D),
260 0,
261 "Should be able to change protection "
262 "class from A to D when unlocked"
263 );
264
265 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
266
267 /* Can we create a class B file while locked? */
268 T_ASSERT_EQ(
269 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_B),
270 0,
271 "Should be able to change protection "
272 "class from D to B when locked"
273 );
274
275 T_ASSERT_EQ(
276 GET_PROT_CLASS(g_fd),
277 PROTECTION_CLASS_B,
278 "File should now have class B protection"
279 );
280
281 /*
282 * We should also be able to read/write to the
283 * file descriptor while it is open.
284 */
285 current_byte = 0;
286
287 while (current_byte < CPT_IO_SIZE) {
288 local_result = pwrite(
289 g_fd,
290 &wr_buffer[current_byte],
291 CPT_IO_SIZE - current_byte,
292 current_byte
293 );
294
295 T_WITH_ERRNO;
296 T_ASSERT_NE(
297 local_result,
298 -1,
299 "Should be able to write to a "
300 "new class B file when locked"
301 );
302
303 current_byte += local_result;
304 }
305
306 current_byte = 0;
307
308 while (current_byte < CPT_IO_SIZE) {
309 local_result = pread(
310 g_fd,
311 &rd_buffer[current_byte],
312 CPT_IO_SIZE - current_byte,
313 current_byte
314 );
315
316 T_ASSERT_NE(
317 local_result,
318 -1,
319 "Should be able to read from a "
320 "new class B file when locked"
321 );
322
323 current_byte += local_result;
324 }
325
326 /* We should not be able to open a class B file under lock. */
327 close(g_fd);
328 T_WITH_ERRNO;
329 T_ASSERT_EQ(
330 g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
331 -1,
332 "Should not be able to open a class B file when locked"
333 );
334
335 unlink(g_filepath);
336
337 /* We still need to test directory semantics. */
338 T_WITH_ERRNO;
339 T_ASSERT_NE(
340 mkdir(g_dirpath, 0x0777),
341 -1,
342 "Should be able to create a new directory when locked"
343 );
344
345 /* The newly created directory should not have a protection class. */
346 T_ASSERT_NE(
347 g_dir_fd = open(g_dirpath, O_RDONLY | O_CLOEXEC),
348 -1,
349 "Should be able to open an unclassed directory when locked"
350 );
351
352 T_ASSERT_TRUE(
353 GET_PROT_CLASS(g_dir_fd) == PROTECTION_CLASS_D ||
354 GET_PROT_CLASS(g_dir_fd) == PROTECTION_CLASS_DIR_NONE,
355 "Directory protection class sholud be D or NONE"
356 );
357
358 T_ASSERT_EQ(
359 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_A),
360 0,
361 "Should be able to change a directory from "
362 "class D to class A while locked"
363 );
364
365 T_ASSERT_EQ(
366 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_D),
367 0,
368 "Should be able to change a directory from "
369 "class A to class D while locked"
370 );
371
372 /*
373 * Do all files created in the directory properly inherit the
374 * directory's protection class?
375 */
376 T_SETUPBEGIN;
377 T_ASSERT_LT(
378 strlcpy(g_filepath, g_dirpath, PATH_MAX),
379 PATH_MAX,
380 "Construct path for file in the directory"
381 );
382 T_ASSERT_LT(
383 strlcat(g_filepath, "test_file", PATH_MAX),
384 PATH_MAX,
385 "Construct path for file in the directory"
386 );
387 T_SETUPEND;
388
389 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
390
391 for (
392 new_prot_class = PROTECTION_CLASS_A;
393 new_prot_class <= PROTECTION_CLASS_D;
394 new_prot_class++
395 ) {
396 int getclass_dir;
397
398 T_WITH_ERRNO;
399 T_ASSERT_NE(
400 old_prot_class = GET_PROT_CLASS(g_dir_fd),
401 -1,
402 "Get protection class for the directory"
403 );
404
405 T_WITH_ERRNO;
406 T_ASSERT_EQ(
407 SET_PROT_CLASS(g_dir_fd, new_prot_class),
408 0,
409 "Should be able to change directory "
410 "protection from %s to %s",
411 dp_class_num_to_string(old_prot_class),
412 dp_class_num_to_string(new_prot_class)
413 );
414
415 T_EXPECT_EQ(
416 getclass_dir = GET_PROT_CLASS(g_dir_fd),
417 new_prot_class,
418 "Get protection class for the directory"
419 );
420
421 T_WITH_ERRNO;
422 T_ASSERT_GE(
423 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777),
424 0,
425 "Should be able to create file in "
426 "%s directory when unlocked",
427 dp_class_num_to_string(new_prot_class)
428 );
429
430 T_WITH_ERRNO;
431 T_ASSERT_NE(
432 local_result = GET_PROT_CLASS(g_fd),
433 -1,
434 "Get the new file's protection class"
435 );
436
437 T_ASSERT_EQ(
438 local_result,
439 new_prot_class,
440 "File should have %s protection",
441 dp_class_num_to_string(new_prot_class)
442 );
443
444 close(g_fd);
445 unlink(g_filepath);
446 }
447
448 /* Do we disallow creation of a class F directory? */
449 T_ASSERT_NE(
450 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_F),
451 0,
452 "Should not be able to create class F directory"
453 );
454
455 /*
456 * Are class A and class B semantics followed for when
457 * we create these files during lock?
458 */
459 T_WITH_ERRNO;
460 T_ASSERT_EQ(
461 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_A),
462 0,
463 "Should be able to change protection "
464 "from class F to class A when unlocked"
465 );
466
467 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
468
469 T_ASSERT_EQ(
470 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777),
471 -1,
472 "Should not be able to create a new file "
473 "in a class A directory when locked"
474 );
475
476 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
477
478 T_WITH_ERRNO;
479 T_ASSERT_EQ(
480 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_B),
481 0,
482 "Should be able to change directory "
483 "from class A to class B when unlocked"
484 );
485
486 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
487
488 T_ASSERT_GE(
489 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777),
490 0,
491 "Should be able to create a new file "
492 "in class B directory when locked"
493 );
494
495 T_ASSERT_NE(
496 local_result = GET_PROT_CLASS(g_fd),
497 -1,
498 "Get the new file's protection class"
499 );
500
501 T_ASSERT_EQ(
502 local_result,
503 PROTECTION_CLASS_B,
504 "File should inherit protection class of class B directory"
505 );
506
507 /* What happens when we try to create new subdirectories? */
508 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
509
510 for (
511 new_prot_class = PROTECTION_CLASS_A;
512 new_prot_class <= PROTECTION_CLASS_D;
513 new_prot_class++
514 ) {
515 T_WITH_ERRNO;
516 T_ASSERT_EQ(
517 SET_PROT_CLASS(g_dir_fd, new_prot_class),
518 0,
519 "Change directory to %s",
520 dp_class_num_to_string(new_prot_class)
521 );
522
523 T_WITH_ERRNO;
524 T_ASSERT_NE(
525 mkdir(g_subdirpath, 0x0777),
526 -1,
527 "Create subdirectory in %s directory",
528 dp_class_num_to_string(new_prot_class)
529 );
530
531 T_WITH_ERRNO;
532 T_ASSERT_NE(
533 g_subdir_fd = open(g_subdirpath, O_RDONLY | O_CLOEXEC),
534 -1,
535 "Should be able to open subdirectory in %s directory",
536 dp_class_num_to_string(new_prot_class)
537 );
538
539 T_ASSERT_NE(
540 local_result = GET_PROT_CLASS(g_subdir_fd),
541 -1,
542 "Get protection class of new subdirectory "
543 "of %s directory",
544 dp_class_num_to_string(new_prot_class)
545 );
546
547 T_ASSERT_EQ(
548 local_result,
549 new_prot_class,
550 "New subdirectory should have same class as %s parent",
551 dp_class_num_to_string(new_prot_class)
552 );
553
554 close(g_subdir_fd);
555 rmdir(g_subdirpath);
556 }
557 }
558
559 void
setup(void)560 setup(void)
561 {
562 int ret = 0;
563 int local_result = -1;
564
565 T_SETUPBEGIN;
566
567 T_ATEND(cleanup);
568
569 T_WITH_ERRNO;
570 T_ASSERT_NOTNULL(
571 mkdtemp(g_test_tempdir),
572 "Create temporary directory for test"
573 );
574 T_LOG("Test temp dir: %s", g_test_tempdir);
575
576 T_ASSERT_NE(
577 local_result = supports_content_prot(),
578 -1,
579 "Get content protection support status"
580 );
581
582 if (local_result == 0) {
583 T_SKIP("Data protection not supported on this system");
584 }
585
586 T_ASSERT_EQ(
587 has_passcode(),
588 0,
589 "Device should not have existing passcode"
590 );
591
592 T_ASSERT_EQ(
593 set_passcode(TEST_PASSCODE, NULL),
594 0,
595 "Set test passcode"
596 );
597
598 bzero(g_filepath, PATH_MAX);
599 bzero(g_dirpath, PATH_MAX);
600 bzero(g_subdirpath, PATH_MAX);
601
602 ret |= (strlcat(g_filepath, g_test_tempdir, PATH_MAX) == PATH_MAX);
603 ret |= (strlcat(g_filepath, "/", PATH_MAX) == PATH_MAX);
604 ret |= (strlcpy(g_dirpath, g_filepath, PATH_MAX) == PATH_MAX);
605 ret |= (strlcat(g_filepath, "test_file", PATH_MAX) == PATH_MAX);
606 ret |= (strlcat(g_dirpath, "test_dir/", PATH_MAX) == PATH_MAX);
607 ret |= (strlcpy(g_subdirpath, g_dirpath, PATH_MAX) == PATH_MAX);
608 ret |= (strlcat(g_subdirpath, "test_subdir/", PATH_MAX) == PATH_MAX);
609
610 T_QUIET;
611 T_ASSERT_EQ(ret, 0, "Initialize test path strings");
612
613 T_WITH_ERRNO;
614 T_ASSERT_GE(
615 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777),
616 0,
617 "Create test file"
618 );
619
620 T_SETUPEND;
621 }
622
623 void
cleanup(void)624 cleanup(void)
625 {
626 T_LOG("Cleaning up…");
627
628 if (g_subdir_fd >= 0) {
629 T_LOG("Cleanup: closing fd %d", g_subdir_fd);
630 close(g_subdir_fd);
631 }
632
633 if (g_subdirpath[0]) {
634 T_LOG("Cleanup: removing %s", g_subdirpath);
635 rmdir(g_subdirpath);
636 }
637
638 if (g_fd >= 0) {
639 T_LOG("Cleanup: closing fd %d", g_fd);
640 close(g_fd);
641 }
642
643 if (g_filepath[0]) {
644 T_LOG("Cleanup: removing %s", g_filepath);
645 unlink(g_filepath);
646 }
647
648 if (g_dir_fd >= 0) {
649 T_LOG("Cleanup: closing fd %d", g_dir_fd);
650 close(g_dir_fd);
651 }
652
653 if (g_dirpath[0]) {
654 T_LOG("Cleanup: removing %s", g_dirpath);
655 rmdir(g_dirpath);
656 }
657
658 if (strcmp(g_test_tempdir, TEMP_DIR_TEMPLATE)) {
659 T_LOG("Cleanup: removing %s", g_test_tempdir);
660 rmdir(g_test_tempdir);
661 }
662
663 if (g_passcode_set) {
664 T_LOG("Cleanup: unlocking device");
665 if (unlock_device(TEST_PASSCODE)) {
666 T_LOG("Warning: failed to unlock device in cleanup");
667 }
668
669 T_LOG("Cleanup: clearing passcode");
670 if (clear_passcode(TEST_PASSCODE)) {
671 T_LOG("Warning: failed to clear passcode in cleanup");
672 }
673 }
674 }
675
676 int
set_passcode(char * new_passcode,char * old_passcode)677 set_passcode(char * new_passcode, char * old_passcode)
678 {
679 int result = -1;
680
681 #ifdef KEYBAG_ENTITLEMENTS
682 /* If we're entitled, we can set the passcode ourselves. */
683 uint64_t inputs[] = {device_keybag_handle};
684 uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
685 void * input_structs = NULL;
686 size_t input_struct_count = 0;
687 char buffer[CPT_AKS_BUF_SIZE];
688 char * buffer_ptr = buffer;
689 uint32_t old_passcode_len = 0;
690 uint32_t new_passcode_len = 0;
691
692 T_LOG("%s(): using keybag entitlements", __func__);
693
694 old_passcode_len = strnlen(old_passcode, CPT_MAX_PASS_LEN);
695 new_passcode_len = strnlen(new_passcode, CPT_MAX_PASS_LEN);
696
697 if ((old_passcode == NULL) || (old_passcode_len == CPT_MAX_PASS_LEN)) {
698 old_passcode = "";
699 old_passcode_len = 0;
700 }
701 if ((new_passcode == NULL) || (new_passcode_len == CPT_MAX_PASS_LEN)) {
702 new_passcode = "";
703 new_passcode_len = 0;
704 }
705
706 *((uint32_t *) buffer_ptr) = ((uint32_t) 2);
707 buffer_ptr += sizeof(uint32_t);
708
709 *((uint32_t *) buffer_ptr) = old_passcode_len;
710 buffer_ptr += sizeof(uint32_t);
711
712 memcpy(buffer_ptr, old_passcode, old_passcode_len);
713 buffer_ptr += ((old_passcode_len + sizeof(uint32_t) - 1) &
714 ~(sizeof(uint32_t) - 1));
715
716 *((uint32_t *) buffer_ptr) = new_passcode_len;
717 buffer_ptr += sizeof(uint32_t);
718
719 memcpy(buffer_ptr, new_passcode, new_passcode_len);
720 buffer_ptr += ((new_passcode_len + sizeof(uint32_t) - 1) &
721 ~(sizeof(uint32_t) - 1));
722
723 input_structs = buffer;
724 input_struct_count = (buffer_ptr - buffer);
725
726 result = apple_key_store(
727 kAppleKeyStoreKeyBagSetPasscode,
728 inputs,
729 input_count,
730 input_structs,
731 input_struct_count,
732 NULL,
733 NULL
734 );
735 #else
736 /*
737 * If we aren't entitled, we'll need to use
738 * keystorectl to set the passcode.
739 */
740 T_LOG("%s(): using keystorectl", __func__);
741
742 if (
743 (old_passcode == NULL) ||
744 (strnlen(old_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
745 ) {
746 old_passcode = "";
747 }
748
749 if (
750 (new_passcode == NULL) ||
751 (strnlen(new_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
752 ) {
753 new_passcode = "";
754 }
755
756 char * const keystorectl_args[] = {
757 KEYBAGDTEST_PATH,
758 "syspass",
759 old_passcode,
760 new_passcode,
761 NULL
762 };
763 result = spawn_proc(keystorectl_args);
764 #endif /* KEYBAG_ENTITLEMENTS */
765 if (result == 0 && new_passcode != NULL) {
766 g_passcode_set = 1;
767 } else if (result == 0 && new_passcode == NULL) {
768 g_passcode_set = 0;
769 }
770
771 return result;
772 }
773
774 int
clear_passcode(char * passcode)775 clear_passcode(char * passcode)
776 {
777 /*
778 * For the moment, this will set the passcode to the empty string
779 * (a known value); this will most likely need to change, or running
780 * this test may ruin everything™
781 */
782 return set_passcode(NULL, passcode);
783 }
784
785 int
has_passcode(void)786 has_passcode(void)
787 {
788 return set_passcode(NULL, NULL);
789 }
790
791 int
lock_device(void)792 lock_device(void)
793 {
794 int result = -1;
795
796 /*
797 * Pass in the path to keybagdTest instead. By doing this, we bypass
798 * the shortcut to get in to the keybag via IOKit and instead use the
799 * pre-existing command line tool.
800 *
801 * This also goes through the normal "lock → locking (10s) → locked"
802 * flow that would normally occuring during system runtime when the
803 * lock button is depressed. To ensure that our single threaded test
804 * works properly in this case, poll until we can't create a class A
805 * file to be safe.
806 */
807 char * const kbd_args[] = {KEYBAGDTEST_PATH, "lock", NULL};
808 result = spawn_proc(kbd_args);
809 if (result) {
810 return result;
811 }
812
813 /*
814 * Delete the file if it is present. Note that this may fail if the
815 * file is actually not there. So don't bomb out if we can't delete
816 * this file right now.
817 */
818 (void) unlink("/private/var/foo_test_file");
819
820 while (1) {
821 int dp_fd;
822
823 dp_fd = open_dprotected_np(
824 "/private/var/foo_test_file",
825 O_RDWR | O_CREAT,
826 PROTECTION_CLASS_A,
827 0
828 );
829
830 if (dp_fd >= 0) {
831 /* delete it and sleep */
832 close(dp_fd);
833 result = unlink("/private/var/foo_test_file");
834
835 if (result) {
836 return result;
837 }
838
839 sync();
840 sleep(1);
841 } else {
842 /* drop out of our polling loop. */
843 break;
844 }
845 }
846
847 /*
848 * Note that our loop breakout condition is whether or not we can
849 * create a class A file, so that loop may execute up to 10 times
850 * (due to the 10s grace period). By the time we get here, we assume
851 * that we didn't hit any of the error cases above.
852 */
853
854 return 0;
855 }
856
857 int
unlock_device(char * passcode)858 unlock_device(char * passcode)
859 {
860 int result = -1;
861
862 #ifdef KEYBAG_ENTITLEMENTS
863 /* If we're entitled, we can unlock the device ourselves. */
864 uint64_t inputs[] = {device_keybag_handle};
865 uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
866 size_t input_struct_count = 0;
867
868 T_LOG("%s(): using keybag entitlements", __func__);
869
870 input_struct_count = strnlen(passcode, CPT_MAX_PASS_LEN);
871 if ((passcode == NULL) || (input_struct_count == CPT_MAX_PASS_LEN)) {
872 passcode = "";
873 input_struct_count = 0;
874 }
875
876 result = apple_key_store(
877 kAppleKeyStoreKeyBagUnlock,
878 inputs,
879 input_count,
880 passcode,
881 input_struct_count,
882 NULL,
883 NULL
884 );
885 #else
886 /*
887 * If we aren't entitled, we'll need to use
888 * keystorectl to unlock the device.
889 */
890 T_LOG("%s(): using keystorectl", __func__);
891
892 if (
893 (passcode == NULL) ||
894 (strnlen(passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
895 ) {
896 passcode = "";
897 }
898
899 char * const keystorectl_args[] = {
900 KEYSTORECTL_PATH, "unlock", passcode, NULL
901 };
902
903 result = spawn_proc(keystorectl_args);
904 #endif /* KEYBAG_ENTITLEMENTS */
905
906 return result;
907 }
908
909 /*
910 * Code based on Mobile Key Bag; specifically
911 * MKBDeviceSupportsContentProtection and
912 * MKBDeviceFormattedForContentProtection.
913 *
914 * We want to verify that we support content protection, and that
915 * we are formatted for it.
916 */
917 int
supports_content_prot(void)918 supports_content_prot(void)
919 {
920 int local_result = -1;
921 int result = -1;
922 uint32_t buffer_size = 1;
923 char buffer[buffer_size];
924 io_registry_entry_t defaults = IO_OBJECT_NULL;
925 kern_return_t k_result = KERN_FAILURE;
926 struct statfs statfs_results;
927
928 defaults = IORegistryEntryFromPath(
929 kIOMasterPortDefault,
930 kIODeviceTreePlane ":/defaults"
931 );
932
933 if (defaults == IO_OBJECT_NULL) {
934 /* Assume data protection is unsupported */
935 T_LOG(
936 "%s(): no defaults entry in IORegistry",
937 __func__
938 );
939 return 0;
940 }
941
942 k_result = IORegistryEntryGetProperty(
943 defaults,
944 "content-protect",
945 buffer,
946 &buffer_size
947 );
948
949 if (k_result != KERN_SUCCESS) {
950 /* Assume data protection is unsupported */
951 T_LOG(
952 "%s(): no content-protect property in IORegistry",
953 __func__
954 );
955 return 0;
956 }
957
958 /*
959 * At this point, we SUPPORT content protection… but are we
960 * formatted for it? This is ugly; we should be testing the file
961 * system we'll be testing in, not just /tmp/.
962 */
963 local_result = statfs(g_test_tempdir, &statfs_results);
964
965 if (local_result == -1) {
966 T_LOG(
967 "%s(): failed to statfs the test directory, errno = %s",
968 __func__, strerror(errno)
969 );
970 return -1;
971 } else if (statfs_results.f_flags & MNT_CPROTECT) {
972 return 1;
973 } else {
974 T_LOG(
975 "%s(): filesystem not formatted for data protection",
976 __func__
977 );
978 return 0;
979 }
980 }
981
982 /*
983 * Shamelessly ripped from keystorectl routines;
984 * a wrapper for invoking the AKS user client.
985 */
986 int
apple_key_store(uint32_t command,uint64_t * inputs,uint32_t input_count,void * input_structs,size_t input_struct_count,uint64_t * outputs,uint32_t * output_count)987 apple_key_store(uint32_t command,
988 uint64_t * inputs,
989 uint32_t input_count,
990 void * input_structs,
991 size_t input_struct_count,
992 uint64_t * outputs,
993 uint32_t * output_count)
994 {
995 int result = -1;
996 io_connect_t connection = IO_OBJECT_NULL;
997 io_registry_entry_t apple_key_bag_service = IO_OBJECT_NULL;
998 kern_return_t k_result = KERN_FAILURE;
999 IOReturn io_result = IO_OBJECT_NULL;
1000
1001 apple_key_bag_service = IOServiceGetMatchingService(
1002 kIOMasterPortDefault,
1003 IOServiceMatching(kAppleKeyStoreServiceName)
1004 );
1005 if (apple_key_bag_service == IO_OBJECT_NULL) {
1006 T_LOG(
1007 "%s: failed to match kAppleKeyStoreServiceName",
1008 __func__
1009 );
1010 goto end;
1011 }
1012
1013 k_result = IOServiceOpen(
1014 apple_key_bag_service,
1015 mach_task_self(),
1016 0,
1017 &connection
1018 );
1019 if (k_result != KERN_SUCCESS) {
1020 T_LOG(
1021 "%s: failed to open AppleKeyStore: "
1022 "IOServiceOpen() returned %d",
1023 __func__, k_result
1024 );
1025 goto end;
1026 }
1027
1028 k_result = IOConnectCallMethod(
1029 connection,
1030 kAppleKeyStoreUserClientOpen,
1031 NULL, 0, NULL, 0, NULL, NULL, NULL, NULL
1032 );
1033 if (k_result != KERN_SUCCESS) {
1034 T_LOG(
1035 "%s: call to AppleKeyStore method "
1036 "kAppleKeyStoreUserClientOpen failed",
1037 __func__
1038 );
1039 goto close;
1040 }
1041
1042 io_result = IOConnectCallMethod(
1043 connection, command, inputs, input_count, input_structs,
1044 input_struct_count, outputs, output_count, NULL, NULL
1045 );
1046 if (io_result != kIOReturnSuccess) {
1047 T_LOG("%s: call to AppleKeyStore method %d failed", __func__, command);
1048 goto close;
1049 }
1050
1051 result = 0;
1052
1053 close:
1054 IOServiceClose(apple_key_bag_service);
1055 end:
1056 return result;
1057 }
1058
1059 /*
1060 * Helper function for launching tools
1061 */
1062 int
spawn_proc(char * const command[])1063 spawn_proc(char * const command[])
1064 {
1065 pid_t pid = 0;
1066 int launch_tool_ret = 0;
1067 bool waitpid_ret = true;
1068 int status = 0;
1069 int signal = 0;
1070 int timeout = 30;
1071
1072 launch_tool_ret = dt_launch_tool(&pid, command, false, NULL, NULL);
1073 T_EXPECT_EQ(launch_tool_ret, 0, "launch tool: %s", command[0]);
1074 if (launch_tool_ret != 0) {
1075 return 1;
1076 }
1077
1078 waitpid_ret = dt_waitpid(pid, &status, &signal, timeout);
1079 T_EXPECT_TRUE(waitpid_ret, "%s should succeed", command[0]);
1080 if (waitpid_ret == false) {
1081 if (status != 0) {
1082 T_LOG("%s exited %d", command[0], status);
1083 }
1084 if (signal != 0) {
1085 T_LOG("%s received signal %d", command[0], signal);
1086 }
1087 return 1;
1088 }
1089
1090 return 0;
1091 }
1092
1093 char*
dp_class_num_to_string(int num)1094 dp_class_num_to_string(int num)
1095 {
1096 switch (num) {
1097 case 0:
1098 return "unclassed";
1099 case PROTECTION_CLASS_A:
1100 return "class A";
1101 case PROTECTION_CLASS_B:
1102 return "class B";
1103 case PROTECTION_CLASS_C:
1104 return "class C";
1105 case PROTECTION_CLASS_D:
1106 return "class D";
1107 case PROTECTION_CLASS_E:
1108 return "class E";
1109 case PROTECTION_CLASS_F:
1110 return "class F";
1111 default:
1112 return "<unknown class>";
1113 }
1114 }
1115
1116 #if 0
1117 int
1118 device_lock_state(void)
1119 {
1120 /*
1121 * TODO: Actually implement this.
1122 *
1123 * We fail if a passcode already exists, and the methods being used
1124 * to lock/unlock the device in this test appear to be synchronous…
1125 * do we need this function?
1126 */
1127 int result = -1;
1128
1129 return result;
1130 }
1131
1132 /* Determines if we will try to test class C semanatics. */
1133 int
1134 unlocked_since_boot()
1135 {
1136 /*
1137 * TODO: Actually implement this.
1138 *
1139 * The actual semantics for CP mean that even with this primative,
1140 * we would need to set a passcode and then reboot the device in
1141 * order to test this; this function will probably be rather
1142 * worthless as a result.
1143 */
1144 int result = 1;
1145
1146 return result;
1147 }
1148 #endif
1149