]> git.saurik.com Git - apple/hfs.git/blob - tests/cases/test-key-roll.c
hfs-366.1.1.tar.gz
[apple/hfs.git] / tests / cases / test-key-roll.c
1 #include <TargetConditionals.h>
2
3 #if TARGET_OS_EMBEDDED
4
5 #include <unistd.h>
6 #include <fcntl.h>
7 #include <stdio.h>
8 #include <sys/mman.h>
9 #include <string.h>
10 #include <sys/attr.h>
11 #include <sys/types.h>
12 #include <sys/sysctl.h>
13 #include <sys/stat.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>
19 #include <pthread.h>
20 #include <spawn.h>
21 #include <MobileKeyBag/MobileKeyBag.h>
22 #include <hfs/hfs_fsctl.h>
23
24 #include "hfs-tests.h"
25 #include "test-utils.h"
26 #include "systemx.h"
27
28 TEST(key_roll, .run_as_root = true)
29
30 static void *buf1;
31
32 #define MB * 1024 * 1024
33
34 #define F_RECYCLE 84
35
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"
43
44 int cmp_zero(const void *p, size_t len)
45 {
46 const uint8_t *x = p;
47
48 while (len && (uintptr_t)x & 7) {
49 if (*x)
50 return 1;
51 ++x;
52 --len;
53 }
54
55 const uint64_t *y = (uint64_t *)x;
56 while (len >= 8) {
57 if (*y)
58 return 1;
59 ++y;
60 len -= 8;
61 }
62
63 x = (uint8_t *)y;
64 while (len) {
65 if (*x)
66 return 1;
67 ++y;
68 --len;
69 }
70
71 return 0;
72 }
73
74 struct append_ctx {
75 int fd;
76 uint8_t digest[CC_MD5_DIGEST_LENGTH];
77 bool done;
78 };
79
80 #if 1
81 static const int append_test_amount = 128 MB;
82 #else
83 #warning
84 static const int append_test_amount = 1 MB;
85 #endif
86
87 void *append_to_file(void *param)
88 {
89 struct append_ctx *ctx = param;
90
91 CC_MD5_CTX md5_ctx;
92 CC_MD5_Init(&md5_ctx);
93
94 uint64_t total = 0;
95
96 void *p = mmap(NULL, append_test_amount, PROT_READ | PROT_WRITE,
97 MAP_SHARED, ctx->fd, 0);
98 assert(p != MAP_FAILED);
99
100 int page_size = getpagesize();
101
102 while (total < append_test_amount) {
103 size_t todo = random() % (1 MB) + 1;
104
105 if (todo > append_test_amount - total)
106 todo = append_test_amount - total;
107
108 check_io(write(ctx->fd, buf1, todo), todo);
109
110 int round = ((uintptr_t)p + total) % page_size;
111
112 assert_no_err(msync(p + total - round, todo + round,
113 MS_ASYNC | MS_INVALIDATE));
114
115 CC_MD5_Update(&md5_ctx, buf1, todo);
116
117 total += todo;
118 }
119
120 CC_MD5_Final(ctx->digest, &md5_ctx);
121
122 OSMemoryBarrier();
123
124 ctx->done = true;
125
126 assert_no_err(munmap(p, append_test_amount));
127
128 return NULL;
129 }
130
131 static uint32_t os_version(void)
132 {
133 static uint32_t os_version;
134 if (os_version)
135 return os_version;
136
137 char os_version_str[128];
138 size_t size = sizeof(os_version_str);
139 assert_no_err(sysctlbyname("kern.osversion", os_version_str,
140 &size, NULL, 0));
141
142 const char *p = os_version_str;
143
144 int a = 0;
145 while (*p >= '0' && *p <= '9') {
146 a = a * 10 + *p - '0';
147 ++p;
148 }
149
150 if (!a)
151 return 0;
152
153 int b = *p++;
154 if (!b)
155 return 0;
156
157 int c = 0;
158 while (*p >= '0' && *p <= '9') {
159 c = c * 10 + *p - '0';
160 ++p;
161 }
162
163 if (!c)
164 return 0;
165
166 os_version = (a & 0xff) << 24 | b << 16 | (c & 0xffff);
167
168 return os_version;
169 }
170
171 static int block_size(void)
172 {
173 static int block_size;
174
175 if (!block_size) {
176 struct statfs sfs;
177
178 assert_no_err(statfs("/private/var", &sfs));
179
180 block_size = sfs.f_bsize;
181 }
182
183 return block_size;
184 }
185
186 static void fill_disk(int *fd, uint64_t *size)
187 {
188 assert_with_errno((*fd = open(KEY_ROLL_FILL_DISK_FILE,
189 O_CREAT | O_RDWR | O_TRUNC, 0666)) >= 0);
190
191 // Fill up the disk so there's no remaining disk space
192 struct statfs sfs;
193 assert_no_err(fstatfs(*fd, &sfs));
194
195 uint32_t blocks = sfs.f_bfree;
196
197 for (;;) {
198 uint64_t size = (uint64_t)blocks * sfs.f_bsize;
199
200 if (!fcntl(*fd, F_SETSIZE, &size))
201 break;
202
203 assert_with_errno(errno == ENOSPC);
204
205 blocks /= 2;
206 }
207
208 // Now increase the size until we hit no space
209 uint32_t upper = sfs.f_bfree + 128;
210
211 for (;;) {
212 uint32_t try = (upper + blocks) / 2;
213
214 if (try <= blocks)
215 try = blocks + 1;
216
217 uint64_t size = (uint64_t)try * sfs.f_bsize;
218 if (!fcntl(*fd, F_SETSIZE, &size)) {
219 blocks = try;
220 if (try >= upper) {
221 assert_no_err(fstatfs(*fd, &sfs));
222 upper = try + sfs.f_bfree + 128;
223 }
224 } else {
225 assert(errno == ENOSPC);
226
227 if (try == blocks + 1)
228 break;
229 else
230 upper = try;
231 }
232 }
233
234 *size = (uint64_t)blocks * sfs.f_bsize;
235 }
236
237 volatile int32_t threads_running;
238
239 static void *roll_thread(void *arg __attribute__((unused)))
240 {
241 int fd;
242
243 assert_with_errno((fd = open(KEY_ROLL_TEST_FILE, O_RDWR)) >= 0);
244
245 hfs_key_roll_args_t args = {
246 .api_version = HFS_KR_API_LATEST_VERSION,
247 .operation = HFS_KR_OP_STEP,
248 };
249
250 for (int i = 0; i < 100; ++i) {
251 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
252
253 if (args.done == -1)
254 args.operation = HFS_KR_OP_START;
255 else
256 args.operation = HFS_KR_OP_STEP;
257 }
258
259 assert_no_err(close(fd));
260
261 OSAtomicDecrement32(&threads_running);
262
263 return NULL;
264 }
265
266 int run_key_roll(__unused test_ctx_t *ctx)
267 {
268 // The root file system needs to be HFS
269 struct statfs sfs;
270
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");
274 return 0;
275 }
276
277 int fd;
278 void *read_buf = malloc(2 MB);
279 void *p;
280
281 struct attrlist attrlist = {
282 .bitmapcount = ATTR_BIT_MAP_COUNT,
283 .commonattr = ATTR_CMN_DATA_PROTECT_FLAGS,
284 };
285
286 struct attrs {
287 uint32_t len;
288 uint32_t dp_flags;
289 } attrs;
290
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);
296
297 buf1 = malloc(1 MB);
298 memset(buf1, 0x25, 1 MB);
299
300 void *buf2 = malloc(1 MB);
301 memset(buf2, 0x49, 1 MB);
302
303 // First, force change to new xattr version
304
305 assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
306 O_CREAT | O_RDWR | O_TRUNC, 0666)) >= 0);
307
308 // Write 3 MB
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);
312
313 hfs_key_roll_args_t args = {
314 .api_version = HFS_KR_API_LATEST_VERSION,
315 .operation = HFS_KR_OP_START,
316 };
317 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
318
319 args.operation = HFS_KR_OP_STEP;
320 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
321
322 assert_no_err(unlink(KEY_ROLL_TEST_FILE));
323 assert_no_err(close(fd));
324
325 /*
326 * That should have switch the device to new xattr version. Continue
327 * with more tests now...
328 */
329
330 assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
331 O_CREAT | O_RDWR | O_TRUNC, 0666)) >= 0);
332
333 // Write 2 MB
334 check_io(write(fd, buf1, 1 MB), 1 MB);
335 check_io(write(fd, buf1, 1 MB), 1 MB);
336
337 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
338
339 assert(args.key_revision == 1
340 && args.key_os_version == os_version()
341 && args.done == -1
342 && args.total == 2 MB);
343
344 // Extend file to 3 MB
345 assert_no_err(ftruncate(fd, 3 MB));
346
347 // Start rolling
348 args.operation = HFS_KR_OP_START;
349 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
350
351 assert((args.key_revision & 0xff00) == 0x0100 && args.done == 0
352 && args.total == 3 MB);
353
354 // Roll 1 chunk
355 args.operation = HFS_KR_OP_STEP;
356 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
357
358 assert(args.done == 2 MB);
359
360 // Write a little way into the last MB
361 off_t offset = 2 MB + 50000;
362
363 check_io(pwrite(fd, buf2, 1024, offset), 1024);
364
365 // Roll to end of file
366 args.operation = HFS_KR_OP_STEP;
367 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
368
369 // This time, it should have finished
370 assert(args.done == -1);
371
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);
375
376 // Force flush of cache
377 assert_no_err(msync(p, 3 MB, MS_INVALIDATE));
378
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));
384
385 // -- Rewrapping tests --
386
387 assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
388
389 // File should be class D
390 assert((attrs.dp_flags & 0x1f) == 4);
391
392 // Start rolling
393 args.operation = HFS_KR_OP_START;
394 args.flags = HFS_KR_MATCH_KEY_REVISION;
395
396 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
397
398 // Reset flags for later tests
399 args.flags = 0;
400
401 assert(args.done == 0 && (args.key_revision & 0xff00) == 0x0200);
402
403 // Roll 1 chunk
404 args.operation = HFS_KR_OP_STEP;
405 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
406
407 assert(args.done == 2 MB);
408
409 // Change file to class C
410 assert_no_err(fcntl(fd, F_SETPROTECTIONCLASS, 3));
411
412 assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
413 assert((attrs.dp_flags & 0x1f) == 3);
414
415 // Force file to be recycled (won't work on release builds)
416 bool release_build = false;
417
418 if (fcntl(fd, F_RECYCLE)) {
419 assert_equal_int(errno, ENOTTY);
420 release_build = true;
421 }
422
423 // Release refs so recycle happens
424 assert_no_err(close(fd));
425 assert_no_err(munmap(p, 3 MB));
426
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);
431
432 args.operation = HFS_KR_OP_STATUS;
433 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
434
435 assert(args.done == 2 MB);
436
437 // Check the content
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));
443
444 // Check the class again
445 assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
446 assert((attrs.dp_flags & 0x1f) == 3);
447
448 // Change to class 1
449 assert_no_err(fcntl(fd, F_SETPROTECTIONCLASS, 1));
450
451 // Check it
452 assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
453 assert((attrs.dp_flags & 0x1f) == 1);
454
455 assert_with_errno(release_build || !fcntl(fd, F_RECYCLE));
456
457 // Should get recycled after this
458 assert_no_err(close(fd));
459 assert_no_err(munmap(p, 3 MB));
460
461 int fd2 = open(KEY_ROLL_TEST_FILE_2, O_RDWR | O_CREAT, 0666);
462
463 /*
464 * We can't check this file until we've triggered an unlock
465 * which means we need to set a system password.
466 */
467
468 // Change system password
469 assert_no_err(systemx(KEYSTORECTL, "change-password", "", "1234", NULL));
470
471 // Now we can check the file
472
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);
476
477 assert(!memcmp(p, buf1, 1 MB));
478
479 // Open for raw access
480 int raw_fd;
481 assert_with_errno((raw_fd = open_dprotected_np(KEY_ROLL_TEST_FILE,
482 O_RDONLY, 0, 1, 0)) >= 0);
483
484 // Lock device
485 assert_no_err(systemx(KEYBAGDTEST, "lock", NULL));
486
487 // Wait until the device is locked
488 while (MKBGetDeviceLockState(NULL) != kMobileKeyBagDeviceIsLocked)
489 sleep(1);
490
491 // Set second file to class B
492 assert_no_err(fcntl(fd2, F_SETPROTECTIONCLASS, 2));
493
494 // Make sure we can write to it
495 check_io(write(fd2, buf1, 1 MB), 1 MB);
496
497 assert_no_err(close(fd2));
498 assert_no_err(unlink(KEY_ROLL_TEST_FILE_2));
499
500 // Try and read data
501 assert(read(fd, read_buf, 1 MB) == -1 && errno == EPERM);
502
503 // Now try and continue rolling
504
505 args.operation = HFS_KR_OP_STEP;
506 assert(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0) == -1
507 && errno == EPERM);
508
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));
512
513 // Make sure reading the raw file fails
514 assert(read(raw_fd, read_buf, 1 MB) == -1 && errno == EPERM);
515
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);
519
520 assert_no_err(systemx(KEYSTORECTL, "unlock", "1234", NULL));
521
522 // Now check the raw read works
523 check_io(read(raw_fd, read_buf, 1 MB), 1 MB);
524
525 assert_no_err(close(raw_fd));
526
527 // Check data
528
529 assert(!memcmp(p, buf1, 1 MB));
530
531 // Change system password back
532
533 assert_no_err(systemx(KEYSTORECTL, "change-password", "1234", "", NULL));
534
535 // -- Raw mode tests --
536
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);
540
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);
545
546 // Issue a read
547 check_io(read(raw_fd, read_buf, 2 MB), 2 MB);
548
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);
553
554 // Issue more reads
555 check_io(read(raw_fd, read_buf, 2 MB), 1 MB);
556
557 // Key rolling should have been finished
558
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);
562
563 assert_no_err(close(raw_fd));
564
565 // Change the revision and os version
566 if (release_build) {
567 // HFS_KR_OP_SET_INFO isn't supported on release build
568
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,
573 };
574
575 assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL, &auto_roll_args, 0));
576 } else {
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));
581
582 args.operation = HFS_KR_OP_STATUS;
583 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
584
585 assert(args.done == -1
586 && args.key_revision == 0x0200
587 && args.key_os_version == CP_OS_VERS_PRE_71);
588
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,
593 };
594
595 assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL, &auto_roll_args, 0));
596 }
597
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);
601
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);
606
607 // Issue a read
608 check_io(read(raw_fd, read_buf, 1 MB), 1 MB);
609
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());
614
615 {
616 // Check that reservation is working as expected
617
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(),
622 };
623
624 assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
625 assert(l2p.l2p_contigbytes == block_size());
626
627 // Now try and extend the file by a block
628 fstore_t fstore = {
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(),
633 };
634
635 assert_no_err(fcntl(fd, F_PREALLOCATE, &fstore));
636 assert_equal_ll(fstore.fst_bytesalloc, block_size());
637
638 // Force it to be initialised
639 check_io(pwrite(fd, buf1, block_size(), 3 MB), block_size());
640
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));
645
646 assert(l2p.l2p_contigbytes == block_size());
647
648 /*
649 * It shouldn't be in the 1 MB spot that should be reserved
650 * for rolling the last bit.
651 */
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);
657 }
658
659 // Restore the file to its original length
660 assert_no_err(ftruncate(fd, 3 MB));
661 }
662
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));
666
667 assert(args.done == 2 MB && (args.key_revision & 0xff00) == 0x0300);
668
669 check_io(read(raw_fd, read_buf, 1 MB), 1 MB);
670
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));
674
675 assert(args.done == 2 MB);
676
677 // Read the last bit
678 check_io(read(raw_fd, read_buf, 2 MB), 1 MB);
679
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));
683
684 assert(args.done == -1);
685
686 // Trying to initiate rolling should fail because we have it open for
687 // raw access.
688 args.operation = HFS_KR_OP_START;
689 assert(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0) == -1
690 && errno == EBUSY);
691
692 assert_no_err(close(raw_fd));
693
694 /*
695 * Make sure we can open directories raw whilst auto-rolling is
696 * enabled. We've picked a directory here that's class C.
697 */
698 assert_with_errno((raw_fd =
699 open_dprotected_np("/private/var/mobile/Library/Passes",
700 O_RDONLY | O_NOFOLLOW,
701 0, 1, 0)) >= 0);
702
703 assert_no_err(close(raw_fd));
704
705 if (!release_build) {
706 /*
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
709 * file.
710 */
711
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);
715
716 // Status should remain unchanged
717 args.operation = HFS_KR_OP_STATUS;
718 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
719
720 assert(args.done == -1 && (args.key_revision & 0xff00) == 0x0300
721 && args.key_os_version == os_version());
722
723 assert_no_err(close(raw_fd));
724 }
725
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,
729 };
730
731 assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL, &auto_roll_args, 0));
732
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));
736
737 assert(args.done == 0 && (args.key_revision & 0xff00) == 0x0400);
738
739 // Roll 1 chunk
740 args.operation = HFS_KR_OP_STEP;
741 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
742
743 assert(args.done == 2 MB);
744
745 // Truncate the file
746 assert_no_err(ftruncate(fd, 1 MB));
747
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));
751
752 assert(args.done == -1);
753
754 assert_no_err(ftruncate(fd, 3 MB));
755
756 // Start rolling again
757 args.operation = HFS_KR_OP_START;
758 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
759
760 assert(args.done == 0 && (args.key_revision & 0xff00) == 0x0500);
761
762 // Roll 1 chunk
763 args.operation = HFS_KR_OP_STEP;
764 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
765
766 assert(args.done == 2 MB);
767
768 // Delete the file
769 assert_no_err(unlink(KEY_ROLL_TEST_FILE));
770
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));
774
775 assert(args.done == 2 MB);
776
777 // Finish rolling
778 args.operation = HFS_KR_OP_STEP;
779 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
780
781 assert(args.done == -1);
782
783 assert_no_err(close(fd));
784
785 // Check file
786 assert(!memcmp(p, buf1, 1 MB));
787 assert(!cmp_zero(p + 1 MB, 2 MB));
788
789 assert_no_err(munmap(p, 3 MB));
790
791 // -- Resource fork --
792
793 assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
794 O_CREAT | O_RDWR, 0666)) >= 0);
795
796 for (int i = 0; i < 3; ++i) {
797 check_io(write(fd, buf1, 1 MB), 1 MB);
798 }
799
800 for (int i = 0; i < 3; ++i) {
801 assert_no_err(fsetxattr(fd, XATTR_RESOURCEFORK_NAME, buf1,
802 1 MB, i MB, 0));
803 }
804
805 args.operation = HFS_KR_OP_START;
806 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
807
808 assert(args.done == 0 && args.total == 6 MB);
809
810 args.operation = HFS_KR_OP_STEP;
811 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
812
813 assert(args.done == 2 MB);
814
815 args.operation = HFS_KR_OP_STEP;
816 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
817
818 assert(args.done == 3 MB);
819
820 // Should have switched to resource fork
821
822 args.operation = HFS_KR_OP_STEP;
823 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
824
825 assert(args.done == 5 MB);
826
827 args.operation = HFS_KR_OP_STEP;
828 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
829
830 assert(args.done == -1);
831
832 // Check the data
833
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);
837
838 assert(!memcmp(buf1, read_buf, 1 MB));
839 }
840
841 // Now try again, but this time truncate data fork
842
843 args.operation = HFS_KR_OP_START;
844 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
845
846 assert(args.done == 0 && args.total == 6 MB);
847
848 args.operation = HFS_KR_OP_STEP;
849 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
850
851 assert(args.done == 2 MB);
852
853 assert_no_err(ftruncate(fd, 0));
854
855 // Should have switched to resource fork
856
857 args.operation = HFS_KR_OP_STATUS;
858 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
859
860 assert(args.done == 0);
861
862 args.operation = HFS_KR_OP_STEP;
863 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
864
865 assert(args.done == 2 MB);
866
867 // Check the data whilst we're in the middle of rolling
868
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);
872
873 assert(!memcmp(buf1, read_buf, 1 MB));
874 }
875
876 // Truncate the resource fork
877 assert_no_err(fremovexattr(fd, XATTR_RESOURCEFORK_NAME, 0));
878
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));
882
883 assert(args.done == -1);
884
885 // Try and create a really fragmented file using F_PREALLOCATE
886
887 // First make a 2 block extent
888
889 off_t len = 2 * block_size();
890
891 fstore_t fstore = {
892 .fst_flags = F_ALLOCATECONTIG | F_ALLOCATEALL,
893 .fst_posmode = F_PEOFPOSMODE,
894 .fst_offset = 0,
895 .fst_length = len,
896 };
897
898 assert_no_err(fcntl(fd, F_PREALLOCATE, &fstore));
899
900 // Now allocate lots of single blocks
901 fstore.fst_posmode = F_VOLPOSMODE;
902
903 /*
904 * The maximum number of extents that the hfs_extents code
905 * can handle is 16384.
906 */
907 for (int i = 0; i < 16384; ++i) {
908 struct log2phys l2p = {
909 .l2p_contigbytes = block_size(),
910 .l2p_devoffset = len - block_size(),
911 };
912
913 assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
914
915 len += block_size();
916
917 // Force a gap
918 fstore.fst_offset = l2p.l2p_devoffset + 2 * block_size();
919 fstore.fst_length = len;
920
921 assert_no_err(fcntl(fd, F_PREALLOCATE, &fstore));
922 }
923
924 assert_no_err(ftruncate(fd, len));
925
926 // Now fill up the disk
927 uint64_t size;
928
929 fill_disk(&fd2, &size);
930
931 // Now try and roll
932 args.operation = HFS_KR_OP_START;
933 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
934
935 off_t start = 0, done = 0, decr = block_size();
936
937 for (;;) {
938 args.operation = HFS_KR_OP_STEP;
939 if (!ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0)) {
940 done += 2 MB;
941 assert_equal_ll(args.done, done);
942 break;
943 }
944
945 assert_with_errno(errno == ENOSPC);
946
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));
950 done = args.done;
951
952 // If we've rolled over 2 MB in small bits, that's good enough
953 if (done > start + 2 MB)
954 break;
955
956 // Shrink a bit
957 size -= decr;
958 assert_no_err(fcntl(fd2, F_SETSIZE, &size));
959
960 /*
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.
964 */
965 decr += block_size();
966 }
967
968 /*
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.
972 */
973
974 assert_no_err(ftruncate(fd2, 0));
975 assert_no_err(close(fd2));
976 assert_no_err(unlink(KEY_ROLL_FILL_DISK_FILE));
977
978 // Finish rolling
979 args.operation = HFS_KR_OP_STEP;
980 while (args.done != -1)
981 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
982
983 // Start rolling again
984 args.operation = HFS_KR_OP_START;
985 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
986
987 args.operation = HFS_KR_OP_STEP;
988 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
989
990 assert_equal_ll(args.done, 2 MB);
991
992 /*
993 * That should have used a single extent and the rest of
994 * file should be reserved.
995 */
996 struct log2phys l2p = {
997 .l2p_contigbytes = 16 MB,
998 .l2p_devoffset = 0,
999 };
1000
1001 assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
1002
1003 /*
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.
1007 */
1008 if (l2p.l2p_contigbytes < 2 MB - 7 * block_size()) {
1009 assert_fail("extent smaller than expected: %llu\n",
1010 l2p.l2p_contigbytes);
1011 }
1012
1013 // Try and allocate something just past it
1014
1015 {
1016 fstore_t fstore = {
1017 .fst_flags = F_ALLOCATECONTIG | F_ALLOCATEALL,
1018 .fst_posmode = F_VOLPOSMODE,
1019 .fst_offset = l2p.l2p_devoffset,
1020 .fst_length = len + block_size(),
1021 };
1022
1023 assert_no_err(fcntl(fd, F_PREALLOCATE, &fstore));
1024 assert(fstore.fst_bytesalloc == block_size());
1025 }
1026
1027 // Force it to be initialised
1028 check_io(pwrite(fd, buf1, block_size(), len), block_size());
1029
1030 // Now see where it was allocated
1031 struct log2phys l2p2 = {
1032 .l2p_contigbytes = 1 MB,
1033 .l2p_devoffset = len,
1034 };
1035
1036 assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p2));
1037
1038 assert(l2p2.l2p_contigbytes == block_size());
1039
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);
1047 }
1048
1049 // Revert extension
1050 assert_no_err(ftruncate(fd, len));
1051
1052 args.operation = HFS_KR_OP_STATUS;
1053 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
1054
1055 assert(args.done == 2 MB);
1056
1057 // Fill up the disk so the tentative blocks get used up
1058 fill_disk(&fd2, &size);
1059
1060 // Now try and roll another chunk
1061 start = done = 2 MB;
1062 decr = block_size();
1063
1064 for (;;) {
1065 args.operation = HFS_KR_OP_STEP;
1066 if (!ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0)) {
1067 done += 2 MB;
1068 assert_equal_ll(args.done, done);
1069 break;
1070 }
1071
1072 assert_with_errno(errno == ENOSPC);
1073
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));
1077 done = args.done;
1078
1079 // If we've rolled over 2 MB in small bits, that's good enough
1080 if (done > start + 2 MB)
1081 break;
1082
1083 // Drop by a bit
1084 size -= decr;
1085 assert_no_err(fcntl(fd2, F_SETSIZE, &size));
1086
1087 decr += block_size();
1088 }
1089
1090 assert_no_err(ftruncate(fd2, 0));
1091 assert_no_err(close(fd2));
1092 assert_no_err(unlink(KEY_ROLL_FILL_DISK_FILE));
1093
1094 // Finish the roll
1095 args.operation = HFS_KR_OP_STEP;
1096 do {
1097 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
1098 } while (args.done != -1);
1099
1100 // Start rolling again
1101 args.operation = HFS_KR_OP_START;
1102 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
1103
1104 // And finish
1105 args.operation = HFS_KR_OP_STEP;
1106 do {
1107 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
1108 } while (args.done != -1);
1109
1110 // That should have created a single extent
1111 l2p.l2p_contigbytes = UINT32_MAX;
1112 l2p.l2p_devoffset = 0;
1113
1114 assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
1115
1116 assert_equal_ll(l2p.l2p_contigbytes, len);
1117
1118 assert_no_err(close(fd));
1119
1120 // -- Appending to file whilst rolling --
1121
1122 assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
1123 O_CREAT | O_RDWR | O_TRUNC, 0666)) >= 0);
1124
1125 struct append_ctx actx = {
1126 .fd = fd,
1127 };
1128
1129 pthread_t thread;
1130 assert_no_err(pthread_create(&thread, NULL, append_to_file, &actx));
1131
1132 while (!actx.done) {
1133 args.operation = HFS_KR_OP_START;
1134 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
1135
1136 do {
1137 args.operation = HFS_KR_OP_STEP;
1138 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
1139 } while (args.done != -1);
1140 }
1141
1142 // Check file
1143 assert_with_errno((p = mmap(NULL, append_test_amount, PROT_READ,
1144 MAP_SHARED, fd, 0)) != MAP_FAILED);
1145
1146 assert_no_err(msync(p, append_test_amount, MS_INVALIDATE));
1147
1148 CC_MD5_CTX md5_ctx;
1149 CC_MD5_Init(&md5_ctx);
1150
1151 CC_MD5_Update(&md5_ctx, p, append_test_amount);
1152
1153 uint8_t digest[CC_MD5_DIGEST_LENGTH];
1154 CC_MD5_Final(digest, &md5_ctx);
1155
1156 /*
1157 * Not really necessary, but making the point that we need a barrier
1158 * between the check for done above and reading the digest.
1159 */
1160 OSMemoryBarrier();
1161
1162 assert(!memcmp(digest, actx.digest, CC_MD5_DIGEST_LENGTH));
1163
1164 assert_no_err(munmap(p, append_test_amount));
1165
1166 assert_no_err(close(fd));
1167
1168 // -- Two threads rolling at the same time --
1169
1170 assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
1171 O_RDWR, 0666)) >= 0);
1172
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);
1177
1178 assert_with_errno((p = mmap(NULL, append_test_amount, PROT_READ,
1179 MAP_SHARED, fd, 0)) != MAP_FAILED);
1180
1181 bool finished = false;
1182 do {
1183 if (!threads_running)
1184 finished = true;
1185
1186 assert_no_err(msync(p, append_test_amount, MS_INVALIDATE));
1187
1188 CC_MD5_CTX md5_ctx;
1189 CC_MD5_Init(&md5_ctx);
1190
1191 CC_MD5_Update(&md5_ctx, p, append_test_amount);
1192
1193 uint8_t digest[CC_MD5_DIGEST_LENGTH];
1194 CC_MD5_Final(digest, &md5_ctx);
1195
1196 assert(!memcmp(digest, actx.digest, CC_MD5_DIGEST_LENGTH));
1197 } while (!finished);
1198
1199 pthread_join(threads[0], NULL);
1200 pthread_join(threads[1], NULL);
1201
1202 // -- Class F files --
1203
1204 assert_with_errno((fd = open(KEY_ROLL_TEST_FILE, O_RDWR, 0666)) >= 0);
1205
1206 // Finish off this file
1207 args.operation = HFS_KR_OP_STEP;
1208
1209 do {
1210 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
1211 } while (args.done != -1);
1212
1213 // Should fail because file has data
1214 assert(fcntl(fd, F_SETPROTECTIONCLASS, 6) == -1 && errno == EINVAL);
1215
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);
1220
1221 // Truncate file
1222 assert_no_err(ftruncate(fd, 0));
1223
1224 // Check status
1225 args.operation = HFS_KR_OP_STATUS;
1226 assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
1227
1228 // We truncated the file so it rolling should have been aborted
1229 assert(args.done == -1);
1230
1231 // Now setting to class F should succeed
1232 assert_no_err(fcntl(fd, F_SETPROTECTIONCLASS, 6));
1233
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);
1237
1238 assert_no_err(close(fd));
1239
1240 // -- Rolling non-files --
1241
1242 // Check class inheritance
1243 assert_no_err(mkdir("/tmp/key-roll-test.dir", 0777));
1244
1245 // Should be class D
1246 assert_no_err(getattrlist("/tmp/key-roll-test.dir", &attrlist, &attrs,
1247 sizeof(attrs), 0));
1248
1249 // Dir should be class D
1250 assert((attrs.dp_flags & 0x1f) == 4);
1251
1252 // Create file
1253 assert_with_errno((fd = open("/tmp/key-roll-test.dir/file1",
1254 O_RDWR | O_TRUNC | O_CREAT, 0666)) >= 0);
1255
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);
1259
1260 assert_with_errno((fd = open("/tmp/key-roll-test.dir", O_RDONLY)) >= 0);
1261
1262 // Change directory to class C
1263 assert_no_err(fcntl(fd, F_SETPROTECTIONCLASS, 3));
1264
1265 assert_no_err(close(fd));
1266
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);
1270
1271 assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
1272 assert((attrs.dp_flags & 0x1f) == 3);
1273
1274 assert_no_err(close(fd));
1275
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);
1280
1281 args.operation = HFS_KR_OP_STATUS;
1282 assert_no_err(fsctl("/tmp/key-roll-test.dir", HFSIOC_KEY_ROLL, &args, 0));
1283
1284 assert(args.done == -1 && args.total == 0);
1285
1286 unlink(KEY_ROLL_SYM_LINK);
1287 assert_no_err(symlink("/tmp/key-roll-test.dir/file2",
1288 KEY_ROLL_SYM_LINK));
1289
1290 assert_with_errno((fd = open(KEY_ROLL_SYM_LINK,
1291 O_RDONLY | O_SYMLINK)) >= 0);
1292
1293 args.operation = HFS_KR_OP_START;
1294 assert(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0) == -1 && errno == ENOTSUP);
1295
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);
1299
1300 assert_no_err(close(fd));
1301
1302 // Tidy up
1303 unlink(KEY_ROLL_TEST_FILE);
1304 unlink(KEY_ROLL_SYM_LINK);
1305 systemx("/bin/rm", "-rf", KEY_ROLL_TEST_DIR, NULL);
1306
1307 return 0;
1308 }
1309
1310 #endif // TARGET_OS_EMBEDDED