1 #include <TargetConditionals.h>
11 #include <sys/types.h>
12 #include <sys/sysctl.h>
14 #include <sys/xattr.h>
15 #include <sys/mount.h>
16 #include <sys/param.h>
17 #include <CommonCrypto/CommonDigest.h>
18 #include <libkern/OSAtomic.h>
21 #include <MobileKeyBag/MobileKeyBag.h>
22 #include <hfs/hfs_fsctl.h>
24 #include "hfs-tests.h"
25 #include "test-utils.h"
28 TEST(key_roll
, .run_as_root
= true)
32 #define MB * 1024 * 1024
36 #define KEY_ROLL_TEST_FILE "/tmp/key-roll-test.data"
37 #define KEY_ROLL_TEST_FILE_2 "/tmp/key-roll-test-2.data"
38 #define KEY_ROLL_FILL_DISK_FILE "/tmp/key-roll-fill-disk"
39 #define KEY_ROLL_TEST_DIR "/tmp/key-roll-test.dir"
40 #define KEY_ROLL_SYM_LINK "/tmp/key-roll-test.symlink"
41 #define KEYSTORECTL "/usr/local/bin/keystorectl"
42 #define KEYBAGDTEST "/usr/local/bin/keybagdTest"
44 int cmp_zero(const void *p
, size_t len
)
48 while (len
&& (uintptr_t)x
& 7) {
55 const uint64_t *y
= (uint64_t *)x
;
76 uint8_t digest
[CC_MD5_DIGEST_LENGTH
];
81 static const int append_test_amount
= 128 MB
;
84 static const int append_test_amount
= 1 MB
;
87 void *append_to_file(void *param
)
89 struct append_ctx
*ctx
= param
;
92 CC_MD5_Init(&md5_ctx
);
96 void *p
= mmap(NULL
, append_test_amount
, PROT_READ
| PROT_WRITE
,
97 MAP_SHARED
, ctx
->fd
, 0);
98 assert(p
!= MAP_FAILED
);
100 int page_size
= getpagesize();
102 while (total
< append_test_amount
) {
103 size_t todo
= random() % (1 MB
) + 1;
105 if (todo
> append_test_amount
- total
)
106 todo
= append_test_amount
- total
;
108 check_io(write(ctx
->fd
, buf1
, todo
), todo
);
110 int round
= ((uintptr_t)p
+ total
) % page_size
;
112 assert_no_err(msync(p
+ total
- round
, todo
+ round
,
113 MS_ASYNC
| MS_INVALIDATE
));
115 CC_MD5_Update(&md5_ctx
, buf1
, todo
);
120 CC_MD5_Final(ctx
->digest
, &md5_ctx
);
126 assert_no_err(munmap(p
, append_test_amount
));
131 static uint32_t os_version(void)
133 static uint32_t os_version
;
137 char os_version_str
[128];
138 size_t size
= sizeof(os_version_str
);
139 assert_no_err(sysctlbyname("kern.osversion", os_version_str
,
142 const char *p
= os_version_str
;
145 while (*p
>= '0' && *p
<= '9') {
146 a
= a
* 10 + *p
- '0';
158 while (*p
>= '0' && *p
<= '9') {
159 c
= c
* 10 + *p
- '0';
166 os_version
= (a
& 0xff) << 24 | b
<< 16 | (c
& 0xffff);
171 static int block_size(void)
173 static int block_size
;
178 assert_no_err(statfs("/private/var", &sfs
));
180 block_size
= sfs
.f_bsize
;
186 static void fill_disk(int *fd
, uint64_t *size
)
188 assert_with_errno((*fd
= open(KEY_ROLL_FILL_DISK_FILE
,
189 O_CREAT
| O_RDWR
| O_TRUNC
, 0666)) >= 0);
191 // Fill up the disk so there's no remaining disk space
193 assert_no_err(fstatfs(*fd
, &sfs
));
195 uint32_t blocks
= sfs
.f_bfree
;
198 uint64_t size
= (uint64_t)blocks
* sfs
.f_bsize
;
200 if (!fcntl(*fd
, F_SETSIZE
, &size
))
203 assert_with_errno(errno
== ENOSPC
);
208 // Now increase the size until we hit no space
209 uint32_t upper
= sfs
.f_bfree
+ 128;
212 uint32_t try = (upper
+ blocks
) / 2;
217 uint64_t size
= (uint64_t)try * sfs
.f_bsize
;
218 if (!fcntl(*fd
, F_SETSIZE
, &size
)) {
221 assert_no_err(fstatfs(*fd
, &sfs
));
222 upper
= try + sfs
.f_bfree
+ 128;
225 assert(errno
== ENOSPC
);
227 if (try == blocks
+ 1)
234 *size
= (uint64_t)blocks
* sfs
.f_bsize
;
237 volatile int32_t threads_running
;
239 static void *roll_thread(void *arg
__attribute__((unused
)))
243 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
, O_RDWR
)) >= 0);
245 hfs_key_roll_args_t args
= {
246 .api_version
= HFS_KR_API_LATEST_VERSION
,
247 .operation
= HFS_KR_OP_STEP
,
250 for (int i
= 0; i
< 100; ++i
) {
251 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
254 args
.operation
= HFS_KR_OP_START
;
256 args
.operation
= HFS_KR_OP_STEP
;
259 assert_no_err(close(fd
));
261 OSAtomicDecrement32(&threads_running
);
266 int run_key_roll(__unused test_ctx_t
*ctx
)
268 // The root file system needs to be HFS
271 assert(statfs("/tmp", &sfs
) == 0);
272 if (strcmp(sfs
.f_fstypename
, "hfs")) {
273 printf("key_roll needs hfs as root file system - skipping.\n");
278 void *read_buf
= malloc(2 MB
);
281 struct attrlist attrlist
= {
282 .bitmapcount
= ATTR_BIT_MAP_COUNT
,
283 .commonattr
= ATTR_CMN_DATA_PROTECT_FLAGS
,
291 // Clean up previous invocation--we don't care about failures here
292 unlink(KEY_ROLL_TEST_FILE_2
);
293 unlink(KEY_ROLL_FILL_DISK_FILE
);
294 unlink(KEY_ROLL_TEST_FILE
);
295 systemx("/bin/rm", "-rf", KEY_ROLL_TEST_DIR
, NULL
);
298 memset(buf1
, 0x25, 1 MB
);
300 void *buf2
= malloc(1 MB
);
301 memset(buf2
, 0x49, 1 MB
);
303 // First, force change to new xattr version
305 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
,
306 O_CREAT
| O_RDWR
| O_TRUNC
, 0666)) >= 0);
309 check_io(write(fd
, buf1
, 1 MB
), 1 MB
);
310 check_io(write(fd
, buf1
, 1 MB
), 1 MB
);
311 check_io(write(fd
, buf1
, 1 MB
), 1 MB
);
313 hfs_key_roll_args_t args
= {
314 .api_version
= HFS_KR_API_LATEST_VERSION
,
315 .operation
= HFS_KR_OP_START
,
317 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
319 args
.operation
= HFS_KR_OP_STEP
;
320 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
322 assert_no_err(unlink(KEY_ROLL_TEST_FILE
));
323 assert_no_err(close(fd
));
326 * That should have switch the device to new xattr version. Continue
327 * with more tests now...
330 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
,
331 O_CREAT
| O_RDWR
| O_TRUNC
, 0666)) >= 0);
334 check_io(write(fd
, buf1
, 1 MB
), 1 MB
);
335 check_io(write(fd
, buf1
, 1 MB
), 1 MB
);
337 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
339 assert(args
.key_revision
== 1
340 && args
.key_os_version
== os_version()
342 && args
.total
== 2 MB
);
344 // Extend file to 3 MB
345 assert_no_err(ftruncate(fd
, 3 MB
));
348 args
.operation
= HFS_KR_OP_START
;
349 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
351 assert((args
.key_revision
& 0xff00) == 0x0100 && args
.done
== 0
352 && args
.total
== 3 MB
);
355 args
.operation
= HFS_KR_OP_STEP
;
356 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
358 assert(args
.done
== 2 MB
);
360 // Write a little way into the last MB
361 off_t offset
= 2 MB
+ 50000;
363 check_io(pwrite(fd
, buf2
, 1024, offset
), 1024);
365 // Roll to end of file
366 args
.operation
= HFS_KR_OP_STEP
;
367 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
369 // This time, it should have finished
370 assert(args
.done
== -1);
372 // Now check all is as we expect
373 assert_with_errno((p
= mmap(NULL
, 3 MB
, PROT_READ
| PROT_WRITE
,
374 MAP_SHARED
, fd
, 0)) != MAP_FAILED
);
376 // Force flush of cache
377 assert_no_err(msync(p
, 3 MB
, MS_INVALIDATE
));
379 assert(!memcmp(p
, buf1
, 1 MB
));
380 assert(!memcmp(p
+ 1 MB
, buf1
, 1 MB
));
381 assert(!cmp_zero(p
+ 2 MB
, 50000));
382 assert(!memcmp(p
+ offset
, buf2
, 1024));
383 assert(!cmp_zero(p
+ offset
+ 1024, 1 MB
- 50000 - 1024));
385 // -- Rewrapping tests --
387 assert_no_err(fgetattrlist(fd
, &attrlist
, &attrs
, sizeof(attrs
), 0));
389 // File should be class D
390 assert((attrs
.dp_flags
& 0x1f) == 4);
393 args
.operation
= HFS_KR_OP_START
;
394 args
.flags
= HFS_KR_MATCH_KEY_REVISION
;
396 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
398 // Reset flags for later tests
401 assert(args
.done
== 0 && (args
.key_revision
& 0xff00) == 0x0200);
404 args
.operation
= HFS_KR_OP_STEP
;
405 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
407 assert(args
.done
== 2 MB
);
409 // Change file to class C
410 assert_no_err(fcntl(fd
, F_SETPROTECTIONCLASS
, 3));
412 assert_no_err(fgetattrlist(fd
, &attrlist
, &attrs
, sizeof(attrs
), 0));
413 assert((attrs
.dp_flags
& 0x1f) == 3);
415 // Force file to be recycled (won't work on release builds)
416 bool release_build
= false;
418 if (fcntl(fd
, F_RECYCLE
)) {
419 assert_equal_int(errno
, ENOTTY
);
420 release_build
= true;
423 // Release refs so recycle happens
424 assert_no_err(close(fd
));
425 assert_no_err(munmap(p
, 3 MB
));
427 // Now check the file
428 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
, O_RDWR
)) >= 0);
429 assert_with_errno((p
= mmap(NULL
, 3 MB
, PROT_READ
| PROT_WRITE
,
430 MAP_SHARED
, fd
, 0)) != MAP_FAILED
);
432 args
.operation
= HFS_KR_OP_STATUS
;
433 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
435 assert(args
.done
== 2 MB
);
438 assert(!memcmp(p
, buf1
, 1 MB
));
439 assert(!memcmp(p
+ 1 MB
, buf1
, 1 MB
));
440 assert(!cmp_zero(p
+ 2 MB
, 50000));
441 assert(!memcmp(p
+ offset
, buf2
, 1024));
442 assert(!cmp_zero(p
+ offset
+ 1024, 1 MB
- 50000 - 1024));
444 // Check the class again
445 assert_no_err(fgetattrlist(fd
, &attrlist
, &attrs
, sizeof(attrs
), 0));
446 assert((attrs
.dp_flags
& 0x1f) == 3);
449 assert_no_err(fcntl(fd
, F_SETPROTECTIONCLASS
, 1));
452 assert_no_err(fgetattrlist(fd
, &attrlist
, &attrs
, sizeof(attrs
), 0));
453 assert((attrs
.dp_flags
& 0x1f) == 1);
455 assert_with_errno(release_build
|| !fcntl(fd
, F_RECYCLE
));
457 // Should get recycled after this
458 assert_no_err(close(fd
));
459 assert_no_err(munmap(p
, 3 MB
));
461 int fd2
= open(KEY_ROLL_TEST_FILE_2
, O_RDWR
| O_CREAT
, 0666);
464 * We can't check this file until we've triggered an unlock
465 * which means we need to set a system password.
468 // Change system password
469 assert_no_err(systemx(KEYSTORECTL
, "change-password", "", "1234", NULL
));
471 // Now we can check the file
473 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
, O_RDWR
)) >= 0);
474 assert_with_errno((p
= mmap(NULL
, 3 MB
, PROT_READ
| PROT_WRITE
,
475 MAP_SHARED
, fd
, 0)) != MAP_FAILED
);
477 assert(!memcmp(p
, buf1
, 1 MB
));
479 // Open for raw access
481 assert_with_errno((raw_fd
= open_dprotected_np(KEY_ROLL_TEST_FILE
,
482 O_RDONLY
, 0, 1, 0)) >= 0);
485 assert_no_err(systemx(KEYBAGDTEST
, "lock", NULL
));
487 // Wait until the device is locked
488 while (MKBGetDeviceLockState(NULL
) != kMobileKeyBagDeviceIsLocked
)
491 // Set second file to class B
492 assert_no_err(fcntl(fd2
, F_SETPROTECTIONCLASS
, 2));
494 // Make sure we can write to it
495 check_io(write(fd2
, buf1
, 1 MB
), 1 MB
);
497 assert_no_err(close(fd2
));
498 assert_no_err(unlink(KEY_ROLL_TEST_FILE_2
));
501 assert(read(fd
, read_buf
, 1 MB
) == -1 && errno
== EPERM
);
503 // Now try and continue rolling
505 args
.operation
= HFS_KR_OP_STEP
;
506 assert(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0) == -1
509 // Make sure we can get the status of the file
510 args
.operation
= HFS_KR_OP_STATUS
;
511 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
513 // Make sure reading the raw file fails
514 assert(read(raw_fd
, read_buf
, 1 MB
) == -1 && errno
== EPERM
);
516 // Make sure opening the file in raw mode fails
517 assert(open_dprotected_np(KEY_ROLL_TEST_FILE
, O_RDONLY
, 0, 1, 0)
518 == -1 && errno
== EPERM
);
520 assert_no_err(systemx(KEYSTORECTL
, "unlock", "1234", NULL
));
522 // Now check the raw read works
523 check_io(read(raw_fd
, read_buf
, 1 MB
), 1 MB
);
525 assert_no_err(close(raw_fd
));
529 assert(!memcmp(p
, buf1
, 1 MB
));
531 // Change system password back
533 assert_no_err(systemx(KEYSTORECTL
, "change-password", "1234", "", NULL
));
535 // -- Raw mode tests --
537 // Open the file in raw mode
538 assert_with_errno((raw_fd
= open_dprotected_np(KEY_ROLL_TEST_FILE
,
539 O_RDONLY
, 0, 1, 0)) >= 0);
541 // Key rolling should be unchanged
542 args
.operation
= HFS_KR_OP_STATUS
;
543 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
544 assert(args
.done
== 2 MB
&& (args
.key_revision
& 0xff00) == 0x0200);
547 check_io(read(raw_fd
, read_buf
, 2 MB
), 2 MB
);
549 // Key rolling status should be remain unchanged
550 args
.operation
= HFS_KR_OP_STATUS
;
551 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
552 assert(args
.done
== 2 MB
&& (args
.key_revision
& 0xff00) == 0x0200);
555 check_io(read(raw_fd
, read_buf
, 2 MB
), 1 MB
);
557 // Key rolling should have been finished
559 args
.operation
= HFS_KR_OP_STATUS
;
560 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
561 assert(args
.done
== -1 && (args
.key_revision
& 0xff00) == 0x0200);
563 assert_no_err(close(raw_fd
));
565 // Change the revision and os version
567 // HFS_KR_OP_SET_INFO isn't supported on release build
569 // Enable auto rolling
570 hfs_key_auto_roll_args_t auto_roll_args
= {
571 .api_version
= HFS_KEY_AUTO_ROLL_API_LATEST_VERSION
,
572 .max_key_os_version
= os_version() + 1,
575 assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL
, &auto_roll_args
, 0));
577 args
.operation
= HFS_KR_OP_SET_INFO
;
578 args
.key_revision
= 0x0200;
579 args
.key_os_version
= CP_OS_VERS_PRE_71
;
580 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
582 args
.operation
= HFS_KR_OP_STATUS
;
583 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
585 assert(args
.done
== -1
586 && args
.key_revision
== 0x0200
587 && args
.key_os_version
== CP_OS_VERS_PRE_71
);
589 // Enable auto rolling
590 hfs_key_auto_roll_args_t auto_roll_args
= {
591 .api_version
= HFS_KEY_AUTO_ROLL_API_LATEST_VERSION
,
592 .max_key_os_version
= CP_OS_VERS_71
,
595 assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL
, &auto_roll_args
, 0));
598 // Open the file in raw mode
599 assert_with_errno((raw_fd
= open_dprotected_np(KEY_ROLL_TEST_FILE
,
600 O_RDONLY
, 0, 1, 0)) >= 0);
602 // That should have initiated key rolling
603 args
.operation
= HFS_KR_OP_STATUS
;
604 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
605 assert(args
.done
== 0 && (args
.key_revision
& 0xff00) == 0x0300);
608 check_io(read(raw_fd
, read_buf
, 1 MB
), 1 MB
);
610 // That should have rolled 2 MB
611 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
612 assert(args
.done
== 2 MB
&& (args
.key_revision
& 0xff00) == 0x0300
613 && args
.key_os_version
== os_version());
616 // Check that reservation is working as expected
618 // First figure out where the last block finished
619 struct log2phys l2p
= {
620 .l2p_contigbytes
= 1024 * 1024,
621 .l2p_devoffset
= 2 MB
- block_size(),
624 assert_no_err(fcntl(fd
, F_LOG2PHYS_EXT
, &l2p
));
625 assert(l2p
.l2p_contigbytes
== block_size());
627 // Now try and extend the file by a block
629 .fst_flags
= F_ALLOCATECONTIG
| F_ALLOCATEALL
,
630 .fst_posmode
= F_VOLPOSMODE
,
631 .fst_offset
= l2p
.l2p_devoffset
+ block_size(),
632 .fst_length
= 3 MB
+ block_size(),
635 assert_no_err(fcntl(fd
, F_PREALLOCATE
, &fstore
));
636 assert_equal_ll(fstore
.fst_bytesalloc
, block_size());
638 // Force it to be initialised
639 check_io(pwrite(fd
, buf1
, block_size(), 3 MB
), block_size());
641 // Now see where it was allocated
642 l2p
.l2p_devoffset
= 3 MB
;
643 l2p
.l2p_contigbytes
= 1 MB
;
644 assert_no_err(fcntl(fd
, F_LOG2PHYS_EXT
, &l2p
));
646 assert(l2p
.l2p_contigbytes
== block_size());
649 * It shouldn't be in the 1 MB spot that should be reserved
650 * for rolling the last bit.
652 if (l2p
.l2p_devoffset
== -1
653 || (l2p
.l2p_devoffset
+ block_size() > fstore
.fst_offset
654 && l2p
.l2p_devoffset
< fstore
.fst_offset
+ 1 MB
)) {
655 assert_fail("unexpected allocation (%lld, %lld)\n",
656 l2p
.l2p_devoffset
, fstore
.fst_offset
);
659 // Restore the file to its original length
660 assert_no_err(ftruncate(fd
, 3 MB
));
663 // Try and start rolling again, this should succeed and just return status
664 args
.operation
= HFS_KR_OP_START
;
665 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
667 assert(args
.done
== 2 MB
&& (args
.key_revision
& 0xff00) == 0x0300);
669 check_io(read(raw_fd
, read_buf
, 1 MB
), 1 MB
);
671 // There should be no change in the roll status
672 args
.operation
= HFS_KR_OP_STATUS
;
673 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
675 assert(args
.done
== 2 MB
);
678 check_io(read(raw_fd
, read_buf
, 2 MB
), 1 MB
);
680 // That should have finished key rolling
681 args
.operation
= HFS_KR_OP_STATUS
;
682 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
684 assert(args
.done
== -1);
686 // Trying to initiate rolling should fail because we have it open for
688 args
.operation
= HFS_KR_OP_START
;
689 assert(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0) == -1
692 assert_no_err(close(raw_fd
));
695 * Make sure we can open directories raw whilst auto-rolling is
696 * enabled. We've picked a directory here that's class C.
698 assert_with_errno((raw_fd
=
699 open_dprotected_np("/private/var/mobile/Library/Passes",
700 O_RDONLY
| O_NOFOLLOW
,
703 assert_no_err(close(raw_fd
));
705 if (!release_build
) {
707 * This test only works on debug builds because for release
708 * builds we had to set things up so that it always rolls the
712 // Open the file again for raw access
713 assert_with_errno((raw_fd
= open_dprotected_np(KEY_ROLL_TEST_FILE
,
714 O_RDONLY
, 0, 1, 0)) >= 0);
716 // Status should remain unchanged
717 args
.operation
= HFS_KR_OP_STATUS
;
718 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
720 assert(args
.done
== -1 && (args
.key_revision
& 0xff00) == 0x0300
721 && args
.key_os_version
== os_version());
723 assert_no_err(close(raw_fd
));
726 // Tidy up auto rolling
727 hfs_key_auto_roll_args_t auto_roll_args
= {
728 .api_version
= HFS_KEY_AUTO_ROLL_API_LATEST_VERSION
,
731 assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL
, &auto_roll_args
, 0));
733 // Now we should be able to start
734 args
.operation
= HFS_KR_OP_START
;
735 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
737 assert(args
.done
== 0 && (args
.key_revision
& 0xff00) == 0x0400);
740 args
.operation
= HFS_KR_OP_STEP
;
741 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
743 assert(args
.done
== 2 MB
);
746 assert_no_err(ftruncate(fd
, 1 MB
));
748 // Key rolling should have finished now
749 args
.operation
= HFS_KR_OP_STATUS
;
750 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
752 assert(args
.done
== -1);
754 assert_no_err(ftruncate(fd
, 3 MB
));
756 // Start rolling again
757 args
.operation
= HFS_KR_OP_START
;
758 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
760 assert(args
.done
== 0 && (args
.key_revision
& 0xff00) == 0x0500);
763 args
.operation
= HFS_KR_OP_STEP
;
764 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
766 assert(args
.done
== 2 MB
);
769 assert_no_err(unlink(KEY_ROLL_TEST_FILE
));
771 // File should be open unlinked now
772 args
.operation
= HFS_KR_OP_STATUS
;
773 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
775 assert(args
.done
== 2 MB
);
778 args
.operation
= HFS_KR_OP_STEP
;
779 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
781 assert(args
.done
== -1);
783 assert_no_err(close(fd
));
786 assert(!memcmp(p
, buf1
, 1 MB
));
787 assert(!cmp_zero(p
+ 1 MB
, 2 MB
));
789 assert_no_err(munmap(p
, 3 MB
));
791 // -- Resource fork --
793 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
,
794 O_CREAT
| O_RDWR
, 0666)) >= 0);
796 for (int i
= 0; i
< 3; ++i
) {
797 check_io(write(fd
, buf1
, 1 MB
), 1 MB
);
800 for (int i
= 0; i
< 3; ++i
) {
801 assert_no_err(fsetxattr(fd
, XATTR_RESOURCEFORK_NAME
, buf1
,
805 args
.operation
= HFS_KR_OP_START
;
806 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
808 assert(args
.done
== 0 && args
.total
== 6 MB
);
810 args
.operation
= HFS_KR_OP_STEP
;
811 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
813 assert(args
.done
== 2 MB
);
815 args
.operation
= HFS_KR_OP_STEP
;
816 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
818 assert(args
.done
== 3 MB
);
820 // Should have switched to resource fork
822 args
.operation
= HFS_KR_OP_STEP
;
823 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
825 assert(args
.done
== 5 MB
);
827 args
.operation
= HFS_KR_OP_STEP
;
828 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
830 assert(args
.done
== -1);
834 for (int i
= 0; i
< 3; ++i
) {
835 check_io(fgetxattr(fd
, XATTR_RESOURCEFORK_NAME
, read_buf
,
836 1 MB
, i MB
, 0), 1 MB
);
838 assert(!memcmp(buf1
, read_buf
, 1 MB
));
841 // Now try again, but this time truncate data fork
843 args
.operation
= HFS_KR_OP_START
;
844 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
846 assert(args
.done
== 0 && args
.total
== 6 MB
);
848 args
.operation
= HFS_KR_OP_STEP
;
849 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
851 assert(args
.done
== 2 MB
);
853 assert_no_err(ftruncate(fd
, 0));
855 // Should have switched to resource fork
857 args
.operation
= HFS_KR_OP_STATUS
;
858 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
860 assert(args
.done
== 0);
862 args
.operation
= HFS_KR_OP_STEP
;
863 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
865 assert(args
.done
== 2 MB
);
867 // Check the data whilst we're in the middle of rolling
869 for (int i
= 0; i
< 3; ++i
) {
870 check_io(fgetxattr(fd
, XATTR_RESOURCEFORK_NAME
, read_buf
,
871 1 MB
, i MB
, 0), 1 MB
);
873 assert(!memcmp(buf1
, read_buf
, 1 MB
));
876 // Truncate the resource fork
877 assert_no_err(fremovexattr(fd
, XATTR_RESOURCEFORK_NAME
, 0));
879 // And that should have finished the roll
880 args
.operation
= HFS_KR_OP_STATUS
;
881 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
883 assert(args
.done
== -1);
885 // Try and create a really fragmented file using F_PREALLOCATE
887 // First make a 2 block extent
889 off_t len
= 2 * block_size();
892 .fst_flags
= F_ALLOCATECONTIG
| F_ALLOCATEALL
,
893 .fst_posmode
= F_PEOFPOSMODE
,
898 assert_no_err(fcntl(fd
, F_PREALLOCATE
, &fstore
));
900 // Now allocate lots of single blocks
901 fstore
.fst_posmode
= F_VOLPOSMODE
;
904 * The maximum number of extents that the hfs_extents code
905 * can handle is 16384.
907 for (int i
= 0; i
< 16384; ++i
) {
908 struct log2phys l2p
= {
909 .l2p_contigbytes
= block_size(),
910 .l2p_devoffset
= len
- block_size(),
913 assert_no_err(fcntl(fd
, F_LOG2PHYS_EXT
, &l2p
));
918 fstore
.fst_offset
= l2p
.l2p_devoffset
+ 2 * block_size();
919 fstore
.fst_length
= len
;
921 assert_no_err(fcntl(fd
, F_PREALLOCATE
, &fstore
));
924 assert_no_err(ftruncate(fd
, len
));
926 // Now fill up the disk
929 fill_disk(&fd2
, &size
);
932 args
.operation
= HFS_KR_OP_START
;
933 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
935 off_t start
= 0, done
= 0, decr
= block_size();
938 args
.operation
= HFS_KR_OP_STEP
;
939 if (!ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0)) {
941 assert_equal_ll(args
.done
, done
);
945 assert_with_errno(errno
== ENOSPC
);
947 // It's possible we rolled a bit and then ran out of space
948 args
.operation
= HFS_KR_OP_STATUS
;
949 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
952 // If we've rolled over 2 MB in small bits, that's good enough
953 if (done
> start
+ 2 MB
)
958 assert_no_err(fcntl(fd2
, F_SETSIZE
, &size
));
961 * It's possible to get in a state where other things on the
962 * system use up disk space as fast as we can free it. To
963 * prevent this loop, decrement by a bit more next time.
965 decr
+= block_size();
969 * If unlink collides with the syncer, the file will be deleted on
970 * a different thread. Truncating the file here makes the
971 * recovery of space synchronous.
974 assert_no_err(ftruncate(fd2
, 0));
975 assert_no_err(close(fd2
));
976 assert_no_err(unlink(KEY_ROLL_FILL_DISK_FILE
));
979 args
.operation
= HFS_KR_OP_STEP
;
980 while (args
.done
!= -1)
981 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
983 // Start rolling again
984 args
.operation
= HFS_KR_OP_START
;
985 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
987 args
.operation
= HFS_KR_OP_STEP
;
988 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
990 assert_equal_ll(args
.done
, 2 MB
);
993 * That should have used a single extent and the rest of
994 * file should be reserved.
996 struct log2phys l2p
= {
997 .l2p_contigbytes
= 16 MB
,
1001 assert_no_err(fcntl(fd
, F_LOG2PHYS_EXT
, &l2p
));
1004 * The extent could have been split to minimise changes to the
1005 * extent groups. The first one should be a minimum of 2 MB
1006 * less 7 blocks. We only bother checking the first extent.
1008 if (l2p
.l2p_contigbytes
< 2 MB
- 7 * block_size()) {
1009 assert_fail("extent smaller than expected: %llu\n",
1010 l2p
.l2p_contigbytes
);
1013 // Try and allocate something just past it
1017 .fst_flags
= F_ALLOCATECONTIG
| F_ALLOCATEALL
,
1018 .fst_posmode
= F_VOLPOSMODE
,
1019 .fst_offset
= l2p
.l2p_devoffset
,
1020 .fst_length
= len
+ block_size(),
1023 assert_no_err(fcntl(fd
, F_PREALLOCATE
, &fstore
));
1024 assert(fstore
.fst_bytesalloc
== block_size());
1027 // Force it to be initialised
1028 check_io(pwrite(fd
, buf1
, block_size(), len
), block_size());
1030 // Now see where it was allocated
1031 struct log2phys l2p2
= {
1032 .l2p_contigbytes
= 1 MB
,
1033 .l2p_devoffset
= len
,
1036 assert_no_err(fcntl(fd
, F_LOG2PHYS_EXT
, &l2p2
));
1038 assert(l2p2
.l2p_contigbytes
== block_size());
1040 // It shouldn't be anywhere in the reserved range
1041 if (l2p2
.l2p_devoffset
== -1
1042 || (l2p2
.l2p_devoffset
+ block_size() > l2p
.l2p_devoffset
1043 && l2p2
.l2p_devoffset
< l2p
.l2p_devoffset
+ len
)) {
1044 assert_fail("unexpected allocation: %llu (reserved: %llu-%llu)",
1045 l2p2
.l2p_devoffset
, l2p
.l2p_devoffset
,
1046 l2p
.l2p_devoffset
+ len
- done
);
1050 assert_no_err(ftruncate(fd
, len
));
1052 args
.operation
= HFS_KR_OP_STATUS
;
1053 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1055 assert(args
.done
== 2 MB
);
1057 // Fill up the disk so the tentative blocks get used up
1058 fill_disk(&fd2
, &size
);
1060 // Now try and roll another chunk
1061 start
= done
= 2 MB
;
1062 decr
= block_size();
1065 args
.operation
= HFS_KR_OP_STEP
;
1066 if (!ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0)) {
1068 assert_equal_ll(args
.done
, done
);
1072 assert_with_errno(errno
== ENOSPC
);
1074 // It's possible we rolled a bit and then ran out of space
1075 args
.operation
= HFS_KR_OP_STATUS
;
1076 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1079 // If we've rolled over 2 MB in small bits, that's good enough
1080 if (done
> start
+ 2 MB
)
1085 assert_no_err(fcntl(fd2
, F_SETSIZE
, &size
));
1087 decr
+= block_size();
1090 assert_no_err(ftruncate(fd2
, 0));
1091 assert_no_err(close(fd2
));
1092 assert_no_err(unlink(KEY_ROLL_FILL_DISK_FILE
));
1095 args
.operation
= HFS_KR_OP_STEP
;
1097 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1098 } while (args
.done
!= -1);
1100 // Start rolling again
1101 args
.operation
= HFS_KR_OP_START
;
1102 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1105 args
.operation
= HFS_KR_OP_STEP
;
1107 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1108 } while (args
.done
!= -1);
1110 // That should have created a single extent
1111 l2p
.l2p_contigbytes
= UINT32_MAX
;
1112 l2p
.l2p_devoffset
= 0;
1114 assert_no_err(fcntl(fd
, F_LOG2PHYS_EXT
, &l2p
));
1116 assert_equal_ll(l2p
.l2p_contigbytes
, len
);
1118 assert_no_err(close(fd
));
1120 // -- Appending to file whilst rolling --
1122 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
,
1123 O_CREAT
| O_RDWR
| O_TRUNC
, 0666)) >= 0);
1125 struct append_ctx actx
= {
1130 assert_no_err(pthread_create(&thread
, NULL
, append_to_file
, &actx
));
1132 while (!actx
.done
) {
1133 args
.operation
= HFS_KR_OP_START
;
1134 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1137 args
.operation
= HFS_KR_OP_STEP
;
1138 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1139 } while (args
.done
!= -1);
1143 assert_with_errno((p
= mmap(NULL
, append_test_amount
, PROT_READ
,
1144 MAP_SHARED
, fd
, 0)) != MAP_FAILED
);
1146 assert_no_err(msync(p
, append_test_amount
, MS_INVALIDATE
));
1149 CC_MD5_Init(&md5_ctx
);
1151 CC_MD5_Update(&md5_ctx
, p
, append_test_amount
);
1153 uint8_t digest
[CC_MD5_DIGEST_LENGTH
];
1154 CC_MD5_Final(digest
, &md5_ctx
);
1157 * Not really necessary, but making the point that we need a barrier
1158 * between the check for done above and reading the digest.
1162 assert(!memcmp(digest
, actx
.digest
, CC_MD5_DIGEST_LENGTH
));
1164 assert_no_err(munmap(p
, append_test_amount
));
1166 assert_no_err(close(fd
));
1168 // -- Two threads rolling at the same time --
1170 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
,
1171 O_RDWR
, 0666)) >= 0);
1173 pthread_t threads
[2];
1174 threads_running
= 2;
1175 pthread_create(&threads
[0], NULL
, roll_thread
, NULL
);
1176 pthread_create(&threads
[0], NULL
, roll_thread
, NULL
);
1178 assert_with_errno((p
= mmap(NULL
, append_test_amount
, PROT_READ
,
1179 MAP_SHARED
, fd
, 0)) != MAP_FAILED
);
1181 bool finished
= false;
1183 if (!threads_running
)
1186 assert_no_err(msync(p
, append_test_amount
, MS_INVALIDATE
));
1189 CC_MD5_Init(&md5_ctx
);
1191 CC_MD5_Update(&md5_ctx
, p
, append_test_amount
);
1193 uint8_t digest
[CC_MD5_DIGEST_LENGTH
];
1194 CC_MD5_Final(digest
, &md5_ctx
);
1196 assert(!memcmp(digest
, actx
.digest
, CC_MD5_DIGEST_LENGTH
));
1197 } while (!finished
);
1199 pthread_join(threads
[0], NULL
);
1200 pthread_join(threads
[1], NULL
);
1202 // -- Class F files --
1204 assert_with_errno((fd
= open(KEY_ROLL_TEST_FILE
, O_RDWR
, 0666)) >= 0);
1206 // Finish off this file
1207 args
.operation
= HFS_KR_OP_STEP
;
1210 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1211 } while (args
.done
!= -1);
1213 // Should fail because file has data
1214 assert(fcntl(fd
, F_SETPROTECTIONCLASS
, 6) == -1 && errno
== EINVAL
);
1216 // Start key rolling
1217 args
.operation
= HFS_KR_OP_START
;
1218 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1219 assert(args
.done
== 0);
1222 assert_no_err(ftruncate(fd
, 0));
1225 args
.operation
= HFS_KR_OP_STATUS
;
1226 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1228 // We truncated the file so it rolling should have been aborted
1229 assert(args
.done
== -1);
1231 // Now setting to class F should succeed
1232 assert_no_err(fcntl(fd
, F_SETPROTECTIONCLASS
, 6));
1234 // Attempts to roll should fail
1235 args
.operation
= HFS_KR_OP_START
;
1236 assert(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0) == -1 && errno
== ENOTSUP
);
1238 assert_no_err(close(fd
));
1240 // -- Rolling non-files --
1242 // Check class inheritance
1243 assert_no_err(mkdir("/tmp/key-roll-test.dir", 0777));
1245 // Should be class D
1246 assert_no_err(getattrlist("/tmp/key-roll-test.dir", &attrlist
, &attrs
,
1249 // Dir should be class D
1250 assert((attrs
.dp_flags
& 0x1f) == 4);
1253 assert_with_errno((fd
= open("/tmp/key-roll-test.dir/file1",
1254 O_RDWR
| O_TRUNC
| O_CREAT
, 0666)) >= 0);
1256 // Make sure it's class D
1257 assert_no_err(fgetattrlist(fd
, &attrlist
, &attrs
, sizeof(attrs
), 0));
1258 assert((attrs
.dp_flags
& 0x1f) == 4);
1260 assert_with_errno((fd
= open("/tmp/key-roll-test.dir", O_RDONLY
)) >= 0);
1262 // Change directory to class C
1263 assert_no_err(fcntl(fd
, F_SETPROTECTIONCLASS
, 3));
1265 assert_no_err(close(fd
));
1267 // Create another file and make sure it's class C
1268 assert_with_errno((fd
= open("/tmp/key-roll-test.dir/file2",
1269 O_RDWR
| O_TRUNC
| O_CREAT
, 0666)) >= 0);
1271 assert_no_err(fgetattrlist(fd
, &attrlist
, &attrs
, sizeof(attrs
), 0));
1272 assert((attrs
.dp_flags
& 0x1f) == 3);
1274 assert_no_err(close(fd
));
1276 // Try and roll a directory--it should fail
1277 args
.operation
= HFS_KR_OP_START
;
1278 assert(fsctl("/tmp/key-roll-test.dir", HFSIOC_KEY_ROLL
, &args
, 0) == -1
1279 && errno
== ENOTSUP
);
1281 args
.operation
= HFS_KR_OP_STATUS
;
1282 assert_no_err(fsctl("/tmp/key-roll-test.dir", HFSIOC_KEY_ROLL
, &args
, 0));
1284 assert(args
.done
== -1 && args
.total
== 0);
1286 unlink(KEY_ROLL_SYM_LINK
);
1287 assert_no_err(symlink("/tmp/key-roll-test.dir/file2",
1288 KEY_ROLL_SYM_LINK
));
1290 assert_with_errno((fd
= open(KEY_ROLL_SYM_LINK
,
1291 O_RDONLY
| O_SYMLINK
)) >= 0);
1293 args
.operation
= HFS_KR_OP_START
;
1294 assert(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0) == -1 && errno
== ENOTSUP
);
1296 args
.operation
= HFS_KR_OP_STATUS
;
1297 assert_no_err(ffsctl(fd
, HFSIOC_KEY_ROLL
, &args
, 0));
1298 assert(args
.done
== -1 && args
.total
== 0);
1300 assert_no_err(close(fd
));
1303 unlink(KEY_ROLL_TEST_FILE
);
1304 unlink(KEY_ROLL_SYM_LINK
);
1305 systemx("/bin/rm", "-rf", KEY_ROLL_TEST_DIR
, NULL
);
1310 #endif // TARGET_OS_EMBEDDED