content_protection_test.c [plain text]
#include "tests.h"
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <IOKit/IOKitLib.h>
#include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
#include <Kernel/sys/content_protection.h>
#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";
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
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)
{
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
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)
{
result = 0;
goto end;
}
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
{
result = 0;
}
end:
return(result);
}
#if 0
int device_lock_state()
{
int result = -1;
return(result);
}
#endif
int lock_device()
{
int result = -1;
#ifdef KEYBAG_ENTITLEMENTS
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
char * const keystorectl_args[] = {keystorectl_path, "lock", "", NULL};
result = keystorectl(keystorectl_args);
#endif
return(result);
}
int unlock_device(char * passcode)
{
int result = -1;
#ifdef KEYBAG_ENTITLEMENTS
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 ((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
return(result);
}
int set_passcode(char * new_passcode, char * old_passcode)
{
int result = -1;
#ifdef KEYBAG_ENTITLEMENTS
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 ((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
return(result);
}
int clear_passcode(char * passcode)
{
int result = -1;
result = set_passcode(NULL, passcode);
return(result);
}
#if 0
int unlocked_since_boot()
{
int result = 1;
return(result);
}
#endif
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";
bzero(filepath, PATH_MAX);
bzero(dirpath, PATH_MAX);
bzero(subdirpath, PATH_MAX);
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)
{
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)
{
printf("This device does not support or is not formatted for content protection.\n");
test_result = 0;
goto end;
}
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);
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;
}
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;
}
}
if (SET_PROT_CLASS(fd, PROTECTION_CLASS_D))
{
printf("%s, line %d: failed to change protection class from F 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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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);
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;
}
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)
{
printf("%s, line %d: newly created directory had a non-D 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;
}
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_E; new_prot_class++)
{
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;
}
fd = open(filepath, O_CREAT | O_EXCL | O_CLOEXEC);
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 did not inherit the directory's protection class.\n",
cpt_fail_header, __LINE__, strerror(errno));
goto cleanup_file;
}
close(fd);
unlink(filepath);
}
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;
}
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);
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);
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;
}
if (unlock_device(passcode))
{
PRINT_UNLOCK_FAIL;
goto cleanup_file;
}
for (new_prot_class = PROTECTION_CLASS_A; new_prot_class <= PROTECTION_CLASS_E; 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);
}
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:
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);
}