#include <sys/cprotect.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/random.h>
#include <sys/xattr.h>
#include <sys/uio_internal.h>
#include <sys/ubc_internal.h>
#include <sys/vnode_if.h>
#include <sys/vnode_internal.h>
#include <sys/fcntl.h>
#include <libkern/OSByteOrder.h>
#include <sys/proc.h>
#include <sys/kauth.h>
#include "hfs.h"
#include "hfs_cnode.h"
#if CONFIG_PROTECT
static struct cp_wrap_func g_cp_wrap_func = {};
static struct cp_global_state g_cp_state = {0, 0, 0};
extern int (**hfs_vnodeop_p) (void *);
static int cp_root_major_vers(mount_t mp);
static int cp_getxattr(cnode_t *, struct hfsmount *hfsmp, struct cprotect **);
static struct cprotect *cp_entry_alloc(size_t);
static void cp_entry_dealloc(struct cprotect *entry);
static int cp_restore_keys(struct cprotect *, struct hfsmount *hfsmp, struct cnode *);
static int cp_lock_vfs_callback(mount_t, void *);
static int cp_lock_vnode_callback(vnode_t, void *);
static int cp_vnode_is_eligible (vnode_t);
static int cp_check_access (cnode_t *, int);
static int cp_new(int newclass, struct hfsmount *hfsmp, struct cnode *cp, mode_t cmode, struct cprotect **output_entry);
static int cp_rewrap(struct cnode *cp, struct hfsmount *hfsmp, int newclass);
static int cp_unwrap(struct hfsmount *, struct cprotect *, struct cnode *);
static int cp_setup_aes_ctx(struct cprotect *entry);
static void cp_init_access(cp_cred_t access, struct cnode *cp);
#if DEVELOPMENT || DEBUG
#define CP_ASSERT(x) \
if ((x) == 0) { \
panic("Content Protection: failed assertion in %s", __FUNCTION__); \
}
#else
#define CP_ASSERT(x)
#endif
int
cp_key_store_action(int action)
{
if (action < 0 || action > CP_MAX_STATE) {
return -1;
}
g_cp_state.lock_state = (uint8_t)action;
if (action == CP_LOCKED_STATE) {
unsigned long action_arg = (unsigned long) action;
return vfs_iterate(0, cp_lock_vfs_callback, (void*)action_arg);
}
return 0;
}
int
cp_register_wraps(cp_wrap_func_t key_store_func)
{
g_cp_wrap_func.new_key = key_store_func->new_key;
g_cp_wrap_func.unwrapper = key_store_func->unwrapper;
g_cp_wrap_func.rewrapper = key_store_func->rewrapper;
g_cp_wrap_func.invalidater = key_store_func->invalidater;
g_cp_state.wrap_functions_set = 1;
return 0;
}
int
cp_entry_init(struct cnode *cp, struct mount *mp)
{
struct cprotect *entry = NULL;
int error = 0;
struct hfsmount *hfsmp = VFSTOHFS(mp);
if (!cp_fs_protected (mp)) {
cp->c_cpentry = NULL;
return 0;
}
if (!S_ISREG(cp->c_mode) && !S_ISDIR(cp->c_mode)) {
cp->c_cpentry = NULL;
return 0;
}
if (!g_cp_state.wrap_functions_set) {
printf("hfs: cp_update_entry: wrap functions not yet set\n");
return ENXIO;
}
if (hfsmp->hfs_running_cp_major_vers == 0) {
panic ("hfs cp: no running mount point version! ");
}
CP_ASSERT (cp->c_cpentry == NULL);
error = cp_getxattr(cp, hfsmp, &entry);
if (error == 0) {
if (S_ISREG(cp->c_mode)) {
if (entry->cp_flags & CP_NEEDS_KEYS) {
entry->cp_flags &= ~CP_KEY_FLUSHED;
}
else {
entry->cp_flags |= CP_KEY_FLUSHED;
}
}
}
else if (error == ENOATTR) {
int target_class = PROTECTION_CLASS_D;
if (S_ISDIR(cp->c_mode)) {
target_class = PROTECTION_CLASS_DIR_NONE;
}
error = cp_new (target_class, hfsmp, cp, cp->c_mode, &entry);
if (error == 0) {
error = cp_setxattr (cp, entry, hfsmp, cp->c_fileid, XATTR_CREATE);
}
}
if (error) {
goto out;
}
cp->c_cpentry = entry;
out:
if (error == 0) {
entry->cp_backing_cnode = cp;
}
else {
if (entry) {
cp_entry_destroy(entry);
}
cp->c_cpentry = NULL;
}
return error;
}
int cp_setup_newentry (struct hfsmount *hfsmp, struct cnode *dcp, int32_t suppliedclass,
mode_t cmode, struct cprotect **tmpentry)
{
int isdir = 0;
struct cprotect *entry = NULL;
uint32_t target_class = hfsmp->default_cp_class;
if (hfsmp->hfs_running_cp_major_vers == 0) {
panic ("CP: major vers not set in mount!");
}
if (S_ISDIR (cmode)) {
isdir = 1;
}
if (cp_is_valid_class (isdir, suppliedclass)) {
target_class = suppliedclass;
if (isdir) {
if (target_class == PROTECTION_CLASS_F) {
*tmpentry = NULL;
return EINVAL;
}
}
}
else {
if ((dcp) && (dcp->c_cpentry)) {
uint32_t parentclass = dcp->c_cpentry->cp_pclass;
if (cp_is_valid_class(1, parentclass)) {
if (isdir) {
target_class = parentclass;
}
else if (parentclass != PROTECTION_CLASS_DIR_NONE) {
target_class = parentclass;
}
}
}
}
entry = cp_entry_alloc (0);
if (entry == NULL) {
*tmpentry = NULL;
return ENOMEM;
}
entry->cp_flags = (CP_NEEDS_KEYS | CP_NO_XATTR);
entry->cp_pclass = target_class;
*tmpentry = entry;
return 0;
}
int cp_needs_tempkeys (struct hfsmount *hfsmp, int *needs)
{
if (hfsmp->hfs_running_cp_major_vers < CP_PREV_MAJOR_VERS ||
hfsmp->hfs_running_cp_major_vers > CP_NEW_MAJOR_VERS) {
return -1;
}
if (hfsmp->hfs_running_cp_major_vers < CP_NEW_MAJOR_VERS) {
*needs = 0;
}
else {
*needs = 1;
}
return 0;
}
int cp_entry_gentempkeys(struct cprotect **entry_ptr, struct hfsmount *hfsmp)
{
struct cprotect *entry = NULL;
if (hfsmp->hfs_running_cp_major_vers < CP_NEW_MAJOR_VERS) {
return EPERM;
}
entry = cp_entry_alloc (0);
if (entry == NULL) {
*entry_ptr = NULL;
return ENOMEM;
}
entry->cp_cache_key_len = CP_MAX_KEYSIZE;
entry->cp_pclass = PROTECTION_CLASS_F;
entry->cp_persistent_key_len = 0;
read_random (&entry->cp_cache_key[0], entry->cp_cache_key_len);
cp_setup_aes_ctx(entry);
entry->cp_flags |= CP_OFF_IV_ENABLED;
*entry_ptr = entry;
return 0;
}
void
cp_entry_destroy(struct cprotect *entry_ptr)
{
if (entry_ptr == NULL) {
return;
}
cp_entry_dealloc(entry_ptr);
}
int
cp_fs_protected (mount_t mnt)
{
return (vfs_flags(mnt) & MNT_CPROTECT);
}
struct cnode *
cp_get_protected_cnode(struct vnode *vp)
{
if (!cp_vnode_is_eligible(vp)) {
return NULL;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return NULL;
}
return (struct cnode*) vp->v_data;
}
int
cp_vnode_getclass(struct vnode *vp, int *class)
{
struct cprotect *entry;
int error = 0;
struct cnode *cp;
int took_truncate_lock = 0;
struct hfsmount *hfsmp = NULL;
if (!cp_vnode_is_eligible (vp)) {
return EBADF;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return ENOTSUP;
}
cp = VTOC(vp);
hfsmp = VTOHFS(vp);
hfs_lock_truncate (cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
took_truncate_lock = 1;
error = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT);
if (error) {
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
return error;
}
entry = cp->c_cpentry;
if (entry == NULL) {
panic("Content Protection: uninitialized cnode %p", cp);
}
if (error == 0) {
*class = entry->cp_pclass;
}
if (took_truncate_lock) {
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
}
hfs_unlock(cp);
return error;
}
int
cp_vnode_setclass(struct vnode *vp, uint32_t newclass)
{
struct cnode *cp;
struct cprotect *entry = 0;
int error = 0;
int took_truncate_lock = 0;
struct hfsmount *hfsmp = NULL;
int isdir = 0;
if (vnode_isdir (vp)) {
isdir = 1;
}
if (!cp_is_valid_class(isdir, newclass)) {
printf("hfs: CP: cp_setclass called with invalid class %d\n", newclass);
return EINVAL;
}
if (!cp_vnode_is_eligible(vp)) {
return EBADF;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return ENOTSUP;
}
hfsmp = VTOHFS(vp);
if (hfsmp->hfs_flags & HFS_READ_ONLY) {
return EROFS;
}
cp = VTOC(vp);
hfs_lock_truncate (cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
took_truncate_lock = 1;
if (hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT)) {
return EINVAL;
}
entry = cp->c_cpentry;
if (entry == NULL) {
error = EINVAL;
goto out;
}
if (vnode_isreg(vp)) {
if (entry->cp_flags & CP_KEY_FLUSHED) {
error = cp_restore_keys (entry, hfsmp, cp);
if (error) {
goto out;
}
}
if (newclass == PROTECTION_CLASS_F) {
if (cp->c_datafork->ff_size > 0) {
error = EINVAL;
goto out;
}
entry->cp_pclass = newclass;
entry->cp_cache_key_len = CP_MAX_KEYSIZE;
read_random (&entry->cp_cache_key[0], entry->cp_cache_key_len);
if (hfsmp->hfs_running_cp_major_vers == CP_NEW_MAJOR_VERS) {
cp_setup_aes_ctx (entry);
entry->cp_flags |= CP_OFF_IV_ENABLED;
}
bzero(entry->cp_persistent_key, entry->cp_persistent_key_len);
entry->cp_persistent_key_len = 0;
} else {
if (entry->cp_pclass == PROTECTION_CLASS_F) {
error = EPERM;
goto out;
}
if (entry->cp_flags & CP_NEEDS_KEYS) {
struct cprotect *newentry = NULL;
error = cp_generate_keys (hfsmp, cp, newclass, &newentry);
if (error == 0) {
cp_replace_entry (cp, newentry);
}
goto out;
}
else {
error = cp_rewrap(cp, hfsmp, newclass);
}
}
if (error) {
goto out;
}
}
else if (vnode_isdir(vp)) {
entry->cp_pclass = newclass;
error = 0;
}
else {
error = EINVAL;
goto out;
}
error = cp_setxattr(cp, cp->c_cpentry, VTOHFS(vp), 0, XATTR_REPLACE);
if (error == ENOATTR) {
error = cp_setxattr(cp, cp->c_cpentry, VTOHFS(vp), 0, XATTR_CREATE);
}
out:
if (took_truncate_lock) {
hfs_unlock_truncate (cp, HFS_LOCK_DEFAULT);
}
hfs_unlock(cp);
return error;
}
int cp_vnode_transcode(vnode_t vp)
{
struct cnode *cp;
struct cprotect *entry = 0;
int error = 0;
int took_truncate_lock = 0;
struct hfsmount *hfsmp = NULL;
cp_cred_s access_in;
cp_wrapped_key_s wrapped_key_in;
if (!cp_vnode_is_eligible(vp)) {
return EBADF;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return ENOTSUP;
}
cp = VTOC(vp);
hfsmp = VTOHFS(vp);
hfs_lock_truncate (cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
took_truncate_lock = 1;
if (hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT)) {
return EINVAL;
}
entry = cp->c_cpentry;
if (entry == NULL) {
error = EINVAL;
goto out;
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
error = EINVAL;
goto out;
}
if (vnode_isreg(vp)) {
if (entry->cp_pclass == PROTECTION_CLASS_F) {
error = EINVAL;
goto out;
}
cp_init_access(&access_in, cp);
bzero(&wrapped_key_in, sizeof(wrapped_key_in));
wrapped_key_in.key = entry->cp_persistent_key;
wrapped_key_in.key_len = entry->cp_persistent_key_len;
wrapped_key_in.dp_class = entry->cp_pclass;
error = g_cp_wrap_func.rewrapper(&access_in,
entry->cp_pclass,
&wrapped_key_in,
NULL);
if(error)
error = EPERM;
}
out:
if (took_truncate_lock) {
hfs_unlock_truncate (cp, HFS_LOCK_DEFAULT);
}
hfs_unlock(cp);
return error;
}
int
cp_handle_vnop(struct vnode *vp, int vnop, int ioflag)
{
struct cprotect *entry;
int error = 0;
struct hfsmount *hfsmp = NULL;
struct cnode *cp = NULL;
if (cp_vnode_is_eligible(vp) == 0) {
return 0;
}
if (cp_fs_protected (VTOVFS(vp)) == 0) {
return 0;
}
cp = VTOC(vp);
if ((error = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) {
return error;
}
entry = cp->c_cpentry;
if (entry == NULL) {
if (vnode_isreg(vp)) {
error = EPERM;
}
goto out;
}
vp = CTOV(cp, 0);
if (vp == NULL) {
vp = CTOV(cp,1);
if (vp == NULL) {
error = EINVAL;
goto out;
}
}
hfsmp = VTOHFS(vp);
if ((error = cp_check_access(cp, vnop))) {
if ((vnop == CP_READ_ACCESS) && (ioflag & IO_ENCRYPTED)) {
error = 0;
}
else {
goto out;
}
}
if (entry->cp_flags == 0) {
goto out;
}
if (lck_rw_lock_shared_to_exclusive(&cp->c_rwlock) == FALSE) {
if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
return error;
}
} else {
cp->c_lockowner = current_thread();
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
struct cprotect *newentry = NULL;
error = cp_generate_keys (hfsmp, cp, cp->c_cpentry->cp_pclass, &newentry);
if (error == 0) {
cp_replace_entry (cp, newentry);
entry = newentry;
}
else {
goto out;
}
}
if (entry->cp_flags & CP_KEY_FLUSHED) {
if ((vnop == CP_READ_ACCESS) && (ioflag & IO_ENCRYPTED)) {
error = 0;
}
else {
error = cp_restore_keys(entry, hfsmp, cp);
if (error) {
goto out;
}
}
}
if (entry->cp_flags & CP_NO_XATTR)
error = cp_setxattr(cp, entry, VTOHFS(cp->c_vp), 0, XATTR_CREATE);
out:
hfs_unlock(cp);
return error;
}
int
cp_handle_open(struct vnode *vp, int mode)
{
struct cnode *cp = NULL ;
struct cprotect *entry = NULL;
struct hfsmount *hfsmp;
int error = 0;
if (!cp_vnode_is_eligible(vp)) {
return 0;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return 0;
}
cp = VTOC(vp);
hfsmp = VTOHFS(vp);
if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
return error;
}
entry = cp->c_cpentry;
if (entry == NULL) {
if (vnode_isreg(vp)) {
error = EPERM;
}
goto out;
}
if (!S_ISREG(cp->c_mode))
goto out;
if (entry->cp_flags & CP_NEEDS_KEYS) {
struct cprotect *newentry = NULL;
error = cp_generate_keys (hfsmp, cp, cp->c_cpentry->cp_pclass, &newentry);
if (error == 0) {
cp_replace_entry (cp, newentry);
entry = newentry;
}
else {
goto out;
}
}
switch (entry->cp_pclass) {
case PROTECTION_CLASS_B:
if (mode & O_CREAT) {
break;
}
if ((entry->cp_flags & CP_KEY_FLUSHED) == 0) {
cp_cred_s access_in;
cp_wrapped_key_s wrapped_key_in;
cp_init_access(&access_in, cp);
bzero(&wrapped_key_in, sizeof(wrapped_key_in));
wrapped_key_in.key = entry->cp_persistent_key;
wrapped_key_in.key_len = entry->cp_persistent_key_len;
wrapped_key_in.dp_class = entry->cp_pclass;
error = g_cp_wrap_func.unwrapper(&access_in, &wrapped_key_in, NULL);
if (error) {
error = EPERM;
}
break;
}
case PROTECTION_CLASS_A:
case PROTECTION_CLASS_C:
if (entry->cp_flags & CP_KEY_FLUSHED) {
error = cp_restore_keys(entry, hfsmp, cp);
}
if (error) {
error = EPERM;
}
break;
case PROTECTION_CLASS_D:
default:
break;
}
out:
hfs_unlock(cp);
return error;
}
int
cp_handle_relocate (struct cnode *cp, struct hfsmount *hfsmp)
{
struct cprotect *entry;
int error = -1;
entry = cp->c_cpentry;
if (!entry)
goto out;
if ((error = cp_check_access(cp, CP_READ_ACCESS | CP_WRITE_ACCESS))) {
goto out;
}
if (entry->cp_flags == 0) {
error = 0;
goto out;
}
if (entry->cp_flags & CP_KEY_FLUSHED) {
error = cp_restore_keys(entry, hfsmp, cp);
}
out:
return error;
}
int
cp_getrootxattr(struct hfsmount* hfsmp, struct cp_root_xattr *outxattr)
{
uio_t auio;
char uio_buf[UIO_SIZEOF(1)];
size_t attrsize = sizeof(struct cp_root_xattr);
int error = 0;
struct vnop_getxattr_args args;
if (!outxattr) {
panic("Content Protection: cp_xattr called with xattr == NULL");
}
auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ, &uio_buf[0], sizeof(uio_buf));
uio_addiov(auio, CAST_USER_ADDR_T(outxattr), attrsize);
args.a_desc = NULL; args.a_vp = NULL; args.a_name = CONTENT_PROTECTION_XATTR_NAME;
args.a_uio = auio;
args.a_size = &attrsize;
args.a_options = XATTR_REPLACE;
args.a_context = NULL;
error = hfs_getxattr_internal(NULL, &args, hfsmp, 1);
outxattr->major_version = OSSwapLittleToHostInt16(outxattr->major_version);
outxattr->minor_version = OSSwapLittleToHostInt16(outxattr->minor_version);
outxattr->flags = OSSwapLittleToHostInt64(outxattr->flags);
if (error != 0) {
goto out;
}
out:
uio_free(auio);
return error;
}
int
cp_setrootxattr(struct hfsmount *hfsmp, struct cp_root_xattr *newxattr)
{
int error = 0;
struct vnop_setxattr_args args;
args.a_desc = NULL;
args.a_vp = NULL;
args.a_name = CONTENT_PROTECTION_XATTR_NAME;
args.a_uio = NULL; args.a_options = 0;
args.a_context = NULL;
newxattr->major_version = OSSwapHostToLittleInt16(newxattr->major_version);
newxattr->minor_version = OSSwapHostToLittleInt16(newxattr->minor_version);
newxattr->flags = OSSwapHostToLittleInt64(newxattr->flags);
error = hfs_setxattr_internal(NULL, (caddr_t)newxattr,
sizeof(struct cp_root_xattr), &args, hfsmp, 1);
return error;
}
int cp_setxattr(struct cnode *cp, struct cprotect *entry, struct hfsmount *hfsmp, uint32_t fileid, int options)
{
int error = 0;
size_t attrsize;
struct vnop_setxattr_args args;
uint32_t target_fileid;
struct cnode *arg_cp = NULL;
uint32_t tempflags = 0;
args.a_desc = NULL;
if (hfsmp->hfs_flags & HFS_READ_ONLY) {
return EROFS;
}
if (cp) {
args.a_vp = cp->c_vp;
target_fileid = 0;
arg_cp = cp;
}
else {
args.a_vp = NULL;
target_fileid = fileid;
}
args.a_name = CONTENT_PROTECTION_XATTR_NAME;
args.a_uio = NULL; args.a_options = options;
args.a_context = vfs_context_current();
tempflags = entry->cp_flags;
tempflags &= ~CP_NO_XATTR;
switch(hfsmp->hfs_running_cp_major_vers) {
case CP_NEW_MAJOR_VERS: {
struct cp_xattr_v4 *newxattr = NULL; MALLOC (newxattr, struct cp_xattr_v4*, sizeof(struct cp_xattr_v4), M_TEMP, M_WAITOK);
if (newxattr == NULL) {
error = ENOMEM;
break;
}
bzero (newxattr, sizeof(struct cp_xattr_v4));
attrsize = sizeof(*newxattr) - CP_MAX_WRAPPEDKEYSIZE + entry->cp_persistent_key_len;
newxattr->xattr_major_version = OSSwapHostToLittleInt16 (hfsmp->hfs_running_cp_major_vers);
newxattr->xattr_minor_version = OSSwapHostToLittleInt16(CP_MINOR_VERS);
newxattr->key_size = OSSwapHostToLittleInt32(entry->cp_persistent_key_len);
newxattr->flags = OSSwapHostToLittleInt32(tempflags);
newxattr->persistent_class = OSSwapHostToLittleInt32(entry->cp_pclass);
bcopy(entry->cp_persistent_key, newxattr->persistent_key, entry->cp_persistent_key_len);
error = hfs_setxattr_internal(arg_cp, (caddr_t)newxattr, attrsize, &args, hfsmp, target_fileid);
FREE(newxattr, M_TEMP);
break;
}
case CP_PREV_MAJOR_VERS: {
struct cp_xattr_v2 *newxattr = NULL;
MALLOC (newxattr, struct cp_xattr_v2*, sizeof(struct cp_xattr_v2), M_TEMP, M_WAITOK);
if (newxattr == NULL) {
error = ENOMEM;
break;
}
bzero (newxattr, sizeof(struct cp_xattr_v2));
attrsize = sizeof(*newxattr);
newxattr->xattr_major_version = OSSwapHostToLittleInt16(hfsmp->hfs_running_cp_major_vers);
newxattr->xattr_minor_version = OSSwapHostToLittleInt16(CP_MINOR_VERS);
newxattr->key_size = OSSwapHostToLittleInt32(entry->cp_persistent_key_len);
newxattr->flags = OSSwapHostToLittleInt32(tempflags);
newxattr->persistent_class = OSSwapHostToLittleInt32(entry->cp_pclass);
bcopy(entry->cp_persistent_key, newxattr->persistent_key, entry->cp_persistent_key_len);
error = hfs_setxattr_internal(arg_cp, (caddr_t)newxattr, attrsize, &args, hfsmp, target_fileid);
FREE (newxattr, M_TEMP);
break;
}
default:
printf("hfs: cp_setxattr: Unknown CP version running \n");
break;
}
if (error == 0 ) {
entry->cp_flags &= ~CP_NO_XATTR;
}
return error;
}
int
cp_get_root_major_vers(vnode_t vp, uint32_t *level)
{
int err = 0;
struct hfsmount *hfsmp = NULL;
struct mount *mp = NULL;
mp = VTOVFS(vp);
if (cp_fs_protected(mp) == 0) {
return ENOTSUP;
}
hfsmp = VFSTOHFS(mp);
err = cp_root_major_vers(mp);
if (err == 0) {
*level = hfsmp->hfs_running_cp_major_vers;
}
return err;
}
int cp_get_default_level (struct vnode *vp, uint32_t *level) {
int err = 0;
struct hfsmount *hfsmp = NULL;
struct mount *mp = NULL;
mp = VTOVFS(vp);
if (cp_fs_protected(mp) == 0) {
return ENOTSUP;
}
hfsmp = VFSTOHFS(mp);
*level = hfsmp->default_cp_class;
return err;
}
static int
cp_root_major_vers(mount_t mp)
{
int err = 0;
struct cp_root_xattr xattr;
struct hfsmount *hfsmp = NULL;
hfsmp = vfs_fsprivate(mp);
err = cp_getrootxattr (hfsmp, &xattr);
if (err == 0) {
hfsmp->hfs_running_cp_major_vers = xattr.major_version;
}
else {
return EINVAL;
}
return 0;
}
static int
cp_vnode_is_eligible(struct vnode *vp)
{
return ((vp->v_op == hfs_vnodeop_p) &&
(!vnode_issystem(vp)) &&
(vnode_isreg(vp) || vnode_isdir(vp)));
}
int
cp_is_valid_class(int isdir, int32_t protectionclass)
{
if (isdir) {
return ((protectionclass >= PROTECTION_CLASS_DIR_NONE) &&
(protectionclass <= PROTECTION_CLASS_D));
}
else {
return ((protectionclass >= PROTECTION_CLASS_A) &&
(protectionclass <= PROTECTION_CLASS_F));
}
}
static struct cprotect *
cp_entry_alloc(size_t keylen)
{
struct cprotect *cp_entry;
if (keylen > CP_MAX_WRAPPEDKEYSIZE)
return (NULL);
MALLOC(cp_entry, struct cprotect *, sizeof(struct cprotect) + keylen,
M_TEMP, M_WAITOK);
if (cp_entry == NULL)
return (NULL);
bzero(cp_entry, sizeof(*cp_entry) + keylen);
cp_entry->cp_persistent_key_len = keylen;
return (cp_entry);
}
static void
cp_entry_dealloc(struct cprotect *entry)
{
uint32_t keylen = entry->cp_persistent_key_len;
bzero(entry, (sizeof(*entry) + keylen));
FREE(entry, M_TEMP);
}
static int
cp_getxattr(struct cnode *cp, struct hfsmount *hfsmp, struct cprotect **outentry)
{
int error = 0;
uio_t auio;
size_t attrsize;
char uio_buf[UIO_SIZEOF(1)];
struct vnop_getxattr_args args;
struct cprotect *entry = NULL;
auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ, &uio_buf[0], sizeof(uio_buf));
args.a_desc = NULL; args.a_vp = cp->c_vp;
args.a_name = CONTENT_PROTECTION_XATTR_NAME;
args.a_uio = auio;
args.a_options = XATTR_REPLACE;
args.a_context = vfs_context_current();
switch (hfsmp->hfs_running_cp_major_vers) {
case CP_NEW_MAJOR_VERS: {
struct cp_xattr_v4 *xattr = NULL;
MALLOC (xattr, struct cp_xattr_v4*, sizeof(struct cp_xattr_v4), M_TEMP, M_WAITOK);
if (xattr == NULL) {
error = ENOMEM;
break;
}
bzero(xattr, sizeof (struct cp_xattr_v4));
attrsize = sizeof(*xattr);
uio_addiov(auio, CAST_USER_ADDR_T(xattr), attrsize);
args.a_size = &attrsize;
error = hfs_getxattr_internal(cp, &args, VTOHFS(cp->c_vp), 0);
if (error != 0) {
FREE (xattr, M_TEMP);
goto out;
}
xattr->xattr_major_version = OSSwapLittleToHostInt16(xattr->xattr_major_version);
xattr->xattr_minor_version = OSSwapLittleToHostInt16(xattr->xattr_minor_version);
xattr->key_size = OSSwapLittleToHostInt32(xattr->key_size);
xattr->flags = OSSwapLittleToHostInt32(xattr->flags);
xattr->persistent_class = OSSwapLittleToHostInt32(xattr->persistent_class);
if (xattr->xattr_major_version != hfsmp->hfs_running_cp_major_vers ) {
printf("hfs: cp_getxattr: bad xattr version %d expecting %d\n",
xattr->xattr_major_version, hfsmp->hfs_running_cp_major_vers);
error = EINVAL;
FREE (xattr, M_TEMP);
goto out;
}
if (xattr->key_size > CP_MAX_WRAPPEDKEYSIZE) {
error = EINVAL;
FREE (xattr, M_TEMP);
goto out;
}
entry = cp_entry_alloc(xattr->key_size);
if (!entry) {
FREE (xattr, M_TEMP);
return ENOMEM;
}
entry->cp_pclass = xattr->persistent_class;
xattr->flags &= ~CP_NO_XATTR;
entry->cp_flags = xattr->flags;
if (xattr->xattr_major_version >= CP_NEW_MAJOR_VERS) {
entry->cp_flags |= CP_OFF_IV_ENABLED;
}
if (entry->cp_pclass != PROTECTION_CLASS_F ) {
bcopy(xattr->persistent_key, entry->cp_persistent_key, xattr->key_size);
}
FREE (xattr, M_TEMP);
break;
}
case CP_PREV_MAJOR_VERS: {
struct cp_xattr_v2 *xattr = NULL;
MALLOC (xattr, struct cp_xattr_v2*, sizeof(struct cp_xattr_v2), M_TEMP, M_WAITOK);
if (xattr == NULL) {
error = ENOMEM;
break;
}
bzero (xattr, sizeof (struct cp_xattr_v2));
attrsize = sizeof(*xattr);
uio_addiov(auio, CAST_USER_ADDR_T(xattr), attrsize);
args.a_size = &attrsize;
error = hfs_getxattr_internal(cp, &args, VTOHFS(cp->c_vp), 0);
if (error != 0) {
FREE (xattr, M_TEMP);
goto out;
}
xattr->xattr_major_version = OSSwapLittleToHostInt16(xattr->xattr_major_version);
xattr->xattr_minor_version = OSSwapLittleToHostInt16(xattr->xattr_minor_version);
xattr->key_size = OSSwapLittleToHostInt32(xattr->key_size);
xattr->flags = OSSwapLittleToHostInt32(xattr->flags);
xattr->persistent_class = OSSwapLittleToHostInt32(xattr->persistent_class);
if (xattr->xattr_major_version != hfsmp->hfs_running_cp_major_vers) {
printf("hfs: cp_getxattr: bad xattr version %d expecting %d\n",
xattr->xattr_major_version, hfsmp->hfs_running_cp_major_vers);
error = EINVAL;
FREE (xattr, M_TEMP);
goto out;
}
if (xattr->key_size > CP_V2_WRAPPEDKEYSIZE) {
error = EINVAL;
FREE (xattr, M_TEMP);
goto out;
}
entry = cp_entry_alloc(xattr->key_size);
if (!entry) {
FREE (xattr, M_TEMP);
return ENOMEM;
}
entry->cp_pclass = xattr->persistent_class;
xattr->flags &= ~CP_NO_XATTR;
entry->cp_flags = xattr->flags;
if (entry->cp_pclass != PROTECTION_CLASS_F ) {
bcopy(xattr->persistent_key, entry->cp_persistent_key, xattr->key_size);
}
FREE (xattr, M_TEMP);
break;
}
}
out:
uio_free(auio);
*outentry = entry;
return error;
}
static int
cp_restore_keys(struct cprotect *entry, struct hfsmount *hfsmp, struct cnode *cp)
{
int error = 0;
error = cp_unwrap(hfsmp, entry, cp);
if (error) {
entry->cp_flags |= CP_KEY_FLUSHED;
bzero(entry->cp_cache_key, entry->cp_cache_key_len);
error = EPERM;
}
else {
entry->cp_flags &= ~CP_KEY_FLUSHED;
}
return error;
}
static int
cp_lock_vfs_callback(mount_t mp, void *arg)
{
unsigned long new_state;
if (!cp_fs_protected(mp)) {
return 0;
}
new_state = (unsigned long) arg;
if (new_state == CP_LOCKED_STATE) {
return vnode_iterate(mp, 0, cp_lock_vnode_callback, arg);
}
return 0;
}
static int
cp_check_access(struct cnode *cp, int vnop __unused)
{
int error = 0;
if (g_cp_state.lock_state == CP_UNLOCKED_STATE) {
return 0;
}
if (!cp->c_cpentry) {
return 0;
}
if (!S_ISREG(cp->c_mode)) {
return 0;
}
switch (cp->c_cpentry->cp_pclass) {
case PROTECTION_CLASS_A: {
error = EPERM;
break;
}
default:
error = 0;
break;
}
return error;
}
static int
cp_lock_vnode_callback(struct vnode *vp, void *arg)
{
cnode_t *cp = NULL;
struct cprotect *entry = NULL;
int error = 0;
int locked = 1;
unsigned long action = 0;
int took_truncate_lock = 0;
error = vnode_getwithref (vp);
if (error) {
return error;
}
cp = VTOC(vp);
hfs_lock_truncate (cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
took_truncate_lock = 1;
hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS);
entry = cp->c_cpentry;
if (!entry) {
goto out;
}
action = (unsigned long) arg;
switch (action) {
case CP_LOCKED_STATE: {
vfs_context_t ctx;
if (entry->cp_pclass != PROTECTION_CLASS_A ||
vnode_isdir(vp)) {
goto out;
}
ctx = vfs_context_current();
(void) hfs_filedone (vp, ctx);
hfs_unlock (cp);
ubc_msync (vp, 0, ubc_getsize(vp), NULL, UBC_PUSHALL | UBC_INVALIDATE | UBC_SYNC);
hfs_lock (cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS);
entry->cp_flags |= CP_KEY_FLUSHED;
bzero(&entry->cp_cache_key, entry->cp_cache_key_len);
bzero(&entry->cp_cache_iv_ctx, sizeof(aes_encrypt_ctx));
hfs_unlock(cp);
locked = 0;
ubc_msync (vp, 0, ubc_getsize(vp), NULL, UBC_INVALIDATE | UBC_SYNC);
break;
}
case CP_UNLOCKED_STATE: {
break;
}
default:
panic("Content Protection: unknown lock action %lu\n", action);
}
out:
if (locked) {
hfs_unlock(cp);
}
if (took_truncate_lock) {
hfs_unlock_truncate (cp, HFS_LOCK_DEFAULT);
}
vnode_put (vp);
return error;
}
static int
cp_rewrap(struct cnode *cp, struct hfsmount *hfsmp, int newclass)
{
struct cprotect *entry = cp->c_cpentry;
uint8_t new_persistent_key[CP_MAX_WRAPPEDKEYSIZE];
size_t keylen = CP_MAX_WRAPPEDKEYSIZE;
int error = 0;
cp_cred_s access_in;
cp_wrapped_key_s wrapped_key_in;
cp_wrapped_key_s wrapped_key_out;
if (newclass == PROTECTION_CLASS_F) {
return EINVAL;
}
cp_init_access(&access_in, cp);
bzero(&wrapped_key_in, sizeof(wrapped_key_in));
wrapped_key_in.key = entry->cp_persistent_key;
wrapped_key_in.key_len = entry->cp_persistent_key_len;
wrapped_key_in.dp_class = entry->cp_pclass;
bzero(&wrapped_key_out, sizeof(wrapped_key_out));
wrapped_key_out.key = new_persistent_key;
wrapped_key_out.key_len = keylen;
error = g_cp_wrap_func.rewrapper(&access_in,
newclass,
&wrapped_key_in,
&wrapped_key_out);
keylen = wrapped_key_out.key_len;
if (error == 0) {
struct cprotect *newentry = NULL;
if ((keylen != CP_V2_WRAPPEDKEYSIZE) &&
(hfsmp->hfs_running_cp_major_vers == CP_PREV_MAJOR_VERS)) {
return EINVAL;
}
newentry = cp_entry_alloc (keylen);
bcopy (entry, newentry, sizeof(struct cprotect));
bcopy (new_persistent_key, newentry->cp_persistent_key, keylen);
newentry->cp_persistent_key_len = keylen;
newentry->cp_backing_cnode = cp;
newentry->cp_pclass = newclass;
cp->c_cpentry = newentry;
cp_entry_destroy (entry);
}
else {
error = EPERM;
}
return error;
}
static int
cp_unwrap(struct hfsmount *hfsmp, struct cprotect *entry, struct cnode *cp)
{
int error = 0;
uint8_t iv_key[CP_IV_KEYSIZE];
cp_cred_s access_in;
cp_wrapped_key_s wrapped_key_in;
cp_raw_key_s key_out;
if (entry->cp_pclass == PROTECTION_CLASS_F) {
return EPERM;
}
cp_init_access(&access_in, cp);
bzero(&wrapped_key_in, sizeof(wrapped_key_in));
wrapped_key_in.key = entry->cp_persistent_key;
wrapped_key_in.key_len = entry->cp_persistent_key_len;
wrapped_key_in.dp_class = entry->cp_pclass;
bzero(&key_out, sizeof(key_out));
key_out.key = entry->cp_cache_key;
key_out.key_len = CP_MAX_KEYSIZE;
key_out.iv_key = iv_key;
key_out.iv_key_len = CP_IV_KEYSIZE;
error = g_cp_wrap_func.unwrapper(&access_in, &wrapped_key_in, &key_out);
if (!error) {
entry->cp_cache_key_len = key_out.key_len;
if (hfsmp->hfs_running_cp_major_vers == CP_NEW_MAJOR_VERS) {
aes_encrypt_key128(iv_key, &entry->cp_cache_iv_ctx);
entry->cp_flags |= CP_OFF_IV_ENABLED;
}
} else {
error = EPERM;
}
return error;
}
static int
cp_setup_aes_ctx(struct cprotect *entry)
{
SHA1_CTX sha1ctxt;
uint8_t cp_cache_iv_key[CP_IV_KEYSIZE];
SHA1Init(&sha1ctxt);
SHA1Update(&sha1ctxt, &entry->cp_cache_key[0], CP_MAX_KEYSIZE);
SHA1Final(&cp_cache_iv_key[0], &sha1ctxt);
aes_encrypt_key128(&cp_cache_iv_key[0], &entry->cp_cache_iv_ctx);
return 0;
}
int cp_generate_keys (struct hfsmount *hfsmp, struct cnode *cp, int targetclass, struct cprotect **newentry)
{
int error = 0;
struct cprotect *newcp = NULL;
*newentry = NULL;
if (cp->c_cpentry == NULL) {
return 0;
}
if (cp->c_cpentry->cp_flags & CP_NO_XATTR) {
error = EINVAL;
goto out;
}
if (S_ISREG(cp->c_mode)) {
if ((cp->c_cpentry->cp_flags & CP_NEEDS_KEYS) == 0){
error = EINVAL;
goto out;
}
}
error = cp_new (targetclass, hfsmp, cp, cp->c_mode, &newcp);
if (error) {
error = EPERM;
goto out;
}
error = cp_setxattr (cp, newcp, hfsmp, cp->c_fileid, XATTR_REPLACE);
if (error) {
if (newcp) {
cp_entry_destroy(newcp);
}
goto out;
}
newcp->cp_flags &= ~CP_NEEDS_KEYS;
*newentry = newcp;
out:
return error;
}
void cp_replace_entry (struct cnode *cp, struct cprotect *newentry)
{
if (cp->c_cpentry) {
cp_entry_destroy (cp->c_cpentry);
}
cp->c_cpentry = newentry;
newentry->cp_backing_cnode = cp;
return;
}
static int
cp_new(int newclass, struct hfsmount *hfsmp, struct cnode *cp, mode_t cmode, struct cprotect **output_entry)
{
struct cprotect *entry = NULL;
int error = 0;
uint8_t new_key[CP_MAX_KEYSIZE];
size_t new_key_len = CP_MAX_KEYSIZE;
uint8_t new_persistent_key[CP_MAX_WRAPPEDKEYSIZE];
size_t new_persistent_len = CP_MAX_WRAPPEDKEYSIZE;
uint8_t iv_key[CP_IV_KEYSIZE];
size_t iv_key_len = CP_IV_KEYSIZE;
cp_cred_s access_in;
cp_wrapped_key_s wrapped_key_out;
cp_raw_key_s key_out;
if (*output_entry != NULL) {
panic ("cp_new with non-null entry!");
}
if (!g_cp_state.wrap_functions_set) {
printf("hfs: cp_new: wrap/gen functions not yet set\n");
return ENXIO;
}
if (S_ISDIR (cmode)) {
new_persistent_len = 0;
new_key_len = 0;
error = 0;
}
else if (S_ISREG(cmode)) {
if (newclass == PROTECTION_CLASS_F) {
new_key_len = CP_MAX_KEYSIZE;
read_random (&new_key[0], new_key_len);
new_persistent_len = 0;
error = 0;
}
else {
cp_init_access(&access_in, cp);
bzero(&key_out, sizeof(key_out));
key_out.key = new_key;
key_out.key_len = new_key_len;
key_out.iv_key = iv_key;
key_out.iv_key_len = iv_key_len;
bzero(&wrapped_key_out, sizeof(wrapped_key_out));
wrapped_key_out.key = new_persistent_key;
wrapped_key_out.key_len = new_persistent_len;
error = g_cp_wrap_func.new_key(&access_in,
newclass,
&key_out,
&wrapped_key_out);
new_key_len = key_out.key_len;
iv_key_len = key_out.iv_key_len;
new_persistent_len = wrapped_key_out.key_len;
}
}
else {
error = EPERM;
}
if (error == 0) {
if ((new_persistent_len != CP_V2_WRAPPEDKEYSIZE) &&
(hfsmp->hfs_running_cp_major_vers == CP_PREV_MAJOR_VERS)) {
return EINVAL;
}
entry = cp_entry_alloc (new_persistent_len);
if (entry == NULL) {
return ENOMEM;
}
*output_entry = entry;
entry->cp_pclass = newclass;
if (new_key_len > 0) {
bcopy (new_key, entry->cp_cache_key, new_key_len);
entry->cp_cache_key_len = new_key_len;
if (hfsmp->hfs_running_cp_major_vers == CP_NEW_MAJOR_VERS) {
if (newclass == PROTECTION_CLASS_F) {
cp_setup_aes_ctx(entry);
}
else {
aes_encrypt_key128(iv_key, &entry->cp_cache_iv_ctx);
}
entry->cp_flags |= CP_OFF_IV_ENABLED;
}
}
if (new_persistent_len > 0) {
bcopy(new_persistent_key, entry->cp_persistent_key, new_persistent_len);
}
}
else {
error = EPERM;
}
return error;
}
static void cp_init_access(cp_cred_t access, struct cnode *cp)
{
vfs_context_t context = vfs_context_current();
kauth_cred_t cred = vfs_context_ucred(context);
proc_t proc = vfs_context_proc(context);
bzero(access, sizeof(*access));
access->inode = cp->c_fileid;
access->pid = proc_pid(proc);
access->uid = kauth_cred_getuid(cred);
return;
}
#else
int cp_key_store_action(int action __unused)
{
return ENOTSUP;
}
int cp_register_wraps(cp_wrap_func_t key_store_func __unused)
{
return ENOTSUP;
}
#endif