#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 <libkern/OSByteOrder.h>
#include "hfs.h"
#include "hfs_cnode.h"
#ifdef CONFIG_PROTECT
static struct cp_wrap_func g_cp_wrap_func = {NULL, NULL};
static struct cp_global_state g_cp_state = {0, 0};
extern int (**hfs_vnodeop_p) (void *);
static int cp_is_valid_class(int);
static int cp_getxattr(cnode_t *, struct cp_xattr *);
static int cp_setxattr(cnode_t *, struct cp_xattr *, int);
static struct cprotect *cp_entry_alloc(void);
static int cp_make_keys (struct cprotect *);
static int cp_restore_keys(struct cprotect *);
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_wrap(int, void *, void *);
static int cp_unwrap(int, void *, void *);
#if DEVELOPMENT || DEBUG
#define CP_ASSERT(x) \
if ((x) == 0) { \
panic("CP: failed assertion in %s", __FUNCTION__); \
}
#else
#define CP_ASSERT(x)
#endif
int
cp_key_store_action(int action)
{
g_cp_state.lock_state = action;
if (action == CP_LOCKED_STATE)
return vfs_iterate(0, cp_lock_vfs_callback, (void *)action);
else
return 0;
}
int
cp_register_wraps(cp_wrap_func_t key_store_func)
{
g_cp_wrap_func.wrapper = key_store_func->wrapper;
g_cp_wrap_func.unwrapper = key_store_func->unwrapper;
g_cp_state.wrap_functions_set = 1;
return 0;
}
int
cp_entry_init(cnode_t *cnode, struct mount *mp)
{
struct cprotect *entry;
struct cp_xattr xattr;
int error = 0;
if (!cp_fs_protected (mp)) {
cnode->c_cpentry = NULL;
return 0;
}
if (!S_ISREG(cnode->c_mode)) {
cnode->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;
}
CP_ASSERT (cnode->c_cpentry == NULL);
entry = cp_entry_alloc();
if (!entry)
return ENOMEM;
entry->cp_flags |= CP_KEY_FLUSHED;
cnode->c_cpentry = entry;
error = cp_getxattr(cnode, &xattr);
if (error == ENOATTR) {
entry->cp_flags |= CP_NEEDS_KEYS;
error = 0;
} else {
if (xattr.xattr_major_version != CP_CURRENT_MAJOR_VERS) {
printf("hfs: cp_entry_init: bad xattr version\n");
error = EINVAL;
goto out;
}
entry->cp_pclass = xattr.persistent_class;
bcopy(&xattr.persistent_key, &entry->cp_persistent_key, CP_WRAPPEDKEYSIZE);
}
out:
if (error) {
cp_entry_destroy (cnode);
}
return error;
}
int
cp_entry_create_keys(cnode_t *cnode)
{
struct cprotect *entry = cnode->c_cpentry;
if (!entry) {
return 0;
}
CP_ASSERT((entry->cp_flags & CP_NEEDS_KEYS));
return cp_make_keys(entry);
}
void
cp_entry_destroy(cnode_t *cnode)
{
struct cprotect *entry = cnode->c_cpentry;
if (!entry) {
return;
}
cnode->c_cpentry = NULL;
bzero(entry, sizeof(*entry));
FREE(entry, M_TEMP);
}
int
cp_fs_protected (mount_t mnt) {
return (vfs_flags(mnt) & MNT_CPROTECT);
}
cnode_t *
cp_get_protected_cnode(vnode_t vp)
{
if (!cp_vnode_is_eligible(vp)) {
return NULL;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return NULL;
}
return (cnode_t *) vp->v_data;
}
int
cp_vnode_getclass(vnode_t vp, int *class)
{
struct cp_xattr xattr;
int error = 0;
struct cnode *cnode;
if (!cp_vnode_is_eligible (vp)) {
return EBADF;
}
cnode = VTOC(vp);
hfs_lock(cnode, HFS_SHARED_LOCK);
if (cp_fs_protected(VTOVFS(vp))) {
struct cprotect *entry = cnode->c_cpentry;
if (!entry) {
panic("Content Protection: uninitialized cnode %p", cnode);
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
error = cp_make_keys(entry);
}
*class = entry->cp_pclass;
} else {
error = cp_getxattr(cnode, &xattr);
if (error == ENOATTR) {
*class = PROTECTION_CLASS_D;
error = 0;
} else if (error == 0) {
*class = xattr.persistent_class;
}
}
hfs_unlock(cnode);
return error;
}
int
cp_vnode_setclass(vnode_t vp, uint32_t newclass)
{
struct cnode *cnode;
struct cp_xattr xattr;
struct cprotect *entry = 0;
int error = 0;
if (!cp_is_valid_class(newclass)) {
printf("hfs: CP: cp_setclass called with invalid class %d\n", newclass);
return EINVAL;
}
if (!cp_vnode_is_eligible(vp)) {
return EBADF;
}
cnode = VTOC(vp);
if (hfs_lock(cnode, HFS_EXCLUSIVE_LOCK)) {
return EINVAL;
}
if (cp_fs_protected(VTOVFS(vp))) {
entry = cnode->c_cpentry;
if (entry == NULL) {
error = EINVAL;
goto out;
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
if ((error = cp_make_keys(entry)) != 0) {
goto out;
}
}
if (entry->cp_flags & CP_KEY_FLUSHED) {
error = cp_restore_keys(entry);
if (error)
goto out;
}
error = cp_wrap(newclass,
&entry->cp_cache_key[0],
&entry->cp_persistent_key[0]);
if (error) {
goto out;
}
entry->cp_pclass = newclass;
bcopy(&entry->cp_persistent_key, &xattr.persistent_key, CP_WRAPPEDKEYSIZE);
} else {
bzero(&xattr.persistent_key, CP_WRAPPEDKEYSIZE);
}
xattr.xattr_major_version = CP_CURRENT_MAJOR_VERS;
xattr.xattr_minor_version = CP_CURRENT_MINOR_VERS;
xattr.key_size = CP_WRAPPEDKEYSIZE;
xattr.flags = 0;
xattr.persistent_class = newclass;
error = cp_setxattr(cnode, &xattr, XATTR_REPLACE);
if (error == ENOATTR) {
error = cp_setxattr (cnode, &xattr, XATTR_CREATE);
}
out:
hfs_unlock(cnode);
return error;
}
int
cp_handle_vnop(cnode_t *cnode, int vnop)
{
struct cprotect *entry;
int error = 0;
struct cp_xattr xattr;
if ((error = hfs_lock(cnode, HFS_SHARED_LOCK)) != KERN_SUCCESS) {
return error;
}
entry = cnode->c_cpentry;
if (!entry)
goto out;
if ((error = cp_check_access(cnode, vnop)) != KERN_SUCCESS) {
goto out;
}
if (entry->cp_flags == 0) {
goto out;
}
if (lck_rw_lock_shared_to_exclusive(&cnode->c_rwlock) == FALSE) {
if ((error = hfs_lock(cnode, HFS_EXCLUSIVE_LOCK)) != KERN_SUCCESS) {
return error;
}
} else {
cnode->c_lockowner = current_thread();
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
if ((error = cp_make_keys(entry)) != 0) {
goto out;
}
}
if (entry->cp_flags & CP_KEY_FLUSHED) {
error = cp_restore_keys(entry);
if (error)
goto out;
}
if (entry->cp_flags & CP_NO_XATTR) {
bcopy(&entry->cp_persistent_key[0], &xattr.persistent_key, CP_WRAPPEDKEYSIZE);
xattr.xattr_major_version = CP_CURRENT_MAJOR_VERS;
xattr.xattr_minor_version = CP_CURRENT_MINOR_VERS;
xattr.key_size = CP_WRAPPEDKEYSIZE;
xattr.persistent_class = entry->cp_pclass;
error = cp_setxattr(cnode, &xattr, XATTR_CREATE);
}
out:
hfs_unlock(cnode);
return error;
}
int cp_handle_relocate (cnode_t *cp) {
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)) != KERN_SUCCESS) {
goto out;
}
if (entry->cp_flags == 0) {
error = 0;
goto out;
}
if (entry->cp_flags & CP_KEY_FLUSHED) {
error = cp_restore_keys(entry);
}
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("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 != KERN_SUCCESS) {
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;
}
static int
cp_vnode_is_eligible(vnode_t vp)
{
return ((vp->v_op == hfs_vnodeop_p) &&
(!vnode_issystem(vp)) &&
(vnode_isreg(vp)));
}
static int
cp_is_valid_class(int class)
{
return ((class >= PROTECTION_CLASS_A) &&
(class <= PROTECTION_CLASS_F));
}
static struct cprotect *
cp_entry_alloc(void)
{
struct cprotect *cp_entry;
MALLOC(cp_entry, struct cprotect *, sizeof(struct cprotect),
M_TEMP, M_WAITOK);
if (cp_entry == NULL)
return (NULL);
bzero(cp_entry, sizeof(*cp_entry));
return (cp_entry);
}
static int
cp_getxattr(cnode_t *cnode, struct cp_xattr *outxattr)
{
uio_t auio;
char uio_buf[UIO_SIZEOF(1)];
size_t attrsize = sizeof(struct cp_xattr);
int error = 0;
struct vnop_getxattr_args args;
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 = cnode->c_vp;
args.a_name = CONTENT_PROTECTION_XATTR_NAME;
args.a_uio = auio;
args.a_size = &attrsize;
args.a_options = XATTR_REPLACE;
args.a_context = vfs_context_current(); error = hfs_getxattr_internal(cnode, &args, VTOHFS(cnode->c_vp), 0);
if (error != KERN_SUCCESS) {
goto out;
}
outxattr->xattr_major_version = OSSwapLittleToHostInt16(outxattr->xattr_major_version);
outxattr->xattr_minor_version = OSSwapLittleToHostInt16(outxattr->xattr_minor_version);
outxattr->key_size = OSSwapLittleToHostInt32(outxattr->key_size);
outxattr->flags = OSSwapLittleToHostInt32(outxattr->flags);
outxattr->persistent_class = OSSwapLittleToHostInt32(outxattr->persistent_class);
out:
uio_free(auio);
return error;
}
static int
cp_setxattr(cnode_t *cnode, struct cp_xattr *newxattr, int options)
{
int error = 0;
struct vnop_setxattr_args args;
args.a_desc = NULL;
args.a_vp = cnode->c_vp;
args.a_name = CONTENT_PROTECTION_XATTR_NAME;
args.a_uio = NULL; args.a_options = options;
args.a_context = vfs_context_current();
newxattr->xattr_major_version = OSSwapHostToLittleInt16(newxattr->xattr_major_version);
newxattr->xattr_minor_version = OSSwapHostToLittleInt16(newxattr->xattr_minor_version);
newxattr->key_size = OSSwapHostToLittleInt32(newxattr->key_size);
newxattr->flags = OSSwapHostToLittleInt32(newxattr->flags);
newxattr->persistent_class = OSSwapHostToLittleInt32(newxattr->persistent_class);
error = hfs_setxattr_internal(cnode, (caddr_t)newxattr,
sizeof(struct cp_xattr), &args, VTOHFS(cnode->c_vp), 0);
if ((error == KERN_SUCCESS) && (cnode->c_cpentry)) {
cnode->c_cpentry->cp_flags &= ~CP_NO_XATTR;
}
return error;
}
static int
cp_make_keys(struct cprotect *entry)
{
int error = 0;
if (g_cp_state.wrap_functions_set != 1) {
printf("hfs: CP: could not create keys: no wrappers set\n");
return ENXIO;
}
read_random(&entry->cp_cache_key[0], CP_KEYSIZE);
entry->cp_pclass = PROTECTION_CLASS_D;
error = cp_wrap(PROTECTION_CLASS_D,
&entry->cp_cache_key[0],
&entry->cp_persistent_key[0]);
if (error) {
panic("could not wrap new key in class D\n");
}
entry->cp_flags &= ~CP_NEEDS_KEYS;
entry->cp_flags |= CP_NO_XATTR;
return error;
}
static int
cp_restore_keys(struct cprotect *entry)
{
int error = 0;
error = cp_unwrap(entry->cp_pclass,
&entry->cp_persistent_key[0],
&entry->cp_cache_key[0]);
if (error) {
entry->cp_flags |= CP_KEY_FLUSHED;
bzero(entry->cp_cache_key, CP_KEYSIZE);
error = EPERM;
}
else {
entry->cp_flags &= ~CP_KEY_FLUSHED;
}
return error;
}
static int
cp_lock_vfs_callback(mount_t mp, void *arg)
{
if (!cp_fs_protected(mp)) {
return 0;
}
return vnode_iterate(mp, 0, cp_lock_vnode_callback, arg);
}
static int
cp_check_access(cnode_t *cnode, int vnop)
{
int error = 0;
if (g_cp_state.lock_state == CP_UNLOCKED_STATE) {
return KERN_SUCCESS;
}
if (!cnode->c_cpentry) {
return KERN_SUCCESS;
}
switch (cnode->c_cpentry->cp_pclass) {
case PROTECTION_CLASS_A: {
error = EPERM;
break;
}
case PROTECTION_CLASS_B: {
if (vnop & CP_READ_ACCESS)
error = EPERM;
else
error = 0;
break;
}
default:
error = 0;
break;
}
return error;
}
static int
cp_lock_vnode_callback(vnode_t vp, void *arg)
{
cnode_t *cp = NULL;
struct cprotect *entry = NULL;
int error = 0;
int locked = 1;
int action = 0;
error = vnode_getwithref (vp);
if (error) {
return error;
}
cp = VTOC(vp);
hfs_lock(cp, HFS_FORCE_LOCK);
entry = cp->c_cpentry;
if (!entry) {
goto out;
}
action = (int)((uintptr_t) arg);
switch (action) {
case CP_LOCKED_STATE: {
vfs_context_t ctx;
if (entry->cp_pclass != PROTECTION_CLASS_A) {
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_FORCE_LOCK);
entry->cp_flags |= CP_KEY_FLUSHED;
bzero(&entry->cp_cache_key, CP_KEYSIZE);
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("unknown lock action %d\n", action);
}
out:
if (locked)
hfs_unlock(cp);
vnode_put (vp);
return error;
}
static int
cp_wrap(int class, void *inkey, void *outkey)
{
int error = 0;
size_t keyln = CP_WRAPPEDKEYSIZE;
if (class == PROTECTION_CLASS_F) {
bzero(outkey, CP_WRAPPEDKEYSIZE);
return 0;
}
error = g_cp_wrap_func.wrapper(class,
inkey,
CP_KEYSIZE,
outkey,
&keyln);
return error;
}
static int
cp_unwrap(int class, void *inkey, void *outkey)
{
int error = 0;
size_t keyln = CP_KEYSIZE;
if (class == PROTECTION_CLASS_F) {
return EPERM;
}
error = g_cp_wrap_func.unwrapper(class,
inkey,
CP_WRAPPEDKEYSIZE,
outkey,
&keyln);
return error;
}
#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