#include "tests.h" #include #include #include #include #include #include #include #include /* Note that this test (due to the need to lock/unlock the device on demand, and the need to manipulate the passcode) has the unfortunate effect of link xnu_quick_test to the IOKit Framework. */ /* TODO: Change the test to use a single cleanup label. */ #define CPT_IO_SIZE 4096 #define CPT_AKS_BUF_SIZE 256 #define CPT_MAX_PASS_LEN 64 #define GET_PROT_CLASS(fd) fcntl((fd), F_GETPROTECTIONCLASS) #define SET_PROT_CLASS(fd, prot_class) fcntl((fd), F_SETPROTECTIONCLASS, (prot_class)) #define PRINT_LOCK_FAIL printf("%s, line %d: failed to lock the device.\n", cpt_fail_header, __LINE__); #define PRINT_UNLOCK_FAIL printf("%s, line %d: failed to unlock the device.\n", cpt_fail_header, __LINE__); extern char g_target_path[PATH_MAX]; char * cpt_fail_header = "Content protection test failed"; char * keystorectl_path = "/usr/local/bin/keystorectl"; /* Shamelessly ripped from keystorectl routines; a wrapper for invoking the AKS user client. */ int apple_key_store(uint32_t command, uint64_t * inputs, uint32_t input_count, void * input_structs, size_t input_struct_count, uint64_t * outputs, uint32_t * output_count) { int result = -1; io_connect_t connection = IO_OBJECT_NULL; io_registry_entry_t apple_key_bag_service = IO_OBJECT_NULL; kern_return_t k_result = KERN_FAILURE; IOReturn io_result = IO_OBJECT_NULL; apple_key_bag_service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kAppleKeyStoreServiceName)); if (apple_key_bag_service == IO_OBJECT_NULL) { printf("FAILURE: failed to match kAppleKeyStoreServiceName.\n"); goto end; } k_result = IOServiceOpen(apple_key_bag_service, mach_task_self(), 0, &connection); if (k_result != KERN_SUCCESS) { printf("FAILURE: failed to open AppleKeyStore.\n"); goto end; } k_result = IOConnectCallMethod(connection, kAppleKeyStoreUserClientOpen, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL); if (k_result != KERN_SUCCESS) { printf("FAILURE: call to AppleKeyStore method kAppleKeyStoreUserClientOpen failed.\n"); goto close; } io_result = IOConnectCallMethod(connection, command, inputs, input_count, input_structs, input_struct_count, outputs, output_count, NULL, NULL); if (io_result != kIOReturnSuccess) { printf("FAILURE: call to AppleKeyStore method %d failed.\n", command); goto close; } result = 0; close: IOServiceClose(apple_key_bag_service); end: return(result); } #ifndef KEYBAG_ENTITLEMENTS /* Just a wrapper around forking to exec keystorectl for commands requiring entitlements. */ int keystorectl(char * const command[]) { int child_result = -1; int result = -1; pid_t child = -1; child = fork(); if (child == -1) { printf("FAILURE: failed to fork.\n"); goto end; } else if (child == 0) { /* TODO: This keeps keystorectl from bombarding us with key state changes, but there must be a better way of doing this; killing stderr is a bit nasty, and if keystorectl fails, we want all the information we can get. */ fclose(stderr); fclose(stdin); execv(keystorectl_path, command); printf("FAILURE: child failed to execv keystorectl, errno = %s.\n", strerror(errno)); exit(EXIT_FAILURE); } if ((waitpid(child, &child_result, 0) != child) || WEXITSTATUS(child_result)) { printf("FAILURE: keystorectl failed.\n"); result = -1; } else { result = 0; } end: return(result); } #endif /* KEYBAG_ENTITLEMENTS */ /* Code based on Mobile Key Bag; specifically MKBDeviceSupportsContentProtection and MKBDeviceFormattedForContentProtection. */ /* We want to verify that we support content protection, and that we are formatted for it. */ int supports_content_prot() { int local_result = -1; int result = -1; uint32_t buffer_size = 1; char buffer[buffer_size]; io_registry_entry_t defaults = IO_OBJECT_NULL; kern_return_t k_result = KERN_FAILURE; struct statfs statfs_results; defaults = IORegistryEntryFromPath(kIOMasterPortDefault, kIODeviceTreePlane ":/defaults"); if (defaults == IO_OBJECT_NULL) { printf("FAILURE: failed to find defaults registry entry.\n"); goto end; } k_result = IORegistryEntryGetProperty(defaults, "content-protect", buffer, &buffer_size); if (k_result != KERN_SUCCESS) { /* This isn't a failure; it means the entry doesn't exist, so we assume CP is unsupported. */ result = 0; goto end; } /* At this point, we SUPPORT content protection... but are we formatted for it? */ /* This is ugly; we should be testing the file system we'll be testing in, not just /tmp/. */ local_result = statfs(g_target_path, &statfs_results); if (local_result == -1) { printf("FAILURE: failed to statfs the test directory, errno = %s.\n", strerror(errno)); } else if (statfs_results.f_flags & MNT_CPROTECT) { result = 1; } else { /* This isn't a failure, it means the filesystem isn't formatted for CP. */ result = 0; } end: return(result); } #if 0 int device_lock_state() { /* TODO: Actually implement this. */ /* We fail if a passcode already exists, and the methods being used to lock/unlock the device in this test appear to be synchronous... do we need this function? */ int result = -1; return(result); } #endif int lock_device() { int result = -1; #ifdef KEYBAG_ENTITLEMENTS /* If we're entitled, we can lock the device ourselves. */ uint64_t inputs[] = {device_keybag_handle}; uint32_t input_count = (sizeof(inputs) / sizeof(*inputs)); result = apple_key_store(kAppleKeyStoreKeyBagLock, inputs, input_count, NULL, 0, NULL, NULL); #else /* If we aren't entitled, we'll need to use keystorectl to lock the device. */ /* keystorectl seems to have a bus error (though it locks successfully) unless lock is passed an argument, so we'll also pass it the empty string. */ char * const keystorectl_args[] = {keystorectl_path, "lock", "", NULL}; result = keystorectl(keystorectl_args); #endif /* KEYBAG_ENTITLEMENTS */ return(result); } int unlock_device(char * passcode) { int result = -1; #ifdef KEYBAG_ENTITLEMENTS /* If we're entitled, we can unlock the device ourselves. */ uint64_t inputs[] = {device_keybag_handle}; uint32_t input_count = (sizeof(inputs) / sizeof(*inputs)); size_t input_struct_count = 0; if ((passcode == NULL) || ((input_struct_count = strnlen(passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN)) { passcode = ""; input_struct_count = 0; } result = apple_key_store(kAppleKeyStoreKeyBagUnlock, inputs, input_count, passcode, input_struct_count, NULL, NULL); #else /* If we aren't entitled, we'll need to use keystorectl to unlock the device. */ if ((passcode == NULL) || (strnlen(passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)) { passcode = ""; } char * const keystorectl_args[] = {keystorectl_path, "unlock", passcode, NULL}; result = keystorectl(keystorectl_args); #endif /* KEYBAG_ENTITLEMENTS */ return(result); } int set_passcode(char * new_passcode, char * old_passcode) { int result = -1; #ifdef KEYBAG_ENTITLEMENTS /* If we're entitled, we can set the passcode ourselves. */ uint64_t inputs[] = {device_keybag_handle}; uint32_t input_count = (sizeof(inputs) / sizeof(*inputs)); void * input_structs = NULL; size_t input_struct_count = 0; char buffer[CPT_AKS_BUF_SIZE]; char * buffer_ptr = buffer; uint32_t old_passcode_len = 0; uint32_t new_passcode_len = 0; if ((old_passcode == NULL) || ((old_passcode_len = strnlen(old_passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN)) { old_passcode = ""; old_passcode_len = 0; } if ((new_passcode == NULL) || ((new_passcode_len = strnlen(new_passcode, CPT_MAX_PASS_LEN)) == CPT_MAX_PASS_LEN)) { new_passcode = ""; new_passcode_len = 0; } *((uint32_t *) buffer_ptr) = ((uint32_t) 2); buffer_ptr += sizeof(uint32_t); *((uint32_t *) buffer_ptr) = old_passcode_len; buffer_ptr += sizeof(uint32_t); memcpy(buffer_ptr, old_passcode, old_passcode_len); buffer_ptr += ((old_passcode_len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1)); *((uint32_t *) buffer_ptr) = new_passcode_len; buffer_ptr += sizeof(uint32_t); memcpy(buffer_ptr, new_passcode, new_passcode_len); buffer_ptr += ((new_passcode_len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1)); input_structs = buffer; input_struct_count = (buffer_ptr - buffer); result = apple_key_store(kAppleKeyStoreKeyBagSetPasscode, inputs, input_count, input_structs, input_struct_count, NULL, NULL); #else /* If we aren't entitled, we'll need to use keystorectl to set the passcode. */ if ((old_passcode == NULL) || (strnlen(old_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)) { old_passcode = ""; } if ((new_passcode == NULL) || (strnlen(new_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)) { new_passcode = ""; } char * const keystorectl_args[] = {keystorectl_path, "change-password", old_passcode, new_passcode, NULL}; result = keystorectl(keystorectl_args); #endif /* KEYBAG_ENTITLEMENTS */ return(result); } int clear_passcode(char * passcode) { /* For the moment, this will set the passcode to the empty string (a known value); this will most likely need to change, or running this test may ruin everything(tm). */ int result = -1; result = set_passcode(NULL, passcode); return(result); } #if 0 /* Determines if we will try to test class C semanatics. */ int unlocked_since_boot() { /* TODO: Actually implement this. */ /* The actual semantics for CP mean that even with this primative, we would need set a passcode and then reboot the device in order to test this; this function will probably be rather worthless as a result. */ int result = 1; return(result); } #endif /* If the device has a passcode when we want to test it, things are going to go wrong. As such, we'll assume the device never has a passcode. No, not even then. Or we could just try "" to ""; it works. */ int has_passcode() { int result = -1; result = set_passcode(NULL, NULL); return(result); } int content_protection_test(void * argp) { #pragma unused (argp) int init_result = 0; int local_result = -1; int test_result = -1; int fd = -1; int dir_fd = -1; int subdir_fd = -1; int new_prot_class = -1; int old_prot_class = -1; int current_byte = 0; char filepath[PATH_MAX]; char dirpath[PATH_MAX]; char subdirpath[PATH_MAX]; char rd_buffer[CPT_IO_SIZE]; char wr_buffer[CPT_IO_SIZE]; char * passcode = "IAmASecurePassword"; /* Do some initial setup (names). */ bzero(filepath, PATH_MAX); bzero(dirpath, PATH_MAX); bzero(subdirpath, PATH_MAX); /* This is just easier than checking each result individually. */ init_result |= (strlcat(filepath, g_target_path, PATH_MAX) == PATH_MAX); init_result |= (strlcat(filepath, "/", PATH_MAX) == PATH_MAX); init_result |= (strlcpy(dirpath, filepath, PATH_MAX) == PATH_MAX); init_result |= (strlcat(filepath, "cpt_test_file", PATH_MAX) == PATH_MAX); init_result |= (strlcat(dirpath, "cpt_test_dir/", PATH_MAX) == PATH_MAX); init_result |= (strlcpy(subdirpath, dirpath, PATH_MAX) == PATH_MAX); init_result |= (strlcat(subdirpath, "cpt_test_subdir/", PATH_MAX) == PATH_MAX); if (init_result) { /* If any of the initialization failed, we're just going to fail now. */ printf("%s, line %d: failed to initialize test strings.\n", cpt_fail_header, __LINE__); goto end; } local_result = supports_content_prot(); if (local_result == -1) { printf("%s, line %d: failed to determine if content protection is supported.\n", cpt_fail_header, __LINE__); goto end; } else if (local_result == 0) { /* If we don't support content protection at the moment, pass the test. */ printf("This device does not support or is not formatted for content protection.\n"); test_result = 0; goto end; } /* If we support content protection, we'll need to be able to set the passcode. */ local_result = has_passcode(); if (local_result == -1) { printf("%s, line %d: the device appears to have a passcode.\n", cpt_fail_header, __LINE__); goto end; } if (set_passcode(passcode, NULL)) { printf("%s, line %d: failed to set a new passcode.\n", cpt_fail_header, __LINE__); goto end; } fd = open(filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777); if (fd == -1) { printf("%s, line %d: failed to create the test file, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto remove_passcode; } /* Ensure we can freely read and change protection classes when unlocked. */ for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_F; new_prot_class++) { old_prot_class = GET_PROT_CLASS(fd); if (old_prot_class == -1) { printf("%s, line %d: failed to get protection class when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } if (SET_PROT_CLASS(fd, new_prot_class)) { printf("%s, line %d: failed to change protection class from %d to %d during unlock, errno = %s.\n", cpt_fail_header, __LINE__, old_prot_class, new_prot_class, strerror(errno)); goto cleanup_file; } } /* Query the filesystem for the default CP level (Is it C?) */ #ifndef F_GETDEFAULTPROTLEVEL #define F_GETDEFAULTPROTLEVEL 79 #endif old_prot_class = fcntl(fd, F_GETDEFAULTPROTLEVEL); if (old_prot_class == -1) { printf("%s , line %d: failed to acquire default protection level for filesystem , errno = %s \n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } /* XXX: Do we want to do anything with the level? What should it be? */ /* * files are allowed to move into F, but not out of it. They can also only do so * when they do not have content. */ close (fd); unlink (filepath); /* re-create the file */ fd = open (filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC); if (fd == -1) { printf("%s, line %d: failed to create the test file, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } /* Try making a class A file while locked. */ if (lock_device()) { PRINT_LOCK_FAIL; goto cleanup_file; } if (!SET_PROT_CLASS(fd, PROTECTION_CLASS_A)) { printf("%s, line %d: was able to change protection class from D to A when locked.\n", cpt_fail_header, __LINE__); goto cleanup_file; } if (unlock_device(passcode)) { PRINT_UNLOCK_FAIL; goto cleanup_file; } /* Attempt opening/IO to a class A file while unlocked. */ if (SET_PROT_CLASS(fd, PROTECTION_CLASS_A)) { printf("%s, line %d: failed to change protection class from D to A when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } close(fd); fd = open(filepath, O_RDWR | O_CLOEXEC); if (fd == -1) { printf("%s, line %d: failed to open a class A file when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto remove_file; } /* TODO: Write specific data we can check for. If we're going to do that, the write scheme should be deliberately ugly. */ current_byte = 0; while (current_byte < CPT_IO_SIZE) { local_result = pwrite(fd, &wr_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte); if (local_result == -1) { printf("%s, line %d: failed to write to class A file when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } current_byte += local_result; } current_byte = 0; while (current_byte < CPT_IO_SIZE) { local_result = pread(fd, &rd_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte); if (local_result == -1) { printf("%s, line %d: failed to read from class A file when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } current_byte += local_result; } /* Again, but now while locked; and try to change the file class as well. */ if (lock_device()) { PRINT_LOCK_FAIL; goto cleanup_file; } if (pread(fd, rd_buffer, CPT_IO_SIZE, 0) > 0) { printf("%s, line %d: was able to read from a class A file when locked.\n", cpt_fail_header, __LINE__); goto cleanup_file; } if (pwrite(fd, wr_buffer, CPT_IO_SIZE, 0) > 0) { printf("%s, line %d: was able to write to a class A file when locked.\n", cpt_fail_header, __LINE__); goto cleanup_file; } if (!SET_PROT_CLASS(fd, PROTECTION_CLASS_D)) { printf("%s, line %d: was able to change protection class from A to D when locked.\n", cpt_fail_header, __LINE__); goto cleanup_file; } /* Try to open and truncate the file. */ close(fd); fd = open(filepath, O_RDWR | O_TRUNC | O_CLOEXEC); if (fd != -1) { printf("%s, line %d: was able to open and truncate a class A file when locked.\n", cpt_fail_header, __LINE__); goto cleanup_file; } /* Try to open the file */ fd = open(filepath, O_RDWR | O_CLOEXEC); if (fd != -1) { printf("%s, line %d: was able to open a class A file when locked.\n", cpt_fail_header, __LINE__); goto cleanup_file; } /* What about class B files? */ if (unlock_device(passcode)) { PRINT_UNLOCK_FAIL; goto cleanup_file; } fd = open(filepath, O_RDWR | O_CLOEXEC); if (fd == -1) { printf("%s, line %d: was unable to open a class A file when unlocked.\n", cpt_fail_header, __LINE__); goto cleanup_file; } if (SET_PROT_CLASS(fd, PROTECTION_CLASS_D)) { printf("%s, line %d: failed to change protection class from A to D when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } if (lock_device()) { PRINT_LOCK_FAIL; goto cleanup_file; } /* Can we create a class B file while locked? */ if (SET_PROT_CLASS(fd, PROTECTION_CLASS_B)) { printf("%s, line %d: failed to change protection class from D to B when locked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } if (GET_PROT_CLASS (fd) != PROTECTION_CLASS_B) { printf("%s, line %d: Failed to switch to class B file \n", cpt_fail_header, __LINE__ ); goto cleanup_file; } /* We should also be able to read/write to the file descriptor while it is open. */ current_byte = 0; while (current_byte < CPT_IO_SIZE) { local_result = pwrite(fd, &wr_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte); if (local_result == -1) { printf("%s, line %d: failed to write to new class B file when locked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } current_byte += local_result; } current_byte = 0; while (current_byte < CPT_IO_SIZE) { local_result = pread(fd, &rd_buffer[current_byte], CPT_IO_SIZE - current_byte, current_byte); if (local_result == -1) { printf("%s, line %d: failed to read from new class B file when locked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } current_byte += local_result; } /* We should not be able to open a class B file under lock. */ close(fd); fd = open(filepath, O_RDWR | O_CLOEXEC); if (fd != -1) { printf("%s, line %d: was able to open a class B file when locked.\n", cpt_fail_header, __LINE__); goto cleanup_file; } unlink(filepath); /* We still need to test directory semantics. */ if (mkdir(dirpath, 0x0777) == -1) { printf("%s, line %d: failed to create a new directory when locked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto remove_passcode; } /* The newly created directory should not have a protection class. */ dir_fd = open(dirpath, O_RDONLY | O_CLOEXEC); if (dir_fd == -1) { printf("%s, line %d: failed to open an unclassed directory when locked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto remove_dir; } if ((GET_PROT_CLASS(dir_fd) != PROTECTION_CLASS_D) && (GET_PROT_CLASS(dir_fd) != PROTECTION_CLASS_DIR_NONE)) { printf("%s, line %d: newly created directory had a non-D and non-NONE protection class.\n", cpt_fail_header, __LINE__); goto cleanup_dir; } if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_A)) { printf("%s, line %d: was unable to change a directory from class D to class A during lock.\n", cpt_fail_header, __LINE__); goto cleanup_dir; } if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_D)) { printf("%s, line %d: failed to change a directory from class A to class D during lock, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_dir; } /* Do all files created in the directory properly inherit the directory's protection class? */ if ((strlcpy(filepath, dirpath, PATH_MAX) == PATH_MAX) || (strlcat(filepath, "cpt_test_file", PATH_MAX) == PATH_MAX)) { printf("%s, line %d: failed to construct the path for a file in the directory.\n", cpt_fail_header, __LINE__); goto cleanup_dir; } if (unlock_device(passcode)) { PRINT_UNLOCK_FAIL; goto cleanup_dir; } for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_D; new_prot_class++) { int getclass_dir; old_prot_class = GET_PROT_CLASS(dir_fd); if (old_prot_class == -1) { printf("%s, line %d: failed to get the protection class for the directory, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_dir; } if (SET_PROT_CLASS(dir_fd, new_prot_class)) { printf("%s, line %d: failed to change the protection class for the directory from %d to %d, errno = %s.\n", cpt_fail_header, __LINE__, old_prot_class, new_prot_class, strerror(errno)); goto cleanup_dir; } getclass_dir = GET_PROT_CLASS(dir_fd); if (getclass_dir != new_prot_class) { printf("%s, line %d: failed to get the new protection class for the directory %d (got %d) \n", cpt_fail_header, __LINE__, new_prot_class, getclass_dir); goto cleanup_dir; } fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777); if (fd == -1) { printf("%s, line %d: failed to create a file in a class %d directory when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); goto cleanup_dir; } local_result = GET_PROT_CLASS(fd); if (local_result == -1) { printf("%s, line %d: failed to get the new file's protection class, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } else if (local_result != new_prot_class) { printf("%s, line %d: new file (%d) did not inherit the directory's protection class (%d) .\n", cpt_fail_header, __LINE__, local_result, new_prot_class); goto cleanup_file; } close(fd); unlink(filepath); } /* Do we disallow creation of a class F directory? */ if (!SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_F)) { printf("%s, line %d: creation of a class F directory did not fail as expected.\n", cpt_fail_header, __LINE__); goto cleanup_dir; } /* And are class A and class B semantics followed for when we create these files during lock? */ if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_A)) { printf("%s, line %d: failed to change directory class from F to A when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_dir; } if (lock_device()) { PRINT_LOCK_FAIL; goto cleanup_dir; } fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777); if (fd != -1) { printf("%s, line %d: was able to create a new file in a class A directory when locked.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } if (unlock_device(passcode)) { PRINT_UNLOCK_FAIL; goto cleanup_dir; } if (SET_PROT_CLASS(dir_fd, PROTECTION_CLASS_B)) { printf("%s, line %d: failed to change directory class from A to B when unlocked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_dir; } if (lock_device()) { PRINT_LOCK_FAIL; goto cleanup_dir; } fd = open(filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777); if (fd == -1) { printf("%s, line %d: failed to create new file in class B directory when locked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_dir; } local_result = GET_PROT_CLASS(fd); if (local_result == -1) { printf("%s, line %d: failed to get protection class for a new file when locked, errno = %s.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } else if (local_result != PROTECTION_CLASS_B) { printf("%s, line %d: new file in class B directory did not inherit protection class.\n", cpt_fail_header, __LINE__, strerror(errno)); goto cleanup_file; } /* What happens when we try to create new subdirectories? */ if (unlock_device(passcode)) { PRINT_UNLOCK_FAIL; goto cleanup_file; } for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_D; new_prot_class++) { if (SET_PROT_CLASS(dir_fd, new_prot_class)) { printf("%s, line %d: failed to change directory to class %d, errno = %s.\n", cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); goto cleanup_file; } local_result = mkdir(subdirpath, 0x0777); if (local_result == -1) { printf("%s, line %d: failed to create subdirectory in class %d directory, errno = %s.\n", cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); goto cleanup_file; } subdir_fd = open(subdirpath, O_RDONLY | O_CLOEXEC); if (subdir_fd == -1) { printf("%s, line %d: failed to open subdirectory in class %d directory, errno = %s.\n", cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); goto remove_subdir; } local_result = GET_PROT_CLASS(subdir_fd); if (local_result == -1) { printf("%s, line %d: failed to get class of new subdirectory of class %d directory, errno = %s.\n", cpt_fail_header, __LINE__, new_prot_class, strerror(errno)); goto cleanup_subdir; } else if (local_result != new_prot_class) { printf("%s, line %d: new subdirectory had different class than class %d parent.\n", cpt_fail_header, __LINE__, new_prot_class); goto cleanup_subdir; } close(subdir_fd); rmdir(subdirpath); } /* If we've made it this far, the test was successful. */ test_result = 0; cleanup_subdir: close(subdir_fd); remove_subdir: rmdir(subdirpath); cleanup_file: close(fd); remove_file: unlink(filepath); cleanup_dir: close(dir_fd); remove_dir: rmdir(dirpath); remove_passcode: /* Try to unlock the device (no ramifications if it isn't locked when we try) and remove the passcode. */ if (unlock_device(passcode)) { printf("WARNING: failed to unlock the device.\n"); } if (clear_passcode(passcode)) { printf("WARNING: failed to clear the passcode.\n"); } end: return(test_result); }