#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 "hfs.h"
#include "hfs_cnode.h"
#if CONFIG_PROTECT
static struct cp_wrap_func g_cp_wrap_func = {NULL, NULL};
static struct cp_global_state g_cp_state = {0, 0, 0};
extern int (**hfs_vnodeop_p) (void *);
static int cp_is_valid_class(int);
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_setup_aes_ctx(struct cprotect *);
static int cp_make_keys (struct cprotect **, struct hfsmount *hfsmp, cnid_t, int);
static int cp_restore_keys(struct cprotect *, struct hfsmount *hfsmp);
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, struct hfsmount *hfsmp, cnid_t, struct cprotect**);
static int cp_unwrap(int, struct cprotect *);
#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)
{
g_cp_state.lock_state = action;
if (action == CP_LOCKED_STATE) {
return vfs_iterate(0, cp_lock_vfs_callback, (void*)((uintptr_t)action));
}
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;
}
#if 0
int
cp_isdevice_locked (void) {
if (g_cp_state.lock_state == CP_UNLOCKED_STATE) {
return 0;
}
return 1;
}
#endif
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) {
cp_root_major_vers(mp);
}
CP_ASSERT (cp->c_cpentry == NULL);
error = cp_getxattr(cp, hfsmp, &entry);
if (error == ENOATTR) {
int sub_error;
sub_error = cp_entry_create_keys (&entry, NULL, hfsmp, PROTECTION_CLASS_D, cp->c_fileid, cp->c_mode);
if (sub_error == 0) {
sub_error = cp_setxattr (cp, entry, hfsmp, cp->c_fileid, XATTR_CREATE);
}
error = sub_error;
}
else if (error == 0) {
if (S_ISREG(cp->c_mode)) {
entry->cp_flags |= CP_KEY_FLUSHED;
}
}
cp->c_cpentry = entry;
if (error) {
cp_entry_destroy(&cp->c_cpentry);
}
return error;
}
int
cp_entry_create_keys(struct cprotect **entry_ptr, struct cnode *dcp, struct hfsmount *hfsmp,
uint32_t input_class, cnid_t fileid, mode_t cmode)
{
int error = 0;
struct cprotect *entry = NULL;
size_t keylen;
uint32_t target_class = PROTECTION_CLASS_D;
if (cp_is_valid_class (input_class)) {
target_class = input_class;
if (S_ISDIR(cmode)) {
if (target_class == PROTECTION_CLASS_F) {
return EINVAL;
}
}
}
else {
if ((dcp) && (dcp->c_cpentry)) {
uint32_t parentclass = dcp->c_cpentry->cp_pclass;
if (cp_is_valid_class(parentclass)) {
target_class = parentclass;
}
}
}
keylen = S_ISDIR(cmode) ? 0 : CP_INITIAL_WRAPPEDKEYSIZE;
entry = cp_entry_alloc (keylen);
if (!entry) {
*entry_ptr = NULL;
return ENOMEM;
}
if (S_ISREG(cmode)) {
entry->cp_pclass = target_class;
entry->cp_flags |= CP_NEEDS_KEYS;
error = cp_make_keys(&entry, hfsmp, fileid, entry->cp_pclass);
}
else if (S_ISDIR(cmode)) {
entry->cp_pclass = target_class;
}
else {
error = EINVAL;
}
if (error) {
cp_entry_destroy (&entry);
*entry_ptr = NULL;
}
else {
*entry_ptr = entry;
}
return error;
}
int cp_entry_gentempkeys(struct cprotect **entry_ptr, struct hfsmount *hfsmp) {
int error = 0;
struct cprotect *entry = NULL;
size_t keylen;
uint32_t target_class = PROTECTION_CLASS_F;
keylen = CP_INITIAL_WRAPPEDKEYSIZE;
entry = cp_entry_alloc (keylen);
if (!entry) {
*entry_ptr = NULL;
return ENOMEM;
}
error = cp_make_keys (&entry, hfsmp, 0, target_class);
if (error) {
cp_entry_destroy (&entry);
*entry_ptr = NULL;
}
else {
*entry_ptr = entry;
}
return error;
}
void
cp_entry_destroy(struct cprotect **entry_ptr) {
struct cprotect *entry = *entry_ptr;
if (!entry) {
return;
}
*entry_ptr = NULL;
cp_entry_dealloc(entry);
}
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 EPERM;
}
cp = VTOC(vp);
hfsmp = VTOHFS(vp);
hfs_lock_truncate (cp, HFS_SHARED_LOCK);
took_truncate_lock = 1;
error = hfs_lock(cp, HFS_SHARED_LOCK);
if (error) {
hfs_unlock_truncate(cp, 0);
return error;
}
entry = cp->c_cpentry;
if (!entry) {
panic("Content Protection: uninitialized cnode %p", cp);
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
panic ("cp_vnode_getclass: cp %p has no keys!", cp);
}
if (error == 0) {
*class = entry->cp_pclass;
}
if (took_truncate_lock) {
hfs_unlock_truncate(cp, 0);
}
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;
u_int32_t keylen = 0;
struct hfsmount *hfsmp = NULL;
if (!cp_is_valid_class(newclass)) {
printf("hfs: CP: cp_setclass called with invalid class %d\n", newclass);
return EINVAL;
}
if (vnode_isdir(vp)) {
if (newclass == PROTECTION_CLASS_F) {
return EINVAL;
}
}
if (!cp_vnode_is_eligible(vp)) {
return EBADF;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return EPERM;
}
cp = VTOC(vp);
hfsmp = VTOHFS(vp);
hfs_lock_truncate (cp, HFS_EXCLUSIVE_LOCK);
took_truncate_lock = 1;
if (hfs_lock(cp, HFS_EXCLUSIVE_LOCK)) {
return EINVAL;
}
entry = cp->c_cpentry;
if (entry == NULL) {
error = EINVAL;
goto out;
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
panic ("cp_vnode_setclass: cp %p has no keys!\n", cp);
}
if (entry->cp_flags & CP_KEY_FLUSHED) {
error = cp_restore_keys(entry, hfsmp);
if (error)
goto out;
}
if (vnode_isreg(vp)) {
error = cp_wrap(newclass, hfsmp, cp->c_fileid, &cp->c_cpentry);
if (error) {
goto out;
}
}
entry = cp->c_cpentry;
entry->cp_pclass = newclass;
keylen = entry->cp_persistent_key_len;
error = cp_setxattr(cp, entry, VTOHFS(vp), 0,XATTR_REPLACE);
if (error == ENOATTR)
error = cp_setxattr(cp, entry, VTOHFS(vp), 0, XATTR_CREATE);
out:
if (took_truncate_lock) {
hfs_unlock_truncate (cp, 0);
}
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;
if (!cp_vnode_is_eligible(vp)) {
return EBADF;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return EPERM;
}
cp = VTOC(vp);
hfsmp = VTOHFS(vp);
hfs_lock_truncate (cp, HFS_EXCLUSIVE_LOCK);
took_truncate_lock = 1;
if (hfs_lock(cp, HFS_EXCLUSIVE_LOCK)) {
return EINVAL;
}
entry = cp->c_cpentry;
if (entry == NULL) {
error = EINVAL;
goto out;
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
panic ("cp_vnode_transcode: cp %p has no keys!", cp);
}
if (entry->cp_flags & CP_KEY_FLUSHED) {
error = cp_restore_keys(entry, hfsmp);
if (error) {
goto out;
}
}
if (vnode_isreg(vp)) {
if (entry->cp_pclass == PROTECTION_CLASS_F) {
error = EINVAL;
goto out;
}
error = g_cp_wrap_func.wrapper(entry->cp_pclass,
cp->c_fileid,
entry->cp_cache_key,
entry->cp_cache_key_len,
NULL,
NULL);
if(error)
error = EPERM;
}
out:
if (took_truncate_lock) {
hfs_unlock_truncate (cp, 0);
}
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))) {
return error;
}
entry = cp->c_cpentry;
if (!entry) {
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))) {
return error;
}
} else {
cp->c_lockowner = current_thread();
}
if ((entry->cp_flags & CP_NEEDS_KEYS)) {
panic ("cp_handle_vnop: cp %p has no keys!", cp);
}
if (entry->cp_flags & CP_KEY_FLUSHED) {
if ((vnop == CP_READ_ACCESS) && (ioflag & IO_ENCRYPTED)) {
error = 0;
}
else {
error = cp_restore_keys(entry, hfsmp);
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;
int error = 0;
if (!cp_vnode_is_eligible(vp)) {
return 0;
}
if (!cp_fs_protected(VTOVFS(vp))) {
return 0;
}
cp = VTOC(vp);
if ((error = hfs_lock(cp, HFS_SHARED_LOCK))) {
return error;
}
entry = cp->c_cpentry;
if (!entry)
goto out;
if (!S_ISREG(cp->c_mode))
goto out;
switch (entry->cp_pclass) {
case PROTECTION_CLASS_B:
if (mode & O_CREAT)
goto out;
case PROTECTION_CLASS_A:
error = g_cp_wrap_func.unwrapper(entry->cp_pclass,
entry->cp_persistent_key,
entry->cp_persistent_key_len,
NULL, NULL);
if (error)
error = EPERM;
break;
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);
}
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 (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();
if (entry->cp_flags & CP_NEEDS_KEYS) {
panic ("cp_setxattr: cp %p , cpentry %p still needs keys!", cp, entry);
}
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;
}
}
if (error == 0 ) {
entry->cp_flags &= ~CP_NO_XATTR;
}
return error;
}
int
cp_update_mkb (struct cprotect *entry, uint32_t fileid) {
int error = 0;
if (entry->cp_pclass != PROTECTION_CLASS_F ) {
error = g_cp_wrap_func.wrapper (entry->cp_pclass, fileid, entry->cp_cache_key,
entry->cp_cache_key_len, NULL, NULL);
}
if (error) {
error = EPERM;
}
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 EINVAL;
}
hfsmp = VFSTOHFS(mp);
err = cp_root_major_vers(mp);
if (err == 0) {
*level = hfsmp->hfs_running_cp_major_vers;
}
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)));
}
static int
cp_is_valid_class(int class)
{
return ((class >= PROTECTION_CLASS_A) &&
(class <= 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;
if (xattr->xattr_major_version >= CP_NEW_MAJOR_VERS) {
entry->cp_flags |= CP_OFF_IV_ENABLED;
}
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;
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_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;
}
static int
cp_make_keys(struct cprotect **entry_arg, struct hfsmount *hfsmp, cnid_t fileid, int default_pclass)
{
struct cprotect *entry = *entry_arg;
int target_pclass = 0;
int error = 0;
if (g_cp_state.wrap_functions_set != 1) {
printf("hfs: CP: could not create keys: no wrappers set\n");
return ENXIO;
}
entry->cp_cache_key_len = CP_MAX_KEYSIZE;
read_random(&entry->cp_cache_key[0], entry->cp_cache_key_len);
if (cp_is_valid_class(default_pclass) == 0) {
target_pclass = PROTECTION_CLASS_D;
} else {
target_pclass = default_pclass;
}
error = cp_wrap(target_pclass, hfsmp, fileid, entry_arg);
if (error) {
goto out;
}
entry = *entry_arg;
entry->cp_pclass = target_pclass;
if (hfsmp->hfs_running_cp_major_vers == CP_NEW_MAJOR_VERS) {
cp_setup_aes_ctx(entry);
entry->cp_flags |= CP_OFF_IV_ENABLED;
}
entry->cp_flags &= ~CP_NEEDS_KEYS;
entry->cp_flags |= CP_NO_XATTR;
out:
return error;
}
static int
cp_restore_keys(struct cprotect *entry, struct hfsmount *hfsmp)
{
int error = 0;
error = cp_unwrap(entry->cp_pclass, entry);
if (error) {
entry->cp_flags |= CP_KEY_FLUSHED;
bzero(entry->cp_cache_key, entry->cp_cache_key_len);
error = EPERM;
}
else {
if (hfsmp->hfs_running_cp_major_vers == CP_NEW_MAJOR_VERS) {
cp_setup_aes_ctx(entry);
entry->cp_flags |= CP_OFF_IV_ENABLED;
}
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(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;
int 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);
took_truncate_lock = 1;
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 ||
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_FORCE_LOCK);
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 %d\n", action);
}
out:
if (locked) {
hfs_unlock(cp);
}
if (took_truncate_lock) {
hfs_unlock_truncate (cp, 0);
}
vnode_put (vp);
return error;
}
static int
cp_wrap(int class, struct hfsmount *hfsmp, cnid_t fileid, struct cprotect **entry_ptr)
{
struct cprotect *entry = *entry_ptr;
uint8_t newkey[CP_MAX_WRAPPEDKEYSIZE];
size_t keylen = CP_MAX_WRAPPEDKEYSIZE;
int error = 0;
if (class == PROTECTION_CLASS_F) {
bzero(entry->cp_persistent_key, entry->cp_persistent_key_len);
entry->cp_persistent_key_len = 0;
return 0;
}
error = g_cp_wrap_func.wrapper(class,
fileid,
entry->cp_cache_key,
entry->cp_cache_key_len,
newkey,
&keylen);
if (!error) {
if ((keylen != CP_V2_WRAPPEDKEYSIZE) &&
(hfsmp->hfs_running_cp_major_vers == CP_PREV_MAJOR_VERS)) {
return EINVAL;
}
if (entry->cp_persistent_key_len != keylen) {
struct cprotect *oldentry = entry;
entry = cp_entry_alloc(keylen);
if (entry == NULL)
return ENOMEM;
bcopy(oldentry, entry, sizeof(struct cprotect));
entry->cp_persistent_key_len = keylen;
cp_entry_destroy (&oldentry);
*entry_ptr = entry;
}
bcopy(newkey, entry->cp_persistent_key, keylen);
}
else {
error = EPERM;
}
return error;
}
static int
cp_unwrap(int class, struct cprotect *entry)
{
int error = 0;
size_t keylen = CP_MAX_KEYSIZE;
if (class == PROTECTION_CLASS_F) {
return EPERM;
}
error = g_cp_wrap_func.unwrapper(class,
entry->cp_persistent_key,
entry->cp_persistent_key_len,
entry->cp_cache_key,
&keylen);
if (!error) {
entry->cp_cache_key_len = keylen;
} else {
error = EPERM;
}
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