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