#include <TargetConditionals.h>
#if TARGET_OS_EMBEDDED
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/attr.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <CommonCrypto/CommonDigest.h>
#include <libkern/OSAtomic.h>
#include <pthread.h>
#include <spawn.h>
#include <MobileKeyBag/MobileKeyBag.h>
#include <hfs/hfs_fsctl.h>
#include "hfs-tests.h"
#include "test-utils.h"
#include "systemx.h"
TEST(key_roll, .run_as_root = true)
static void *buf1;
#define MB * 1024 * 1024
#define F_RECYCLE 84
#define KEY_ROLL_TEST_FILE "/tmp/key-roll-test.data"
#define KEY_ROLL_TEST_FILE_2 "/tmp/key-roll-test-2.data"
#define KEY_ROLL_FILL_DISK_FILE "/tmp/key-roll-fill-disk"
#define KEY_ROLL_TEST_DIR "/tmp/key-roll-test.dir"
#define KEY_ROLL_SYM_LINK "/tmp/key-roll-test.symlink"
#define KEYSTORECTL "/usr/local/bin/keystorectl"
#define KEYBAGDTEST "/usr/local/bin/keybagdTest"
int cmp_zero(const void *p, size_t len)
{
const uint8_t *x = p;
while (len && (uintptr_t)x & 7) {
if (*x)
return 1;
++x;
--len;
}
const uint64_t *y = (uint64_t *)x;
while (len >= 8) {
if (*y)
return 1;
++y;
len -= 8;
}
x = (uint8_t *)y;
while (len) {
if (*x)
return 1;
++y;
--len;
}
return 0;
}
struct append_ctx {
int fd;
uint8_t digest[CC_MD5_DIGEST_LENGTH];
bool done;
};
#if 1
static const int append_test_amount = 128 MB;
#else
#warning
static const int append_test_amount = 1 MB;
#endif
void *append_to_file(void *param)
{
struct append_ctx *ctx = param;
CC_MD5_CTX md5_ctx;
CC_MD5_Init(&md5_ctx);
uint64_t total = 0;
void *p = mmap(NULL, append_test_amount, PROT_READ | PROT_WRITE,
MAP_SHARED, ctx->fd, 0);
assert(p != MAP_FAILED);
int page_size = getpagesize();
while (total < append_test_amount) {
size_t todo = random() % (1 MB) + 1;
if (todo > append_test_amount - total)
todo = append_test_amount - total;
check_io(write(ctx->fd, buf1, todo), todo);
int round = ((uintptr_t)p + total) % page_size;
assert_no_err(msync(p + total - round, todo + round,
MS_ASYNC | MS_INVALIDATE));
CC_MD5_Update(&md5_ctx, buf1, todo);
total += todo;
}
CC_MD5_Final(ctx->digest, &md5_ctx);
OSMemoryBarrier();
ctx->done = true;
assert_no_err(munmap(p, append_test_amount));
return NULL;
}
static uint32_t os_version(void)
{
static uint32_t os_version;
if (os_version)
return os_version;
char os_version_str[128];
size_t size = sizeof(os_version_str);
assert_no_err(sysctlbyname("kern.osversion", os_version_str,
&size, NULL, 0));
const char *p = os_version_str;
int a = 0;
while (*p >= '0' && *p <= '9') {
a = a * 10 + *p - '0';
++p;
}
if (!a)
return 0;
int b = *p++;
if (!b)
return 0;
int c = 0;
while (*p >= '0' && *p <= '9') {
c = c * 10 + *p - '0';
++p;
}
if (!c)
return 0;
os_version = (a & 0xff) << 24 | b << 16 | (c & 0xffff);
return os_version;
}
static int block_size(void)
{
static int block_size;
if (!block_size) {
struct statfs sfs;
assert_no_err(statfs("/private/var", &sfs));
block_size = sfs.f_bsize;
}
return block_size;
}
static void fill_disk(int *fd, uint64_t *size)
{
assert_with_errno((*fd = open(KEY_ROLL_FILL_DISK_FILE,
O_CREAT | O_RDWR | O_TRUNC, 0666)) >= 0);
struct statfs sfs;
assert_no_err(fstatfs(*fd, &sfs));
uint32_t blocks = sfs.f_bfree;
for (;;) {
uint64_t size = (uint64_t)blocks * sfs.f_bsize;
if (!fcntl(*fd, F_SETSIZE, &size))
break;
assert_with_errno(errno == ENOSPC);
blocks /= 2;
}
uint32_t upper = sfs.f_bfree + 128;
for (;;) {
uint32_t try = (upper + blocks) / 2;
if (try <= blocks)
try = blocks + 1;
uint64_t size = (uint64_t)try * sfs.f_bsize;
if (!fcntl(*fd, F_SETSIZE, &size)) {
blocks = try;
if (try >= upper) {
assert_no_err(fstatfs(*fd, &sfs));
upper = try + sfs.f_bfree + 128;
}
} else {
assert(errno == ENOSPC);
if (try == blocks + 1)
break;
else
upper = try;
}
}
*size = (uint64_t)blocks * sfs.f_bsize;
}
volatile int32_t threads_running;
static void *roll_thread(void *arg __attribute__((unused)))
{
int fd;
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE, O_RDWR)) >= 0);
hfs_key_roll_args_t args = {
.api_version = HFS_KR_API_LATEST_VERSION,
.operation = HFS_KR_OP_STEP,
};
for (int i = 0; i < 100; ++i) {
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
if (args.done == -1)
args.operation = HFS_KR_OP_START;
else
args.operation = HFS_KR_OP_STEP;
}
assert_no_err(close(fd));
OSAtomicDecrement32(&threads_running);
return NULL;
}
int run_key_roll(__unused test_ctx_t *ctx)
{
struct statfs sfs;
assert(statfs("/tmp", &sfs) == 0);
if (strcmp(sfs.f_fstypename, "hfs")) {
printf("key_roll needs hfs as root file system - skipping.\n");
return 0;
}
int fd;
void *read_buf = malloc(2 MB);
void *p;
struct attrlist attrlist = {
.bitmapcount = ATTR_BIT_MAP_COUNT,
.commonattr = ATTR_CMN_DATA_PROTECT_FLAGS,
};
struct attrs {
uint32_t len;
uint32_t dp_flags;
} attrs;
unlink(KEY_ROLL_TEST_FILE_2);
unlink(KEY_ROLL_FILL_DISK_FILE);
unlink(KEY_ROLL_TEST_FILE);
systemx("/bin/rm", "-rf", KEY_ROLL_TEST_DIR, NULL);
buf1 = malloc(1 MB);
memset(buf1, 0x25, 1 MB);
void *buf2 = malloc(1 MB);
memset(buf2, 0x49, 1 MB);
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
O_CREAT | O_RDWR | O_TRUNC, 0666)) >= 0);
check_io(write(fd, buf1, 1 MB), 1 MB);
check_io(write(fd, buf1, 1 MB), 1 MB);
check_io(write(fd, buf1, 1 MB), 1 MB);
hfs_key_roll_args_t args = {
.api_version = HFS_KR_API_LATEST_VERSION,
.operation = HFS_KR_OP_START,
};
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert_no_err(unlink(KEY_ROLL_TEST_FILE));
assert_no_err(close(fd));
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
O_CREAT | O_RDWR | O_TRUNC, 0666)) >= 0);
check_io(write(fd, buf1, 1 MB), 1 MB);
check_io(write(fd, buf1, 1 MB), 1 MB);
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.key_revision == 1
&& args.key_os_version == os_version()
&& args.done == -1
&& args.total == 2 MB);
assert_no_err(ftruncate(fd, 3 MB));
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert((args.key_revision & 0xff00) == 0x0100 && args.done == 0
&& args.total == 3 MB);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
off_t offset = 2 MB + 50000;
check_io(pwrite(fd, buf2, 1024, offset), 1024);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1);
assert_with_errno((p = mmap(NULL, 3 MB, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0)) != MAP_FAILED);
assert_no_err(msync(p, 3 MB, MS_INVALIDATE));
assert(!memcmp(p, buf1, 1 MB));
assert(!memcmp(p + 1 MB, buf1, 1 MB));
assert(!cmp_zero(p + 2 MB, 50000));
assert(!memcmp(p + offset, buf2, 1024));
assert(!cmp_zero(p + offset + 1024, 1 MB - 50000 - 1024));
assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
assert((attrs.dp_flags & 0x1f) == 4);
args.operation = HFS_KR_OP_START;
args.flags = HFS_KR_MATCH_KEY_REVISION;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
args.flags = 0;
assert(args.done == 0 && (args.key_revision & 0xff00) == 0x0200);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
assert_no_err(fcntl(fd, F_SETPROTECTIONCLASS, 3));
assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
assert((attrs.dp_flags & 0x1f) == 3);
bool release_build = false;
if (fcntl(fd, F_RECYCLE)) {
assert_equal_int(errno, ENOTTY);
release_build = true;
}
assert_no_err(close(fd));
assert_no_err(munmap(p, 3 MB));
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE, O_RDWR)) >= 0);
assert_with_errno((p = mmap(NULL, 3 MB, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0)) != MAP_FAILED);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
assert(!memcmp(p, buf1, 1 MB));
assert(!memcmp(p + 1 MB, buf1, 1 MB));
assert(!cmp_zero(p + 2 MB, 50000));
assert(!memcmp(p + offset, buf2, 1024));
assert(!cmp_zero(p + offset + 1024, 1 MB - 50000 - 1024));
assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
assert((attrs.dp_flags & 0x1f) == 3);
assert_no_err(fcntl(fd, F_SETPROTECTIONCLASS, 1));
assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
assert((attrs.dp_flags & 0x1f) == 1);
assert_with_errno(release_build || !fcntl(fd, F_RECYCLE));
assert_no_err(close(fd));
assert_no_err(munmap(p, 3 MB));
int fd2 = open(KEY_ROLL_TEST_FILE_2, O_RDWR | O_CREAT, 0666);
assert_no_err(systemx(KEYSTORECTL, "change-password", "", "1234", NULL));
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE, O_RDWR)) >= 0);
assert_with_errno((p = mmap(NULL, 3 MB, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0)) != MAP_FAILED);
assert(!memcmp(p, buf1, 1 MB));
int raw_fd;
assert_with_errno((raw_fd = open_dprotected_np(KEY_ROLL_TEST_FILE,
O_RDONLY, 0, 1, 0)) >= 0);
assert_no_err(systemx(KEYBAGDTEST, "lock", NULL));
while (MKBGetDeviceLockState(NULL) != kMobileKeyBagDeviceIsLocked)
sleep(1);
assert_no_err(fcntl(fd2, F_SETPROTECTIONCLASS, 2));
check_io(write(fd2, buf1, 1 MB), 1 MB);
assert_no_err(close(fd2));
assert_no_err(unlink(KEY_ROLL_TEST_FILE_2));
assert(read(fd, read_buf, 1 MB) == -1 && errno == EPERM);
args.operation = HFS_KR_OP_STEP;
assert(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0) == -1
&& errno == EPERM);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(read(raw_fd, read_buf, 1 MB) == -1 && errno == EPERM);
assert(open_dprotected_np(KEY_ROLL_TEST_FILE, O_RDONLY, 0, 1, 0)
== -1 && errno == EPERM);
assert_no_err(systemx(KEYSTORECTL, "unlock", "1234", NULL));
check_io(read(raw_fd, read_buf, 1 MB), 1 MB);
assert_no_err(close(raw_fd));
assert(!memcmp(p, buf1, 1 MB));
assert_no_err(systemx(KEYSTORECTL, "change-password", "1234", "", NULL));
assert_with_errno((raw_fd = open_dprotected_np(KEY_ROLL_TEST_FILE,
O_RDONLY, 0, 1, 0)) >= 0);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB && (args.key_revision & 0xff00) == 0x0200);
check_io(read(raw_fd, read_buf, 2 MB), 2 MB);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB && (args.key_revision & 0xff00) == 0x0200);
check_io(read(raw_fd, read_buf, 2 MB), 1 MB);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1 && (args.key_revision & 0xff00) == 0x0200);
assert_no_err(close(raw_fd));
if (release_build) {
hfs_key_auto_roll_args_t auto_roll_args = {
.api_version = HFS_KEY_AUTO_ROLL_API_LATEST_VERSION,
.max_key_os_version = os_version() + 1,
};
assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL, &auto_roll_args, 0));
} else {
args.operation = HFS_KR_OP_SET_INFO;
args.key_revision = 0x0200;
args.key_os_version = CP_OS_VERS_PRE_71;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1
&& args.key_revision == 0x0200
&& args.key_os_version == CP_OS_VERS_PRE_71);
hfs_key_auto_roll_args_t auto_roll_args = {
.api_version = HFS_KEY_AUTO_ROLL_API_LATEST_VERSION,
.max_key_os_version = CP_OS_VERS_71,
};
assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL, &auto_roll_args, 0));
}
assert_with_errno((raw_fd = open_dprotected_np(KEY_ROLL_TEST_FILE,
O_RDONLY, 0, 1, 0)) >= 0);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 0 && (args.key_revision & 0xff00) == 0x0300);
check_io(read(raw_fd, read_buf, 1 MB), 1 MB);
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB && (args.key_revision & 0xff00) == 0x0300
&& args.key_os_version == os_version());
{
struct log2phys l2p = {
.l2p_contigbytes = 1024 * 1024,
.l2p_devoffset = 2 MB - block_size(),
};
assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
assert(l2p.l2p_contigbytes == block_size());
fstore_t fstore = {
.fst_flags = F_ALLOCATECONTIG | F_ALLOCATEALL,
.fst_posmode = F_VOLPOSMODE,
.fst_offset = l2p.l2p_devoffset + block_size(),
.fst_length = 3 MB + block_size(),
};
assert_no_err(fcntl(fd, F_PREALLOCATE, &fstore));
assert_equal_ll(fstore.fst_bytesalloc, block_size());
check_io(pwrite(fd, buf1, block_size(), 3 MB), block_size());
l2p.l2p_devoffset = 3 MB;
l2p.l2p_contigbytes = 1 MB;
assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
assert(l2p.l2p_contigbytes == block_size());
if (l2p.l2p_devoffset == -1
|| (l2p.l2p_devoffset + block_size() > fstore.fst_offset
&& l2p.l2p_devoffset < fstore.fst_offset + 1 MB)) {
assert_fail("unexpected allocation (%lld, %lld)\n",
l2p.l2p_devoffset, fstore.fst_offset);
}
assert_no_err(ftruncate(fd, 3 MB));
}
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB && (args.key_revision & 0xff00) == 0x0300);
check_io(read(raw_fd, read_buf, 1 MB), 1 MB);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
check_io(read(raw_fd, read_buf, 2 MB), 1 MB);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1);
args.operation = HFS_KR_OP_START;
assert(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0) == -1
&& errno == EBUSY);
assert_no_err(close(raw_fd));
assert_with_errno((raw_fd =
open_dprotected_np("/private/var/mobile/Library/Passes",
O_RDONLY | O_NOFOLLOW,
0, 1, 0)) >= 0);
assert_no_err(close(raw_fd));
if (!release_build) {
assert_with_errno((raw_fd = open_dprotected_np(KEY_ROLL_TEST_FILE,
O_RDONLY, 0, 1, 0)) >= 0);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1 && (args.key_revision & 0xff00) == 0x0300
&& args.key_os_version == os_version());
assert_no_err(close(raw_fd));
}
hfs_key_auto_roll_args_t auto_roll_args = {
.api_version = HFS_KEY_AUTO_ROLL_API_LATEST_VERSION,
};
assert_no_err(fsctl("/private/var", HFSIOC_SET_KEY_AUTO_ROLL, &auto_roll_args, 0));
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 0 && (args.key_revision & 0xff00) == 0x0400);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
assert_no_err(ftruncate(fd, 1 MB));
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1);
assert_no_err(ftruncate(fd, 3 MB));
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 0 && (args.key_revision & 0xff00) == 0x0500);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
assert_no_err(unlink(KEY_ROLL_TEST_FILE));
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1);
assert_no_err(close(fd));
assert(!memcmp(p, buf1, 1 MB));
assert(!cmp_zero(p + 1 MB, 2 MB));
assert_no_err(munmap(p, 3 MB));
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
O_CREAT | O_RDWR, 0666)) >= 0);
for (int i = 0; i < 3; ++i) {
check_io(write(fd, buf1, 1 MB), 1 MB);
}
for (int i = 0; i < 3; ++i) {
assert_no_err(fsetxattr(fd, XATTR_RESOURCEFORK_NAME, buf1,
1 MB, i MB, 0));
}
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 0 && args.total == 6 MB);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 3 MB);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 5 MB);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1);
for (int i = 0; i < 3; ++i) {
check_io(fgetxattr(fd, XATTR_RESOURCEFORK_NAME, read_buf,
1 MB, i MB, 0), 1 MB);
assert(!memcmp(buf1, read_buf, 1 MB));
}
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 0 && args.total == 6 MB);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
assert_no_err(ftruncate(fd, 0));
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 0);
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
for (int i = 0; i < 3; ++i) {
check_io(fgetxattr(fd, XATTR_RESOURCEFORK_NAME, read_buf,
1 MB, i MB, 0), 1 MB);
assert(!memcmp(buf1, read_buf, 1 MB));
}
assert_no_err(fremovexattr(fd, XATTR_RESOURCEFORK_NAME, 0));
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1);
off_t len = 2 * block_size();
fstore_t fstore = {
.fst_flags = F_ALLOCATECONTIG | F_ALLOCATEALL,
.fst_posmode = F_PEOFPOSMODE,
.fst_offset = 0,
.fst_length = len,
};
assert_no_err(fcntl(fd, F_PREALLOCATE, &fstore));
fstore.fst_posmode = F_VOLPOSMODE;
for (int i = 0; i < 16384; ++i) {
struct log2phys l2p = {
.l2p_contigbytes = block_size(),
.l2p_devoffset = len - block_size(),
};
assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
len += block_size();
fstore.fst_offset = l2p.l2p_devoffset + 2 * block_size();
fstore.fst_length = len;
assert_no_err(fcntl(fd, F_PREALLOCATE, &fstore));
}
assert_no_err(ftruncate(fd, len));
uint64_t size;
fill_disk(&fd2, &size);
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
off_t start = 0, done = 0, decr = block_size();
for (;;) {
args.operation = HFS_KR_OP_STEP;
if (!ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0)) {
done += 2 MB;
assert_equal_ll(args.done, done);
break;
}
assert_with_errno(errno == ENOSPC);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
done = args.done;
if (done > start + 2 MB)
break;
size -= decr;
assert_no_err(fcntl(fd2, F_SETSIZE, &size));
decr += block_size();
}
assert_no_err(ftruncate(fd2, 0));
assert_no_err(close(fd2));
assert_no_err(unlink(KEY_ROLL_FILL_DISK_FILE));
args.operation = HFS_KR_OP_STEP;
while (args.done != -1)
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert_equal_ll(args.done, 2 MB);
struct log2phys l2p = {
.l2p_contigbytes = 16 MB,
.l2p_devoffset = 0,
};
assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
if (l2p.l2p_contigbytes < 2 MB - 7 * block_size()) {
assert_fail("extent smaller than expected: %llu\n",
l2p.l2p_contigbytes);
}
{
fstore_t fstore = {
.fst_flags = F_ALLOCATECONTIG | F_ALLOCATEALL,
.fst_posmode = F_VOLPOSMODE,
.fst_offset = l2p.l2p_devoffset,
.fst_length = len + block_size(),
};
assert_no_err(fcntl(fd, F_PREALLOCATE, &fstore));
assert(fstore.fst_bytesalloc == block_size());
}
check_io(pwrite(fd, buf1, block_size(), len), block_size());
struct log2phys l2p2 = {
.l2p_contigbytes = 1 MB,
.l2p_devoffset = len,
};
assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p2));
assert(l2p2.l2p_contigbytes == block_size());
if (l2p2.l2p_devoffset == -1
|| (l2p2.l2p_devoffset + block_size() > l2p.l2p_devoffset
&& l2p2.l2p_devoffset < l2p.l2p_devoffset + len)) {
assert_fail("unexpected allocation: %llu (reserved: %llu-%llu)",
l2p2.l2p_devoffset, l2p.l2p_devoffset,
l2p.l2p_devoffset + len - done);
}
assert_no_err(ftruncate(fd, len));
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 2 MB);
fill_disk(&fd2, &size);
start = done = 2 MB;
decr = block_size();
for (;;) {
args.operation = HFS_KR_OP_STEP;
if (!ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0)) {
done += 2 MB;
assert_equal_ll(args.done, done);
break;
}
assert_with_errno(errno == ENOSPC);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
done = args.done;
if (done > start + 2 MB)
break;
size -= decr;
assert_no_err(fcntl(fd2, F_SETSIZE, &size));
decr += block_size();
}
assert_no_err(ftruncate(fd2, 0));
assert_no_err(close(fd2));
assert_no_err(unlink(KEY_ROLL_FILL_DISK_FILE));
args.operation = HFS_KR_OP_STEP;
do {
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
} while (args.done != -1);
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
args.operation = HFS_KR_OP_STEP;
do {
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
} while (args.done != -1);
l2p.l2p_contigbytes = UINT32_MAX;
l2p.l2p_devoffset = 0;
assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p));
assert_equal_ll(l2p.l2p_contigbytes, len);
assert_no_err(close(fd));
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
O_CREAT | O_RDWR | O_TRUNC, 0666)) >= 0);
struct append_ctx actx = {
.fd = fd,
};
pthread_t thread;
assert_no_err(pthread_create(&thread, NULL, append_to_file, &actx));
while (!actx.done) {
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
do {
args.operation = HFS_KR_OP_STEP;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
} while (args.done != -1);
}
assert_with_errno((p = mmap(NULL, append_test_amount, PROT_READ,
MAP_SHARED, fd, 0)) != MAP_FAILED);
assert_no_err(msync(p, append_test_amount, MS_INVALIDATE));
CC_MD5_CTX md5_ctx;
CC_MD5_Init(&md5_ctx);
CC_MD5_Update(&md5_ctx, p, append_test_amount);
uint8_t digest[CC_MD5_DIGEST_LENGTH];
CC_MD5_Final(digest, &md5_ctx);
OSMemoryBarrier();
assert(!memcmp(digest, actx.digest, CC_MD5_DIGEST_LENGTH));
assert_no_err(munmap(p, append_test_amount));
assert_no_err(close(fd));
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE,
O_RDWR, 0666)) >= 0);
pthread_t threads[2];
threads_running = 2;
pthread_create(&threads[0], NULL, roll_thread, NULL);
pthread_create(&threads[0], NULL, roll_thread, NULL);
assert_with_errno((p = mmap(NULL, append_test_amount, PROT_READ,
MAP_SHARED, fd, 0)) != MAP_FAILED);
bool finished = false;
do {
if (!threads_running)
finished = true;
assert_no_err(msync(p, append_test_amount, MS_INVALIDATE));
CC_MD5_CTX md5_ctx;
CC_MD5_Init(&md5_ctx);
CC_MD5_Update(&md5_ctx, p, append_test_amount);
uint8_t digest[CC_MD5_DIGEST_LENGTH];
CC_MD5_Final(digest, &md5_ctx);
assert(!memcmp(digest, actx.digest, CC_MD5_DIGEST_LENGTH));
} while (!finished);
pthread_join(threads[0], NULL);
pthread_join(threads[1], NULL);
assert_with_errno((fd = open(KEY_ROLL_TEST_FILE, O_RDWR, 0666)) >= 0);
args.operation = HFS_KR_OP_STEP;
do {
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
} while (args.done != -1);
assert(fcntl(fd, F_SETPROTECTIONCLASS, 6) == -1 && errno == EINVAL);
args.operation = HFS_KR_OP_START;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == 0);
assert_no_err(ftruncate(fd, 0));
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1);
assert_no_err(fcntl(fd, F_SETPROTECTIONCLASS, 6));
args.operation = HFS_KR_OP_START;
assert(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0) == -1 && errno == ENOTSUP);
assert_no_err(close(fd));
assert_no_err(mkdir("/tmp/key-roll-test.dir", 0777));
assert_no_err(getattrlist("/tmp/key-roll-test.dir", &attrlist, &attrs,
sizeof(attrs), 0));
assert((attrs.dp_flags & 0x1f) == 4);
assert_with_errno((fd = open("/tmp/key-roll-test.dir/file1",
O_RDWR | O_TRUNC | O_CREAT, 0666)) >= 0);
assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
assert((attrs.dp_flags & 0x1f) == 4);
assert_with_errno((fd = open("/tmp/key-roll-test.dir", O_RDONLY)) >= 0);
assert_no_err(fcntl(fd, F_SETPROTECTIONCLASS, 3));
assert_no_err(close(fd));
assert_with_errno((fd = open("/tmp/key-roll-test.dir/file2",
O_RDWR | O_TRUNC | O_CREAT, 0666)) >= 0);
assert_no_err(fgetattrlist(fd, &attrlist, &attrs, sizeof(attrs), 0));
assert((attrs.dp_flags & 0x1f) == 3);
assert_no_err(close(fd));
args.operation = HFS_KR_OP_START;
assert(fsctl("/tmp/key-roll-test.dir", HFSIOC_KEY_ROLL, &args, 0) == -1
&& errno == ENOTSUP);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(fsctl("/tmp/key-roll-test.dir", HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1 && args.total == 0);
unlink(KEY_ROLL_SYM_LINK);
assert_no_err(symlink("/tmp/key-roll-test.dir/file2",
KEY_ROLL_SYM_LINK));
assert_with_errno((fd = open(KEY_ROLL_SYM_LINK,
O_RDONLY | O_SYMLINK)) >= 0);
args.operation = HFS_KR_OP_START;
assert(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0) == -1 && errno == ENOTSUP);
args.operation = HFS_KR_OP_STATUS;
assert_no_err(ffsctl(fd, HFSIOC_KEY_ROLL, &args, 0));
assert(args.done == -1 && args.total == 0);
assert_no_err(close(fd));
unlink(KEY_ROLL_TEST_FILE);
unlink(KEY_ROLL_SYM_LINK);
systemx("/bin/rm", "-rf", KEY_ROLL_TEST_DIR, NULL);
return 0;
}
#endif // TARGET_OS_EMBEDDED