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
vnode_iterate_clear(mount_t mp)
{
mp->mnt_lflag &= ~MNT_LITER;
- if (mp->mnt_lflag & MNT_LITERWAIT) {
- mp->mnt_lflag &= ~MNT_LITERWAIT;
- wakeup(mp);
- }
}
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);
}
(void)vnode_iterate_reloadq(mp);
vnode_iterate_clear(mp);
mount_unlock(mp);
+ mount_iterate_unlock(mp);
return (ret);
}
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)
{
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);
/*
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);
}
}
vnode_iterate_clear(mp);
mount_unlock(mp);
+ mount_iterate_unlock(mp);
if (busy && ((flags & FORCECLOSE)==0))
return (EBUSY);
VFSATTR_WANTED(&va, f_ffree);
VFSATTR_WANTED(&va, f_bsize);
VFSATTR_WANTED(&va, f_fssubtype);
+
+ if ((error = vfs_getattr(mp, &va, ctx)) != 0) {
+ KAUTH_DEBUG("STAT - filesystem returned error %d", error);
+ return(error);
+ }
#if CONFIG_MACF
if (eventtype == VFS_USER_EVENT) {
error = mac_mount_check_getattr(ctx, mp, &va);
return (error);
}
#endif
-
- if ((error = vfs_getattr(mp, &va, ctx)) != 0) {
- KAUTH_DEBUG("STAT - filesystem returned error %d", error);
- return(error);
- }
-
/*
* Unpack into the per-mount structure.
*
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;
#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
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.
*
* 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;
*
* Sockets, fifos and devices require special handling.
*/
- switch(vp->v_type) {
+ switch(vap->va_type) {
case VSOCK:
case VFIFO:
case VBLK:
if (rights & KAUTH_VNODE_WRITE_RIGHTS) {
/* check per-filesystem options if possible */
- mp = vp->v_mount;
if (mp != NULL) {
/* check for no-EA filesystems */
* 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 {
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;
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);
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;
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)
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;
}
/*
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) {
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);
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)
{