]>
Commit | Line | Data |
---|---|---|
558d2836 A |
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 |