X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/743345f9a4b36f7e2f9ba37691e70c50baecb56e..4d15aeb193b2c68f1d38666c317f8d3734f5f083:/bsd/vfs/vfs_subr.c?ds=sidebyside diff --git a/bsd/vfs/vfs_subr.c b/bsd/vfs/vfs_subr.c index 2317401e6..6b16ca6cb 100644 --- a/bsd/vfs/vfs_subr.c +++ b/bsd/vfs/vfs_subr.c @@ -507,13 +507,7 @@ vnode_hascleanblks(vnode_t vp) void vnode_iterate_setup(mount_t mp) { - while (mp->mnt_lflag & MNT_LITER) { - mp->mnt_lflag |= MNT_LITERWAIT; - msleep((caddr_t)mp, &mp->mnt_mlock, PVFS, "vnode_iterate_setup", NULL); - } - mp->mnt_lflag |= MNT_LITER; - } int @@ -622,10 +616,6 @@ void vnode_iterate_clear(mount_t mp) { mp->mnt_lflag &= ~MNT_LITER; - if (mp->mnt_lflag & MNT_LITERWAIT) { - mp->mnt_lflag &= ~MNT_LITERWAIT; - wakeup(mp); - } } @@ -670,16 +660,28 @@ vnode_iterate(mount_t mp, int flags, int (*callout)(struct vnode *, void *), int vid, retval; int ret = 0; + /* + * The mount iterate mutex is held for the duration of the iteration. + * This can be done by a state flag on the mount structure but we can + * run into priority inversion issues sometimes. + * Using a mutex allows us to benefit from the priority donation + * mechanisms in the kernel for locks. This mutex should never be + * acquired in spin mode and it should be acquired before attempting to + * acquire the mount lock. + */ + mount_iterate_lock(mp); + mount_lock(mp); vnode_iterate_setup(mp); - /* it is returns 0 then there is nothing to do */ + /* If it returns 0 then there is nothing to do */ retval = vnode_iterate_prepare(mp); if (retval == 0) { vnode_iterate_clear(mp); mount_unlock(mp); + mount_iterate_unlock(mp); return(ret); } @@ -745,6 +747,7 @@ out: (void)vnode_iterate_reloadq(mp); vnode_iterate_clear(mp); mount_unlock(mp); + mount_iterate_unlock(mp); return (ret); } @@ -760,6 +763,18 @@ mount_unlock_renames(mount_t mp) lck_mtx_unlock(&mp->mnt_renamelock); } +void +mount_iterate_lock(mount_t mp) +{ + lck_mtx_lock(&mp->mnt_iter_lock); +} + +void +mount_iterate_unlock(mount_t mp) +{ + lck_mtx_unlock(&mp->mnt_iter_lock); +} + void mount_lock(mount_t mp) { @@ -1999,6 +2014,11 @@ vflush(struct mount *mp, struct vnode *skipvp, int flags) int retval; unsigned int vid; + /* + * See comments in vnode_iterate() for the rationale for this lock + */ + mount_iterate_lock(mp); + mount_lock(mp); vnode_iterate_setup(mp); /* @@ -2012,16 +2032,18 @@ vflush(struct mount *mp, struct vnode *skipvp, int flags) if (vnode_umount_preflight(mp, skipvp, flags)) { vnode_iterate_clear(mp); mount_unlock(mp); + mount_iterate_unlock(mp); return(EBUSY); } } loop: - /* it is returns 0 then there is nothing to do */ + /* If it returns 0 then there is nothing to do */ retval = vnode_iterate_prepare(mp); if (retval == 0) { vnode_iterate_clear(mp); mount_unlock(mp); + mount_iterate_unlock(mp); return(retval); } @@ -2162,6 +2184,7 @@ loop: } vnode_iterate_clear(mp); mount_unlock(mp); + mount_iterate_unlock(mp); if (busy && ((flags & FORCECLOSE)==0)) return (EBUSY); @@ -5806,8 +5829,8 @@ out: static kauth_scope_t vnode_scope; static int vnode_authorize_callback(kauth_cred_t credential, void *idata, kauth_action_t action, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3); -static int vnode_authorize_callback_int(__unused kauth_cred_t credential, __unused void *idata, kauth_action_t action, - uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3); +static int vnode_authorize_callback_int(kauth_action_t action, vfs_context_t ctx, + vnode_t vp, vnode_t dvp, int *errorp); typedef struct _vnode_authorize_context { vnode_t vp; @@ -5821,6 +5844,7 @@ typedef struct _vnode_authorize_context { #define _VAC_IN_GROUP (1<<1) #define _VAC_IS_DIR_OWNER (1<<2) #define _VAC_IN_DIR_GROUP (1<<3) +#define _VAC_NO_VNODE_POINTERS (1<<4) } *vauth_ctx; void @@ -6337,6 +6361,111 @@ vn_authorize_rmdir(vnode_t dvp, vnode_t vp, struct componentname *cnp, vfs_conte return vnode_authorize(vp, dvp, KAUTH_VNODE_DELETE, ctx); } +/* + * Authorizer for directory cloning. This does not use vnodes but instead + * uses prefilled vnode attributes from the filesystem. + * + * The same function is called to set up the attributes required, perform the + * authorization and cleanup (if required) + */ +int +vnode_attr_authorize_dir_clone(struct vnode_attr *vap, kauth_action_t action, + struct vnode_attr *dvap, __unused vnode_t sdvp, mount_t mp, + dir_clone_authorizer_op_t vattr_op, vfs_context_t ctx, + __unused void *reserved) +{ + int error; + int is_suser = vfs_context_issuser(ctx); + + if (vattr_op == OP_VATTR_SETUP) { + VATTR_INIT(vap); + + /* + * When ACL inheritence is implemented, both vap->va_acl and + * dvap->va_acl will be required (even as superuser). + */ + VATTR_WANTED(vap, va_type); + VATTR_WANTED(vap, va_mode); + VATTR_WANTED(vap, va_flags); + VATTR_WANTED(vap, va_uid); + VATTR_WANTED(vap, va_gid); + if (dvap) { + VATTR_INIT(dvap); + VATTR_WANTED(dvap, va_flags); + } + + if (!is_suser) { + /* + * If not superuser, we have to evaluate ACLs and + * need the target directory gid to set the initial + * gid of the new object. + */ + VATTR_WANTED(vap, va_acl); + if (dvap) + VATTR_WANTED(dvap, va_gid); + } + + return (0); + } else if (vattr_op == OP_VATTR_CLEANUP) { + return (0); /* Nothing to do for now */ + } + + /* dvap isn't used for authorization */ + error = vnode_attr_authorize(vap, NULL, mp, action, ctx); + + if (error) + return (error); + + /* + * vn_attribute_prepare should be able to accept attributes as well as + * vnodes but for now we do this inline. + */ + if (!is_suser) { + /* + * If the filesystem is mounted IGNORE_OWNERSHIP and an explicit + * owner is set, that owner takes ownership of all new files. + */ + if ((mp->mnt_flag & MNT_IGNORE_OWNERSHIP) && + (mp->mnt_fsowner != KAUTH_UID_NONE)) { + VATTR_SET(vap, va_uid, mp->mnt_fsowner); + } else { + /* default owner is current user */ + VATTR_SET(vap, va_uid, + kauth_cred_getuid(vfs_context_ucred(ctx))); + } + + if ((mp->mnt_flag & MNT_IGNORE_OWNERSHIP) && + (mp->mnt_fsgroup != KAUTH_GID_NONE)) { + VATTR_SET(vap, va_gid, mp->mnt_fsgroup); + } else { + /* + * default group comes from parent object, + * fallback to current user + */ + if (VATTR_IS_SUPPORTED(dvap, va_gid)) { + VATTR_SET(vap, va_gid, dvap->va_gid); + } else { + VATTR_SET(vap, va_gid, + kauth_cred_getgid(vfs_context_ucred(ctx))); + } + } + } + + /* Inherit SF_RESTRICTED bit from destination directory only */ + if (VATTR_IS_ACTIVE(vap, va_flags)) { + VATTR_SET(vap, va_flags, + ((vap->va_flags & ~SF_RESTRICTED))); /* Turn off from source */ + if (VATTR_IS_ACTIVE(dvap, va_flags)) + VATTR_SET(vap, va_flags, + vap->va_flags | (dvap->va_flags & SF_RESTRICTED)); + } else if (VATTR_IS_ACTIVE(dvap, va_flags)) { + VATTR_SET(vap, va_flags, (dvap->va_flags & SF_RESTRICTED)); + } + + return (0); +} + + /* * Authorize an operation on a vnode. * @@ -7164,9 +7293,8 @@ vnode_authorize_simple(vauth_ctx vcp, kauth_ace_rights_t acl_rights, kauth_ace_r * Check for file immutability. */ static int -vnode_authorize_checkimmutable(vnode_t vp, struct vnode_attr *vap, int rights, int ignore) +vnode_authorize_checkimmutable(mount_t mp, struct vnode_attr *vap, int rights, int ignore) { - mount_t mp; int error; int append; @@ -7175,7 +7303,7 @@ vnode_authorize_checkimmutable(vnode_t vp, struct vnode_attr *vap, int rights, i * * Sockets, fifos and devices require special handling. */ - switch(vp->v_type) { + switch(vap->va_type) { case VSOCK: case VFIFO: case VBLK: @@ -7194,7 +7322,6 @@ vnode_authorize_checkimmutable(vnode_t vp, struct vnode_attr *vap, int rights, i if (rights & KAUTH_VNODE_WRITE_RIGHTS) { /* check per-filesystem options if possible */ - mp = vp->v_mount; if (mp != NULL) { /* check for no-EA filesystems */ @@ -7211,7 +7338,7 @@ vnode_authorize_checkimmutable(vnode_t vp, struct vnode_attr *vap, int rights, i * allowable for a UF_APPEND file. */ append = 0; - if (vp->v_type == VDIR) { + if (vap->va_type == VDIR) { if ((rights & (KAUTH_VNODE_ADD_FILE | KAUTH_VNODE_ADD_SUBDIRECTORY | KAUTH_VNODE_WRITE_EXTATTRIBUTES)) == rights) append = 1; } else { @@ -7322,8 +7449,9 @@ vnode_authorize_opaque(vnode_t vp, int *resultp, kauth_action_t action, vfs_cont static int -vnode_authorize_callback(kauth_cred_t cred, void *idata, kauth_action_t action, - uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) +vnode_authorize_callback(__unused kauth_cred_t cred, __unused void *idata, + kauth_action_t action, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, + uintptr_t arg3) { vfs_context_t ctx; vnode_t cvp = NULLVP; @@ -7399,7 +7527,7 @@ vnode_authorize_callback(kauth_cred_t cred, void *idata, kauth_action_t action, goto out; } defer: - result = vnode_authorize_callback_int(cred, idata, action, arg0, arg1, arg2, arg3); + result = vnode_authorize_callback_int(action, ctx, vp, dvp, (int *)arg3); if (result == KAUTH_RESULT_ALLOW && cvp != NULLVP) { KAUTH_DEBUG("%p - caching action = %x", cvp, action); @@ -7414,20 +7542,87 @@ out: return result; } +static int +vnode_attr_authorize_internal(vauth_ctx vcp, mount_t mp, + kauth_ace_rights_t rights, int is_suser, boolean_t *found_deny, + int noimmutable, int parent_authorized_for_delete_child) +{ + int result; + + /* + * Check for immutability. + * + * In the deletion case, parent directory immutability vetoes specific + * file rights. + */ + if ((result = vnode_authorize_checkimmutable(mp, vcp->vap, rights, + noimmutable)) != 0) + goto out; + + if ((rights & KAUTH_VNODE_DELETE) && + !parent_authorized_for_delete_child) { + result = vnode_authorize_checkimmutable(mp, vcp->dvap, + KAUTH_VNODE_DELETE_CHILD, 0); + if (result) + goto out; + } + + /* + * Clear rights that have been authorized by reaching this point, bail if nothing left to + * check. + */ + rights &= ~(KAUTH_VNODE_LINKTARGET | KAUTH_VNODE_CHECKIMMUTABLE); + if (rights == 0) + goto out; + + /* + * If we're not the superuser, authorize based on file properties; + * note that even if parent_authorized_for_delete_child is TRUE, we + * need to check on the node itself. + */ + if (!is_suser) { + /* process delete rights */ + if ((rights & KAUTH_VNODE_DELETE) && + ((result = vnode_authorize_delete(vcp, parent_authorized_for_delete_child)) != 0)) + goto out; + + /* process remaining rights */ + if ((rights & ~KAUTH_VNODE_DELETE) && + (result = vnode_authorize_simple(vcp, rights, rights & KAUTH_VNODE_DELETE, found_deny)) != 0) + goto out; + } else { + /* + * Execute is only granted to root if one of the x bits is set. This check only + * makes sense if the posix mode bits are actually supported. + */ + if ((rights & KAUTH_VNODE_EXECUTE) && + (vcp->vap->va_type == VREG) && + VATTR_IS_SUPPORTED(vcp->vap, va_mode) && + !(vcp->vap->va_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { + result = EPERM; + KAUTH_DEBUG("%p DENIED - root execute requires at least one x bit in 0x%x", vp, va.va_mode); + goto out; + } + + /* Assume that there were DENYs so we don't wrongly cache KAUTH_VNODE_SEARCHBYANYONE */ + *found_deny = TRUE; + + KAUTH_DEBUG("%p ALLOWED - caller is superuser", vp); + } +out: + return (result); +} static int -vnode_authorize_callback_int(__unused kauth_cred_t unused_cred, __unused void *idata, kauth_action_t action, - uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) +vnode_authorize_callback_int(kauth_action_t action, vfs_context_t ctx, + vnode_t vp, vnode_t dvp, int *errorp) { struct _vnode_authorize_context auth_context; vauth_ctx vcp; - vfs_context_t ctx; - vnode_t vp, dvp; kauth_cred_t cred; kauth_ace_rights_t rights; struct vnode_attr va, dva; int result; - int *errorp; int noimmutable; boolean_t parent_authorized_for_delete_child = FALSE; boolean_t found_deny = FALSE; @@ -7435,10 +7630,9 @@ vnode_authorize_callback_int(__unused kauth_cred_t unused_cred, __unused void *i boolean_t is_suser = FALSE; vcp = &auth_context; - ctx = vcp->ctx = (vfs_context_t)arg0; - vp = vcp->vp = (vnode_t)arg1; - dvp = vcp->dvp = (vnode_t)arg2; - errorp = (int *)arg3; + vcp->ctx = ctx; + vcp->vp = vp; + vcp->dvp = dvp; /* * Note that we authorize against the context, not the passed cred * (the same thing anyway) @@ -7498,7 +7692,8 @@ vnode_authorize_callback_int(__unused kauth_cred_t unused_cred, __unused void *i if (vnode_cache_is_authorized(dvp, ctx, KAUTH_VNODE_DELETE_CHILD) == TRUE) parent_authorized_for_delete_child = TRUE; } else { - dvp = NULL; + vcp->dvp = NULLVP; + vcp->dvap = NULL; } /* @@ -7583,7 +7778,10 @@ vnode_authorize_callback_int(__unused kauth_cred_t unused_cred, __unused void *i KAUTH_DEBUG("%p ERROR - failed to get vnode attributes - %d", vp, result); goto out; } - if (dvp) { + VATTR_WANTED(&va, va_type); + VATTR_RETURN(&va, va_type, vnode_vtype(vp)); + + if (vcp->dvp) { VATTR_WANTED(&dva, va_mode); VATTR_WANTED(&dva, va_flags); if (!is_suser) { @@ -7591,68 +7789,16 @@ vnode_authorize_callback_int(__unused kauth_cred_t unused_cred, __unused void *i VATTR_WANTED(&dva, va_gid); VATTR_WANTED(&dva, va_acl); } - if ((result = vnode_getattr(dvp, &dva, ctx)) != 0) { + if ((result = vnode_getattr(vcp->dvp, &dva, ctx)) != 0) { KAUTH_DEBUG("%p ERROR - failed to get directory vnode attributes - %d", vp, result); goto out; } + VATTR_WANTED(&dva, va_type); + VATTR_RETURN(&dva, va_type, vnode_vtype(vcp->dvp)); } - /* - * Check for immutability. - * - * In the deletion case, parent directory immutability vetoes specific - * file rights. - */ - if ((result = vnode_authorize_checkimmutable(vp, &va, rights, noimmutable)) != 0) - goto out; - if ((rights & KAUTH_VNODE_DELETE) && - parent_authorized_for_delete_child == FALSE && - ((result = vnode_authorize_checkimmutable(dvp, &dva, KAUTH_VNODE_DELETE_CHILD, 0)) != 0)) - goto out; - - /* - * Clear rights that have been authorized by reaching this point, bail if nothing left to - * check. - */ - rights &= ~(KAUTH_VNODE_LINKTARGET | KAUTH_VNODE_CHECKIMMUTABLE); - if (rights == 0) - goto out; - - /* - * If we're not the superuser, authorize based on file properties; - * note that even if parent_authorized_for_delete_child is TRUE, we - * need to check on the node itself. - */ - if (!is_suser) { - /* process delete rights */ - if ((rights & KAUTH_VNODE_DELETE) && - ((result = vnode_authorize_delete(vcp, parent_authorized_for_delete_child)) != 0)) - goto out; - - /* process remaining rights */ - if ((rights & ~KAUTH_VNODE_DELETE) && - (result = vnode_authorize_simple(vcp, rights, rights & KAUTH_VNODE_DELETE, &found_deny)) != 0) - goto out; - } else { - - /* - * Execute is only granted to root if one of the x bits is set. This check only - * makes sense if the posix mode bits are actually supported. - */ - if ((rights & KAUTH_VNODE_EXECUTE) && - (vp->v_type == VREG) && - VATTR_IS_SUPPORTED(&va, va_mode) && - !(va.va_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { - result = EPERM; - KAUTH_DEBUG("%p DENIED - root execute requires at least one x bit in 0x%x", vp, va.va_mode); - goto out; - } - - /* Assume that there were DENYs so we don't wrongly cache KAUTH_VNODE_SEARCHBYANYONE */ - found_deny = TRUE; - - KAUTH_DEBUG("%p ALLOWED - caller is superuser", vp); - } + result = vnode_attr_authorize_internal(vcp, vp->v_mount, rights, is_suser, + &found_deny, noimmutable, parent_authorized_for_delete_child); out: if (VATTR_IS_SUPPORTED(&va, va_acl) && (va.va_acl != NULL)) kauth_acl_free(va.va_acl); @@ -7696,6 +7842,109 @@ success: return(KAUTH_RESULT_ALLOW); } +int +vnode_attr_authorize_init(struct vnode_attr *vap, struct vnode_attr *dvap, + kauth_action_t action, vfs_context_t ctx) +{ + VATTR_INIT(vap); + VATTR_WANTED(vap, va_type); + VATTR_WANTED(vap, va_mode); + VATTR_WANTED(vap, va_flags); + if (dvap) { + VATTR_INIT(dvap); + if (action & KAUTH_VNODE_DELETE) { + VATTR_WANTED(dvap, va_type); + VATTR_WANTED(dvap, va_mode); + VATTR_WANTED(dvap, va_flags); + } + } else if (action & KAUTH_VNODE_DELETE) { + return (EINVAL); + } + + if (!vfs_context_issuser(ctx)) { + VATTR_WANTED(vap, va_uid); + VATTR_WANTED(vap, va_gid); + VATTR_WANTED(vap, va_acl); + if (dvap && (action & KAUTH_VNODE_DELETE)) { + VATTR_WANTED(dvap, va_uid); + VATTR_WANTED(dvap, va_gid); + VATTR_WANTED(dvap, va_acl); + } + } + + return (0); +} + +int +vnode_attr_authorize(struct vnode_attr *vap, struct vnode_attr *dvap, mount_t mp, + kauth_action_t action, vfs_context_t ctx) +{ + struct _vnode_authorize_context auth_context; + vauth_ctx vcp; + kauth_ace_rights_t rights; + int noimmutable; + boolean_t found_deny; + boolean_t is_suser = FALSE; + int result = 0; + + vcp = &auth_context; + vcp->ctx = ctx; + vcp->vp = NULLVP; + vcp->vap = vap; + vcp->dvp = NULLVP; + vcp->dvap = dvap; + vcp->flags = vcp->flags_valid = 0; + + noimmutable = (action & KAUTH_VNODE_NOIMMUTABLE) ? 1 : 0; + rights = action & ~(KAUTH_VNODE_ACCESS | KAUTH_VNODE_NOIMMUTABLE); + + /* + * Check for read-only filesystems. + */ + if ((rights & KAUTH_VNODE_WRITE_RIGHTS) && + mp && (mp->mnt_flag & MNT_RDONLY) && + ((vap->va_type == VREG) || (vap->va_type == VDIR) || + (vap->va_type == VLNK) || (rights & KAUTH_VNODE_DELETE) || + (rights & KAUTH_VNODE_DELETE_CHILD))) { + result = EROFS; + goto out; + } + + /* + * Check for noexec filesystems. + */ + if ((rights & KAUTH_VNODE_EXECUTE) && + (vap->va_type == VREG) && mp && (mp->mnt_flag & MNT_NOEXEC)) { + result = EACCES; + goto out; + } + + if (vfs_context_issuser(ctx)) { + /* + * if we're not asking for execute permissions or modifications, + * then we're done, this action is authorized. + */ + if (!(rights & (KAUTH_VNODE_EXECUTE | KAUTH_VNODE_WRITE_RIGHTS))) + goto out; + is_suser = TRUE; + } else { + if (!VATTR_IS_SUPPORTED(vap, va_uid) || + !VATTR_IS_SUPPORTED(vap, va_gid) || + (mp && vfs_extendedsecurity(mp) && !VATTR_IS_SUPPORTED(vap, va_acl))) { + panic("vnode attrs not complete for vnode_attr_authorize\n"); + } + } + + result = vnode_attr_authorize_internal(vcp, mp, rights, is_suser, + &found_deny, noimmutable, FALSE); + + if (result == EPERM) + result = EACCES; +out: + return (result); +} + + int vnode_authattr_new(vnode_t dvp, struct vnode_attr *vap, int noauth, vfs_context_t ctx) {