+ mp->mnt_vfsstat.f_bsize = mp->mnt_devblocksize; /* default from the device block size */
+ }
+ if (VFSATTR_IS_SUPPORTED(&va, f_iosize)) {
+ mp->mnt_vfsstat.f_iosize = va.f_iosize;
+ } else {
+ mp->mnt_vfsstat.f_iosize = 1024 * 1024; /* 1MB sensible I/O size */
+ }
+ if (VFSATTR_IS_SUPPORTED(&va, f_blocks)) {
+ mp->mnt_vfsstat.f_blocks = va.f_blocks;
+ }
+ if (VFSATTR_IS_SUPPORTED(&va, f_bfree)) {
+ mp->mnt_vfsstat.f_bfree = va.f_bfree;
+ }
+ if (VFSATTR_IS_SUPPORTED(&va, f_bavail)) {
+ mp->mnt_vfsstat.f_bavail = va.f_bavail;
+ }
+ if (VFSATTR_IS_SUPPORTED(&va, f_bused)) {
+ mp->mnt_vfsstat.f_bused = va.f_bused;
+ }
+ if (VFSATTR_IS_SUPPORTED(&va, f_files)) {
+ mp->mnt_vfsstat.f_files = va.f_files;
+ }
+ if (VFSATTR_IS_SUPPORTED(&va, f_ffree)) {
+ mp->mnt_vfsstat.f_ffree = va.f_ffree;
+ }
+
+ /* this is unlikely to change, but has to be queried for */
+ if (VFSATTR_IS_SUPPORTED(&va, f_fssubtype)) {
+ mp->mnt_vfsstat.f_fssubtype = va.f_fssubtype;
+ }
+
+ return 0;
+}
+
+int
+mount_list_add(mount_t mp)
+{
+ int res;
+
+ mount_list_lock();
+ if (system_inshutdown != 0) {
+ res = -1;
+ } else {
+ TAILQ_INSERT_TAIL(&mountlist, mp, mnt_list);
+ nummounts++;
+ res = 0;
+ }
+ mount_list_unlock();
+
+ return res;
+}
+
+void
+mount_list_remove(mount_t mp)
+{
+ mount_list_lock();
+ TAILQ_REMOVE(&mountlist, mp, mnt_list);
+ nummounts--;
+ mp->mnt_list.tqe_next = NULL;
+ mp->mnt_list.tqe_prev = NULL;
+ mount_list_unlock();
+}
+
+mount_t
+mount_lookupby_volfsid(int volfs_id, int withref)
+{
+ mount_t cur_mount = (mount_t)0;
+ mount_t mp;
+
+ mount_list_lock();
+ TAILQ_FOREACH(mp, &mountlist, mnt_list) {
+ if (!(mp->mnt_kern_flag & MNTK_UNMOUNT) &&
+ (mp->mnt_kern_flag & MNTK_PATH_FROM_ID) &&
+ (mp->mnt_vfsstat.f_fsid.val[0] == volfs_id)) {
+ cur_mount = mp;
+ if (withref) {
+ if (mount_iterref(cur_mount, 1)) {
+ cur_mount = (mount_t)0;
+ mount_list_unlock();
+ goto out;
+ }
+ }
+ break;
+ }
+ }
+ mount_list_unlock();
+ if (withref && (cur_mount != (mount_t)0)) {
+ mp = cur_mount;
+ if (vfs_busy(mp, LK_NOWAIT) != 0) {
+ cur_mount = (mount_t)0;
+ }
+ mount_iterdrop(mp);
+ }
+out:
+ return cur_mount;
+}
+
+mount_t
+mount_list_lookupby_fsid(fsid_t *fsid, int locked, int withref)
+{
+ mount_t retmp = (mount_t)0;
+ mount_t mp;
+
+ if (!locked) {
+ mount_list_lock();
+ }
+ TAILQ_FOREACH(mp, &mountlist, mnt_list)
+ if (mp->mnt_vfsstat.f_fsid.val[0] == fsid->val[0] &&
+ mp->mnt_vfsstat.f_fsid.val[1] == fsid->val[1]) {
+ retmp = mp;
+ if (withref) {
+ if (mount_iterref(retmp, 1)) {
+ retmp = (mount_t)0;
+ }
+ }
+ goto out;
+ }
+out:
+ if (!locked) {
+ mount_list_unlock();
+ }
+ return retmp;
+}
+
+errno_t
+vnode_lookupat(const char *path, int flags, vnode_t *vpp, vfs_context_t ctx,
+ vnode_t start_dvp)
+{
+ struct nameidata nd;
+ int error;
+ u_int32_t ndflags = 0;
+
+ if (ctx == NULL) {
+ return EINVAL;
+ }
+
+ if (flags & VNODE_LOOKUP_NOFOLLOW) {
+ ndflags = NOFOLLOW;
+ } else {
+ ndflags = FOLLOW;
+ }
+
+ if (flags & VNODE_LOOKUP_NOCROSSMOUNT) {
+ ndflags |= NOCROSSMOUNT;
+ }
+
+ if (flags & VNODE_LOOKUP_CROSSMOUNTNOWAIT) {
+ ndflags |= CN_NBMOUNTLOOK;
+ }
+
+ /* XXX AUDITVNPATH1 needed ? */
+ NDINIT(&nd, LOOKUP, OP_LOOKUP, ndflags, UIO_SYSSPACE,
+ CAST_USER_ADDR_T(path), ctx);
+
+ if (start_dvp && (path[0] != '/')) {
+ nd.ni_dvp = start_dvp;
+ nd.ni_cnd.cn_flags |= USEDVP;
+ }
+
+ if ((error = namei(&nd))) {
+ return error;
+ }
+
+ nd.ni_cnd.cn_flags &= ~USEDVP;
+
+ *vpp = nd.ni_vp;
+ nameidone(&nd);
+
+ return 0;
+}
+
+errno_t
+vnode_lookup(const char *path, int flags, vnode_t *vpp, vfs_context_t ctx)
+{
+ return vnode_lookupat(path, flags, vpp, ctx, NULLVP);
+}
+
+errno_t
+vnode_open(const char *path, int fmode, int cmode, int flags, vnode_t *vpp, vfs_context_t ctx)
+{
+ struct nameidata nd;
+ int error;
+ u_int32_t ndflags = 0;
+ int lflags = flags;
+
+ if (ctx == NULL) { /* XXX technically an error */
+ ctx = vfs_context_current();
+ }
+
+ if (fmode & O_NOFOLLOW) {
+ lflags |= VNODE_LOOKUP_NOFOLLOW;
+ }
+
+ if (lflags & VNODE_LOOKUP_NOFOLLOW) {
+ ndflags = NOFOLLOW;
+ } else {
+ ndflags = FOLLOW;
+ }
+
+ if (lflags & VNODE_LOOKUP_NOCROSSMOUNT) {
+ ndflags |= NOCROSSMOUNT;
+ }
+
+ if (lflags & VNODE_LOOKUP_CROSSMOUNTNOWAIT) {
+ ndflags |= CN_NBMOUNTLOOK;
+ }
+
+ /* XXX AUDITVNPATH1 needed ? */
+ NDINIT(&nd, LOOKUP, OP_OPEN, ndflags, UIO_SYSSPACE,
+ CAST_USER_ADDR_T(path), ctx);
+
+ if ((error = vn_open(&nd, fmode, cmode))) {
+ *vpp = NULL;
+ } else {
+ *vpp = nd.ni_vp;
+ }
+
+ return error;
+}
+
+errno_t
+vnode_close(vnode_t vp, int flags, vfs_context_t ctx)
+{
+ int error;
+
+ if (ctx == NULL) {
+ ctx = vfs_context_current();
+ }
+
+ error = vn_close(vp, flags, ctx);
+ vnode_put(vp);
+ return error;
+}
+
+errno_t
+vnode_mtime(vnode_t vp, struct timespec *mtime, vfs_context_t ctx)
+{
+ struct vnode_attr va;
+ int error;
+
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_modify_time);
+ error = vnode_getattr(vp, &va, ctx);
+ if (!error) {
+ *mtime = va.va_modify_time;
+ }
+ return error;
+}
+
+errno_t
+vnode_flags(vnode_t vp, uint32_t *flags, vfs_context_t ctx)
+{
+ struct vnode_attr va;
+ int error;
+
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_flags);
+ error = vnode_getattr(vp, &va, ctx);
+ if (!error) {
+ *flags = va.va_flags;
+ }
+ return error;
+}
+
+/*
+ * Returns: 0 Success
+ * vnode_getattr:???
+ */
+errno_t
+vnode_size(vnode_t vp, off_t *sizep, vfs_context_t ctx)
+{
+ struct vnode_attr va;
+ int error;
+
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_data_size);
+ error = vnode_getattr(vp, &va, ctx);
+ if (!error) {
+ *sizep = va.va_data_size;
+ }
+ return error;
+}
+
+errno_t
+vnode_setsize(vnode_t vp, off_t size, int ioflag, vfs_context_t ctx)
+{
+ struct vnode_attr va;
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_data_size, size);
+ va.va_vaflags = ioflag & 0xffff;
+ return vnode_setattr(vp, &va, ctx);
+}
+
+int
+vnode_setdirty(vnode_t vp)
+{
+ vnode_lock_spin(vp);
+ vp->v_flag |= VISDIRTY;
+ vnode_unlock(vp);
+ return 0;
+}
+
+int
+vnode_cleardirty(vnode_t vp)
+{
+ vnode_lock_spin(vp);
+ vp->v_flag &= ~VISDIRTY;
+ vnode_unlock(vp);
+ return 0;
+}
+
+int
+vnode_isdirty(vnode_t vp)
+{
+ int dirty;
+
+ vnode_lock_spin(vp);
+ dirty = (vp->v_flag & VISDIRTY) ? 1 : 0;
+ vnode_unlock(vp);
+
+ return dirty;
+}
+
+static int
+vn_create_reg(vnode_t dvp, vnode_t *vpp, struct nameidata *ndp, struct vnode_attr *vap, uint32_t flags, int fmode, uint32_t *statusp, vfs_context_t ctx)
+{
+ /* Only use compound VNOP for compound operation */
+ if (vnode_compound_open_available(dvp) && ((flags & VN_CREATE_DOOPEN) != 0)) {
+ *vpp = NULLVP;
+ return VNOP_COMPOUND_OPEN(dvp, vpp, ndp, O_CREAT, fmode, statusp, vap, ctx);
+ } else {
+ return VNOP_CREATE(dvp, vpp, &ndp->ni_cnd, vap, ctx);
+ }
+}
+
+/*
+ * Create a filesystem object of arbitrary type with arbitrary attributes in
+ * the spevied directory with the specified name.
+ *
+ * Parameters: dvp Pointer to the vnode of the directory
+ * in which to create the object.
+ * vpp Pointer to the area into which to
+ * return the vnode of the created object.
+ * cnp Component name pointer from the namei
+ * data structure, containing the name to
+ * use for the create object.
+ * vap Pointer to the vnode_attr structure
+ * describing the object to be created,
+ * including the type of object.
+ * flags VN_* flags controlling ACL inheritance
+ * and whether or not authorization is to
+ * be required for the operation.
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ * Implicit: *vpp Contains the vnode of the object that
+ * was created, if successful.
+ * *cnp May be modified by the underlying VFS.
+ * *vap May be modified by the underlying VFS.
+ * modified by either ACL inheritance or
+ *
+ *
+ * be modified, even if the operation is
+ *
+ *
+ * Notes: The kauth_filesec_t in 'vap', if any, is in host byte order.
+ *
+ * Modification of '*cnp' and '*vap' by the underlying VFS is
+ * strongly discouraged.
+ *
+ * XXX: This function is a 'vn_*' function; it belongs in vfs_vnops.c
+ *
+ * XXX: We should enummerate the possible errno values here, and where
+ * in the code they originated.
+ */
+errno_t
+vn_create(vnode_t dvp, vnode_t *vpp, struct nameidata *ndp, struct vnode_attr *vap, uint32_t flags, int fmode, uint32_t *statusp, vfs_context_t ctx)
+{
+ errno_t error, old_error;
+ vnode_t vp = (vnode_t)0;
+ boolean_t batched;
+ struct componentname *cnp;
+ uint32_t defaulted;
+
+ cnp = &ndp->ni_cnd;
+ error = 0;
+ batched = namei_compound_available(dvp, ndp) ? TRUE : FALSE;
+
+ KAUTH_DEBUG("%p CREATE - '%s'", dvp, cnp->cn_nameptr);
+
+ if (flags & VN_CREATE_NOINHERIT) {
+ vap->va_vaflags |= VA_NOINHERIT;
+ }
+ if (flags & VN_CREATE_NOAUTH) {
+ vap->va_vaflags |= VA_NOAUTH;
+ }
+ /*
+ * Handle ACL inheritance, initialize vap.
+ */
+ error = vn_attribute_prepare(dvp, vap, &defaulted, ctx);
+ if (error) {
+ return error;
+ }
+
+ if (vap->va_type != VREG && (fmode != 0 || (flags & VN_CREATE_DOOPEN) || statusp)) {
+ panic("Open parameters, but not a regular file.");
+ }
+ if ((fmode != 0) && ((flags & VN_CREATE_DOOPEN) == 0)) {
+ panic("Mode for open, but not trying to open...");
+ }
+
+
+ /*
+ * Create the requested node.
+ */
+ switch (vap->va_type) {
+ case VREG:
+ error = vn_create_reg(dvp, vpp, ndp, vap, flags, fmode, statusp, ctx);
+ break;
+ case VDIR:
+ error = vn_mkdir(dvp, vpp, ndp, vap, ctx);
+ break;
+ case VSOCK:
+ case VFIFO:
+ case VBLK:
+ case VCHR:
+ error = VNOP_MKNOD(dvp, vpp, cnp, vap, ctx);
+ break;
+ default:
+ panic("vnode_create: unknown vtype %d", vap->va_type);
+ }
+ if (error != 0) {
+ KAUTH_DEBUG("%p CREATE - error %d returned by filesystem", dvp, error);
+ goto out;
+ }
+
+ vp = *vpp;
+ old_error = error;
+
+#if CONFIG_MACF
+ if (!(flags & VN_CREATE_NOLABEL)) {
+ error = vnode_label(vnode_mount(vp), dvp, vp, cnp, VNODE_LABEL_CREATE, ctx);
+ if (error) {
+ goto error;
+ }
+ }
+#endif
+
+ /*
+ * If some of the requested attributes weren't handled by the VNOP,
+ * use our fallback code.
+ */
+ if (!VATTR_ALL_SUPPORTED(vap) && *vpp) {
+ KAUTH_DEBUG(" CREATE - doing fallback with ACL %p", vap->va_acl);
+ error = vnode_setattr_fallback(*vpp, vap, ctx);
+ }
+#if CONFIG_MACF
+error:
+#endif
+ if ((error != 0) && (vp != (vnode_t)0)) {
+ /* If we've done a compound open, close */
+ if (batched && (old_error == 0) && (vap->va_type == VREG)) {
+ VNOP_CLOSE(vp, fmode, ctx);
+ }
+
+ /* Need to provide notifications if a create succeeded */
+ if (!batched) {
+ *vpp = (vnode_t) 0;
+ vnode_put(vp);
+ vp = NULLVP;
+ }
+ }
+
+ /*
+ * For creation VNOPs, this is the equivalent of
+ * lookup_handle_found_vnode.
+ */
+ if (kdebug_enable && *vpp) {
+ kdebug_lookup(*vpp, cnp);
+ }
+
+out:
+ vn_attribute_cleanup(vap, defaulted);
+
+ return error;
+}
+
+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(kauth_action_t action, vfs_context_t ctx,
+ vnode_t vp, vnode_t dvp, int *errorp);
+
+typedef struct _vnode_authorize_context {
+ vnode_t vp;
+ struct vnode_attr *vap;
+ vnode_t dvp;
+ struct vnode_attr *dvap;
+ vfs_context_t ctx;
+ int flags;
+ int flags_valid;
+#define _VAC_IS_OWNER (1<<0)
+#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
+vnode_authorize_init(void)
+{
+ vnode_scope = kauth_register_scope(KAUTH_SCOPE_VNODE, vnode_authorize_callback, NULL);
+}
+
+#define VATTR_PREPARE_DEFAULTED_UID 0x1
+#define VATTR_PREPARE_DEFAULTED_GID 0x2
+#define VATTR_PREPARE_DEFAULTED_MODE 0x4
+
+int
+vn_attribute_prepare(vnode_t dvp, struct vnode_attr *vap, uint32_t *defaulted_fieldsp, vfs_context_t ctx)
+{
+ kauth_acl_t nacl = NULL, oacl = NULL;
+ int error;
+
+ /*
+ * Handle ACL inheritance.
+ */
+ if (!(vap->va_vaflags & VA_NOINHERIT) && vfs_extendedsecurity(dvp->v_mount)) {
+ /* save the original filesec */
+ if (VATTR_IS_ACTIVE(vap, va_acl)) {
+ oacl = vap->va_acl;
+ }
+
+ vap->va_acl = NULL;
+ if ((error = kauth_acl_inherit(dvp,
+ oacl,
+ &nacl,
+ vap->va_type == VDIR,
+ ctx)) != 0) {
+ KAUTH_DEBUG("%p CREATE - error %d processing inheritance", dvp, error);
+ return error;
+ }
+
+ /*
+ * If the generated ACL is NULL, then we can save ourselves some effort
+ * by clearing the active bit.
+ */
+ if (nacl == NULL) {
+ VATTR_CLEAR_ACTIVE(vap, va_acl);
+ } else {
+ vap->va_base_acl = oacl;
+ VATTR_SET(vap, va_acl, nacl);
+ }
+ }
+
+ error = vnode_authattr_new_internal(dvp, vap, (vap->va_vaflags & VA_NOAUTH), defaulted_fieldsp, ctx);
+ if (error) {
+ vn_attribute_cleanup(vap, *defaulted_fieldsp);
+ }
+
+ return error;
+}
+
+void
+vn_attribute_cleanup(struct vnode_attr *vap, uint32_t defaulted_fields)
+{
+ /*
+ * If the caller supplied a filesec in vap, it has been replaced
+ * now by the post-inheritance copy. We need to put the original back
+ * and free the inherited product.
+ */
+ kauth_acl_t nacl, oacl;
+
+ if (VATTR_IS_ACTIVE(vap, va_acl)) {
+ nacl = vap->va_acl;
+ oacl = vap->va_base_acl;
+
+ if (oacl) {
+ VATTR_SET(vap, va_acl, oacl);
+ vap->va_base_acl = NULL;
+ } else {
+ VATTR_CLEAR_ACTIVE(vap, va_acl);
+ }
+
+ if (nacl != NULL) {
+ kauth_acl_free(nacl);
+ }
+ }
+
+ if ((defaulted_fields & VATTR_PREPARE_DEFAULTED_MODE) != 0) {
+ VATTR_CLEAR_ACTIVE(vap, va_mode);
+ }
+ if ((defaulted_fields & VATTR_PREPARE_DEFAULTED_GID) != 0) {
+ VATTR_CLEAR_ACTIVE(vap, va_gid);
+ }
+ if ((defaulted_fields & VATTR_PREPARE_DEFAULTED_UID) != 0) {
+ VATTR_CLEAR_ACTIVE(vap, va_uid);
+ }
+
+ return;
+}
+
+int
+vn_authorize_unlink(vnode_t dvp, vnode_t vp, struct componentname *cnp, vfs_context_t ctx, __unused void *reserved)
+{
+#if !CONFIG_MACF
+#pragma unused(cnp)
+#endif
+ int error = 0;
+
+ /*
+ * Normally, unlinking of directories is not supported.
+ * However, some file systems may have limited support.
+ */
+ if ((vp->v_type == VDIR) &&
+ !(vp->v_mount->mnt_kern_flag & MNTK_DIR_HARDLINKS)) {
+ return EPERM; /* POSIX */
+ }
+
+ /* authorize the delete operation */
+#if CONFIG_MACF
+ if (!error) {
+ error = mac_vnode_check_unlink(ctx, dvp, vp, cnp);
+ }
+#endif /* MAC */
+ if (!error) {
+ error = vnode_authorize(vp, dvp, KAUTH_VNODE_DELETE, ctx);
+ }
+
+ return error;
+}
+
+int
+vn_authorize_open_existing(vnode_t vp, struct componentname *cnp, int fmode, vfs_context_t ctx, void *reserved)
+{
+ /* Open of existing case */
+ kauth_action_t action;
+ int error = 0;
+ if (cnp->cn_ndp == NULL) {
+ panic("NULL ndp");
+ }
+ if (reserved != NULL) {
+ panic("reserved not NULL.");
+ }
+
+#if CONFIG_MACF
+ /* XXX may do duplicate work here, but ignore that for now (idempotent) */
+ if (vfs_flags(vnode_mount(vp)) & MNT_MULTILABEL) {
+ error = vnode_label(vnode_mount(vp), NULL, vp, NULL, 0, ctx);
+ if (error) {
+ return error;
+ }
+ }
+#endif
+
+ if ((fmode & O_DIRECTORY) && vp->v_type != VDIR) {
+ return ENOTDIR;
+ }
+
+ if (vp->v_type == VSOCK && vp->v_tag != VT_FDESC) {
+ return EOPNOTSUPP; /* Operation not supported on socket */
+ }
+
+ if (vp->v_type == VLNK && (fmode & O_NOFOLLOW) != 0) {
+ return ELOOP; /* O_NOFOLLOW was specified and the target is a symbolic link */
+ }
+
+ /* disallow write operations on directories */
+ if (vnode_isdir(vp) && (fmode & (FWRITE | O_TRUNC))) {
+ return EISDIR;
+ }
+
+ if ((cnp->cn_ndp->ni_flag & NAMEI_TRAILINGSLASH)) {
+ if (vp->v_type != VDIR) {
+ return ENOTDIR;
+ }
+ }
+
+#if CONFIG_MACF
+ /* If a file being opened is a shadow file containing
+ * namedstream data, ignore the macf checks because it
+ * is a kernel internal file and access should always
+ * be allowed.
+ */
+ if (!(vnode_isshadow(vp) && vnode_isnamedstream(vp))) {
+ error = mac_vnode_check_open(ctx, vp, fmode);
+ if (error) {
+ return error;
+ }
+ }
+#endif
+
+ /* compute action to be authorized */
+ action = 0;
+ if (fmode & FREAD) {
+ action |= KAUTH_VNODE_READ_DATA;
+ }
+ if (fmode & (FWRITE | O_TRUNC)) {
+ /*
+ * If we are writing, appending, and not truncating,
+ * indicate that we are appending so that if the
+ * UF_APPEND or SF_APPEND bits are set, we do not deny
+ * the open.
+ */
+ if ((fmode & O_APPEND) && !(fmode & O_TRUNC)) {
+ action |= KAUTH_VNODE_APPEND_DATA;
+ } else {
+ action |= KAUTH_VNODE_WRITE_DATA;
+ }
+ }
+ error = vnode_authorize(vp, NULL, action, ctx);
+#if NAMEDSTREAMS
+ if (error == EACCES) {
+ /*
+ * Shadow files may exist on-disk with a different UID/GID
+ * than that of the current context. Verify that this file
+ * is really a shadow file. If it was created successfully
+ * then it should be authorized.
+ */
+ if (vnode_isshadow(vp) && vnode_isnamedstream(vp)) {
+ error = vnode_verifynamedstream(vp);
+ }
+ }
+#endif
+
+ return error;
+}
+
+int
+vn_authorize_create(vnode_t dvp, struct componentname *cnp, struct vnode_attr *vap, vfs_context_t ctx, void *reserved)
+{
+#if !CONFIG_MACF
+#pragma unused(vap)
+#endif
+ /* Creation case */
+ int error;
+
+ if (cnp->cn_ndp == NULL) {
+ panic("NULL cn_ndp");
+ }
+ if (reserved != NULL) {
+ panic("reserved not NULL.");
+ }
+
+ /* Only validate path for creation if we didn't do a complete lookup */
+ if (cnp->cn_ndp->ni_flag & NAMEI_UNFINISHED) {
+ error = lookup_validate_creation_path(cnp->cn_ndp);
+ if (error) {
+ return error;
+ }
+ }
+
+#if CONFIG_MACF
+ error = mac_vnode_check_create(ctx, dvp, cnp, vap);
+ if (error) {
+ return error;
+ }
+#endif /* CONFIG_MACF */
+
+ return vnode_authorize(dvp, NULL, KAUTH_VNODE_ADD_FILE, ctx);
+}
+
+int
+vn_authorize_rename(struct vnode *fdvp, struct vnode *fvp, struct componentname *fcnp,
+ struct vnode *tdvp, struct vnode *tvp, struct componentname *tcnp,
+ vfs_context_t ctx, void *reserved)
+{
+ return vn_authorize_renamex(fdvp, fvp, fcnp, tdvp, tvp, tcnp, ctx, 0, reserved);
+}
+
+int
+vn_authorize_renamex(struct vnode *fdvp, struct vnode *fvp, struct componentname *fcnp,
+ struct vnode *tdvp, struct vnode *tvp, struct componentname *tcnp,
+ vfs_context_t ctx, vfs_rename_flags_t flags, void *reserved)
+{
+ return vn_authorize_renamex_with_paths(fdvp, fvp, fcnp, NULL, tdvp, tvp, tcnp, NULL, ctx, flags, reserved);
+}
+
+int
+vn_authorize_renamex_with_paths(struct vnode *fdvp, struct vnode *fvp, struct componentname *fcnp, const char *from_path,
+ struct vnode *tdvp, struct vnode *tvp, struct componentname *tcnp, const char *to_path,
+ vfs_context_t ctx, vfs_rename_flags_t flags, void *reserved)
+{
+ int error = 0;
+ int moving = 0;
+ bool swap = flags & VFS_RENAME_SWAP;
+
+ if (reserved != NULL) {
+ panic("Passed something other than NULL as reserved field!");
+ }
+
+ /*
+ * Avoid renaming "." and "..".
+ *
+ * XXX No need to check for this in the FS. We should always have the leaves
+ * in VFS in this case.
+ */
+ if (fvp->v_type == VDIR &&
+ ((fdvp == fvp) ||
+ (fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') ||
+ ((fcnp->cn_flags | tcnp->cn_flags) & ISDOTDOT))) {
+ error = EINVAL;
+ goto out;
+ }
+
+ if (tvp == NULLVP && vnode_compound_rename_available(tdvp)) {
+ error = lookup_validate_creation_path(tcnp->cn_ndp);
+ if (error) {
+ goto out;
+ }
+ }
+
+ /***** <MACF> *****/
+#if CONFIG_MACF
+ error = mac_vnode_check_rename(ctx, fdvp, fvp, fcnp, tdvp, tvp, tcnp);
+ if (error) {
+ goto out;
+ }
+ if (swap) {
+ error = mac_vnode_check_rename(ctx, tdvp, tvp, tcnp, fdvp, fvp, fcnp);
+ if (error) {
+ goto out;
+ }
+ }
+#endif
+ /***** </MACF> *****/
+
+ /***** <MiscChecks> *****/
+ if (tvp != NULL) {
+ if (!swap) {
+ if (fvp->v_type == VDIR && tvp->v_type != VDIR) {
+ error = ENOTDIR;
+ goto out;
+ } else if (fvp->v_type != VDIR && tvp->v_type == VDIR) {
+ error = EISDIR;
+ goto out;
+ }
+ }
+ } else if (swap) {
+ /*
+ * Caller should have already checked this and returned
+ * ENOENT. If we send back ENOENT here, caller will retry
+ * which isn't what we want so we send back EINVAL here
+ * instead.
+ */
+ error = EINVAL;
+ goto out;
+ }
+
+ if (fvp == tdvp) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * The following edge case is caught here:
+ * (to cannot be a descendent of from)
+ *
+ * o fdvp
+ * /
+ * /
+ * o fvp
+ * \
+ * \
+ * o tdvp
+ * /
+ * /
+ * o tvp
+ */
+ if (tdvp->v_parent == fvp) {
+ error = EINVAL;
+ goto out;
+ }
+
+ if (swap && fdvp->v_parent == tvp) {
+ error = EINVAL;
+ goto out;
+ }
+ /***** </MiscChecks> *****/
+
+ /***** <Kauth> *****/
+
+ /*
+ * As part of the Kauth step, we call out to allow 3rd-party
+ * fileop notification of "about to rename". This is needed
+ * in the event that 3rd-parties need to know that the DELETE
+ * authorization is actually part of a rename. It's important
+ * that we guarantee that the DELETE call-out will always be
+ * made if the WILL_RENAME call-out is made. Another fileop
+ * call-out will be performed once the operation is completed.
+ * We can ignore the result of kauth_authorize_fileop().
+ *
+ * N.B. We are passing the vnode and *both* paths to each
+ * call; kauth_authorize_fileop() extracts the "from" path
+ * when posting a KAUTH_FILEOP_WILL_RENAME notification.
+ * As such, we only post these notifications if all of the
+ * information we need is provided.
+ */
+
+ if (swap) {
+ kauth_action_t f = 0, t = 0;
+
+ /*
+ * Directories changing parents need ...ADD_SUBDIR... to
+ * permit changing ".."
+ */
+ if (fdvp != tdvp) {
+ if (vnode_isdir(fvp)) {
+ f = KAUTH_VNODE_ADD_SUBDIRECTORY;
+ }
+ if (vnode_isdir(tvp)) {
+ t = KAUTH_VNODE_ADD_SUBDIRECTORY;
+ }
+ }
+ if (to_path != NULL) {
+ kauth_authorize_fileop(vfs_context_ucred(ctx),
+ KAUTH_FILEOP_WILL_RENAME,
+ (uintptr_t)fvp,
+ (uintptr_t)to_path);
+ }
+ error = vnode_authorize(fvp, fdvp, KAUTH_VNODE_DELETE | f, ctx);
+ if (error) {
+ goto out;
+ }
+ if (from_path != NULL) {
+ kauth_authorize_fileop(vfs_context_ucred(ctx),
+ KAUTH_FILEOP_WILL_RENAME,
+ (uintptr_t)tvp,
+ (uintptr_t)from_path);
+ }
+ error = vnode_authorize(tvp, tdvp, KAUTH_VNODE_DELETE | t, ctx);
+ if (error) {
+ goto out;
+ }
+ f = vnode_isdir(fvp) ? KAUTH_VNODE_ADD_SUBDIRECTORY : KAUTH_VNODE_ADD_FILE;
+ t = vnode_isdir(tvp) ? KAUTH_VNODE_ADD_SUBDIRECTORY : KAUTH_VNODE_ADD_FILE;
+ if (fdvp == tdvp) {
+ error = vnode_authorize(fdvp, NULL, f | t, ctx);
+ } else {
+ error = vnode_authorize(fdvp, NULL, t, ctx);
+ if (error) {
+ goto out;
+ }
+ error = vnode_authorize(tdvp, NULL, f, ctx);
+ }
+ if (error) {
+ goto out;
+ }
+ } else {
+ error = 0;
+ if ((tvp != NULL) && vnode_isdir(tvp)) {
+ if (tvp != fdvp) {
+ moving = 1;
+ }
+ } else if (tdvp != fdvp) {
+ moving = 1;
+ }
+
+ /*
+ * must have delete rights to remove the old name even in
+ * the simple case of fdvp == tdvp.
+ *
+ * If fvp is a directory, and we are changing it's parent,
+ * then we also need rights to rewrite its ".." entry as well.
+ */
+ if (to_path != NULL) {
+ kauth_authorize_fileop(vfs_context_ucred(ctx),
+ KAUTH_FILEOP_WILL_RENAME,
+ (uintptr_t)fvp,
+ (uintptr_t)to_path);
+ }
+ if (vnode_isdir(fvp)) {
+ if ((error = vnode_authorize(fvp, fdvp, KAUTH_VNODE_DELETE | KAUTH_VNODE_ADD_SUBDIRECTORY, ctx)) != 0) {
+ goto out;
+ }
+ } else {
+ if ((error = vnode_authorize(fvp, fdvp, KAUTH_VNODE_DELETE, ctx)) != 0) {
+ goto out;
+ }
+ }
+ if (moving) {
+ /* moving into tdvp or tvp, must have rights to add */
+ if ((error = vnode_authorize(((tvp != NULL) && vnode_isdir(tvp)) ? tvp : tdvp,
+ NULL,
+ vnode_isdir(fvp) ? KAUTH_VNODE_ADD_SUBDIRECTORY : KAUTH_VNODE_ADD_FILE,
+ ctx)) != 0) {
+ goto out;
+ }
+ } else {
+ /* node staying in same directory, must be allowed to add new name */
+ if ((error = vnode_authorize(fdvp, NULL,
+ vnode_isdir(fvp) ? KAUTH_VNODE_ADD_SUBDIRECTORY : KAUTH_VNODE_ADD_FILE, ctx)) != 0) {
+ goto out;
+ }
+ }
+ /* overwriting tvp */
+ if ((tvp != NULL) && !vnode_isdir(tvp) &&
+ ((error = vnode_authorize(tvp, tdvp, KAUTH_VNODE_DELETE, ctx)) != 0)) {
+ goto out;
+ }
+ }
+
+ /***** </Kauth> *****/
+
+ /* XXX more checks? */
+out:
+ return error;
+}
+
+int
+vn_authorize_mkdir(vnode_t dvp, struct componentname *cnp, struct vnode_attr *vap, vfs_context_t ctx, void *reserved)
+{
+#if !CONFIG_MACF
+#pragma unused(vap)
+#endif
+ int error;
+
+ if (reserved != NULL) {
+ panic("reserved not NULL in vn_authorize_mkdir()");
+ }
+
+ /* XXX A hack for now, to make shadow files work */
+ if (cnp->cn_ndp == NULL) {
+ return 0;
+ }
+
+ if (vnode_compound_mkdir_available(dvp)) {
+ error = lookup_validate_creation_path(cnp->cn_ndp);
+ if (error) {
+ goto out;
+ }
+ }
+
+#if CONFIG_MACF
+ error = mac_vnode_check_create(ctx,
+ dvp, cnp, vap);
+ if (error) {
+ goto out;
+ }
+#endif
+
+ /* authorize addition of a directory to the parent */
+ if ((error = vnode_authorize(dvp, NULL, KAUTH_VNODE_ADD_SUBDIRECTORY, ctx)) != 0) {
+ goto out;
+ }
+
+out:
+ return error;
+}
+
+int
+vn_authorize_rmdir(vnode_t dvp, vnode_t vp, struct componentname *cnp, vfs_context_t ctx, void *reserved)
+{
+#if CONFIG_MACF
+ int error;
+#else
+#pragma unused(cnp)
+#endif
+ if (reserved != NULL) {
+ panic("Non-NULL reserved argument to vn_authorize_rmdir()");
+ }
+
+ if (vp->v_type != VDIR) {
+ /*
+ * rmdir only deals with directories
+ */
+ return ENOTDIR;
+ }
+
+ if (dvp == vp) {
+ /*
+ * No rmdir "." please.
+ */
+ return EINVAL;
+ }
+
+#if CONFIG_MACF
+ error = mac_vnode_check_unlink(ctx, dvp,
+ vp, cnp);
+ if (error) {
+ return error;
+ }
+#endif
+
+ 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, uint32_t flags, 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);
+ }
+ } else if (dvap && (flags & VNODE_CLONEFILE_NOOWNERCOPY)) {
+ 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 || (flags & VNODE_CLONEFILE_NOOWNERCOPY)) {
+ /*
+ * 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 & ~(UF_DATAVAULT | SF_RESTRICTED)))); /* Turn off from source */
+ if (VATTR_IS_ACTIVE(dvap, va_flags)) {
+ VATTR_SET(vap, va_flags,
+ vap->va_flags | (dvap->va_flags & (UF_DATAVAULT | SF_RESTRICTED)));
+ }
+ } else if (VATTR_IS_ACTIVE(dvap, va_flags)) {
+ VATTR_SET(vap, va_flags, (dvap->va_flags & (UF_DATAVAULT | SF_RESTRICTED)));
+ }
+
+ return 0;
+}
+
+
+/*
+ * Authorize an operation on a vnode.
+ *
+ * This is KPI, but here because it needs vnode_scope.
+ *
+ * Returns: 0 Success
+ * kauth_authorize_action:EPERM ...
+ * xlate => EACCES Permission denied
+ * kauth_authorize_action:0 Success
+ * kauth_authorize_action: Depends on callback return; this is
+ * usually only vnode_authorize_callback(),
+ * but may include other listerners, if any
+ * exist.
+ * EROFS
+ * EACCES
+ * EPERM
+ * ???
+ */
+int
+vnode_authorize(vnode_t vp, vnode_t dvp, kauth_action_t action, vfs_context_t ctx)
+{
+ int error, result;
+
+ /*
+ * We can't authorize against a dead vnode; allow all operations through so that
+ * the correct error can be returned.
+ */
+ if (vp->v_type == VBAD) {
+ return 0;
+ }
+
+ error = 0;
+ result = kauth_authorize_action(vnode_scope, vfs_context_ucred(ctx), action,
+ (uintptr_t)ctx, (uintptr_t)vp, (uintptr_t)dvp, (uintptr_t)&error);
+ if (result == EPERM) { /* traditional behaviour */
+ result = EACCES;
+ }
+ /* did the lower layers give a better error return? */
+ if ((result != 0) && (error != 0)) {
+ return error;
+ }
+ return result;
+}
+
+/*
+ * Test for vnode immutability.
+ *
+ * The 'append' flag is set when the authorization request is constrained
+ * to operations which only request the right to append to a file.
+ *
+ * The 'ignore' flag is set when an operation modifying the immutability flags
+ * is being authorized. We check the system securelevel to determine which
+ * immutability flags we can ignore.
+ */
+static int
+vnode_immutable(struct vnode_attr *vap, int append, int ignore)
+{
+ int mask;
+
+ /* start with all bits precluding the operation */
+ mask = IMMUTABLE | APPEND;
+
+ /* if appending only, remove the append-only bits */
+ if (append) {
+ mask &= ~APPEND;
+ }
+
+ /* ignore only set when authorizing flags changes */
+ if (ignore) {
+ if (securelevel <= 0) {
+ /* in insecure state, flags do not inhibit changes */
+ mask = 0;
+ } else {
+ /* in secure state, user flags don't inhibit */
+ mask &= ~(UF_IMMUTABLE | UF_APPEND);
+ }
+ }
+ KAUTH_DEBUG("IMMUTABLE - file flags 0x%x mask 0x%x append = %d ignore = %d", vap->va_flags, mask, append, ignore);
+ if ((vap->va_flags & mask) != 0) {
+ return EPERM;
+ }
+ return 0;
+}
+
+static int
+vauth_node_owner(struct vnode_attr *vap, kauth_cred_t cred)
+{
+ int result;
+
+ /* default assumption is not-owner */
+ result = 0;
+
+ /*
+ * If the filesystem has given us a UID, we treat this as authoritative.
+ */
+ if (vap && VATTR_IS_SUPPORTED(vap, va_uid)) {
+ result = (vap->va_uid == kauth_cred_getuid(cred)) ? 1 : 0;
+ }
+ /* we could test the owner UUID here if we had a policy for it */
+
+ return result;
+}
+
+/*
+ * vauth_node_group
+ *
+ * Description: Ask if a cred is a member of the group owning the vnode object
+ *
+ * Parameters: vap vnode attribute
+ * vap->va_gid group owner of vnode object
+ * cred credential to check
+ * ismember pointer to where to put the answer
+ * idontknow Return this if we can't get an answer
+ *
+ * Returns: 0 Success
+ * idontknow Can't get information
+ * kauth_cred_ismember_gid:? Error from kauth subsystem
+ * kauth_cred_ismember_gid:? Error from kauth subsystem
+ */
+static int
+vauth_node_group(struct vnode_attr *vap, kauth_cred_t cred, int *ismember, int idontknow)
+{
+ int error;
+ int result;
+
+ error = 0;
+ result = 0;
+
+ /*
+ * The caller is expected to have asked the filesystem for a group
+ * at some point prior to calling this function. The answer may
+ * have been that there is no group ownership supported for the
+ * vnode object, in which case we return
+ */
+ if (vap && VATTR_IS_SUPPORTED(vap, va_gid)) {
+ error = kauth_cred_ismember_gid(cred, vap->va_gid, &result);
+ /*
+ * Credentials which are opted into external group membership
+ * resolution which are not known to the external resolver
+ * will result in an ENOENT error. We translate this into
+ * the appropriate 'idontknow' response for our caller.
+ *
+ * XXX We do not make a distinction here between an ENOENT
+ * XXX arising from a response from the external resolver,
+ * XXX and an ENOENT which is internally generated. This is
+ * XXX a deficiency of the published kauth_cred_ismember_gid()
+ * XXX KPI which can not be overcome without new KPI. For
+ * XXX all currently known cases, however, this wil result
+ * XXX in correct behaviour.
+ */
+ if (error == ENOENT) {
+ error = idontknow;
+ }
+ }
+ /*
+ * XXX We could test the group UUID here if we had a policy for it,
+ * XXX but this is problematic from the perspective of synchronizing
+ * XXX group UUID and POSIX GID ownership of a file and keeping the
+ * XXX values coherent over time. The problem is that the local
+ * XXX system will vend transient group UUIDs for unknown POSIX GID
+ * XXX values, and these are not persistent, whereas storage of values
+ * XXX is persistent. One potential solution to this is a local
+ * XXX (persistent) replica of remote directory entries and vended
+ * XXX local ids in a local directory server (think in terms of a
+ * XXX caching DNS server).
+ */
+
+ if (!error) {
+ *ismember = result;
+ }
+ return error;
+}
+
+static int
+vauth_file_owner(vauth_ctx vcp)
+{
+ int result;
+
+ if (vcp->flags_valid & _VAC_IS_OWNER) {
+ result = (vcp->flags & _VAC_IS_OWNER) ? 1 : 0;
+ } else {
+ result = vauth_node_owner(vcp->vap, vcp->ctx->vc_ucred);
+
+ /* cache our result */
+ vcp->flags_valid |= _VAC_IS_OWNER;
+ if (result) {
+ vcp->flags |= _VAC_IS_OWNER;
+ } else {
+ vcp->flags &= ~_VAC_IS_OWNER;
+ }
+ }
+ return result;
+}
+
+
+/*
+ * vauth_file_ingroup
+ *
+ * Description: Ask if a user is a member of the group owning the directory
+ *
+ * Parameters: vcp The vnode authorization context that
+ * contains the user and directory info
+ * vcp->flags_valid Valid flags
+ * vcp->flags Flags values
+ * vcp->vap File vnode attributes
+ * vcp->ctx VFS Context (for user)
+ * ismember pointer to where to put the answer
+ * idontknow Return this if we can't get an answer
+ *
+ * Returns: 0 Success
+ * vauth_node_group:? Error from vauth_node_group()
+ *
+ * Implicit returns: *ismember 0 The user is not a group member
+ * 1 The user is a group member
+ */
+static int
+vauth_file_ingroup(vauth_ctx vcp, int *ismember, int idontknow)
+{
+ int error;
+
+ /* Check for a cached answer first, to avoid the check if possible */
+ if (vcp->flags_valid & _VAC_IN_GROUP) {
+ *ismember = (vcp->flags & _VAC_IN_GROUP) ? 1 : 0;
+ error = 0;
+ } else {
+ /* Otherwise, go look for it */
+ error = vauth_node_group(vcp->vap, vcp->ctx->vc_ucred, ismember, idontknow);
+
+ if (!error) {
+ /* cache our result */
+ vcp->flags_valid |= _VAC_IN_GROUP;
+ if (*ismember) {
+ vcp->flags |= _VAC_IN_GROUP;
+ } else {
+ vcp->flags &= ~_VAC_IN_GROUP;
+ }
+ }
+ }
+ return error;
+}
+
+static int
+vauth_dir_owner(vauth_ctx vcp)
+{
+ int result;
+
+ if (vcp->flags_valid & _VAC_IS_DIR_OWNER) {
+ result = (vcp->flags & _VAC_IS_DIR_OWNER) ? 1 : 0;
+ } else {
+ result = vauth_node_owner(vcp->dvap, vcp->ctx->vc_ucred);
+
+ /* cache our result */
+ vcp->flags_valid |= _VAC_IS_DIR_OWNER;
+ if (result) {
+ vcp->flags |= _VAC_IS_DIR_OWNER;
+ } else {
+ vcp->flags &= ~_VAC_IS_DIR_OWNER;
+ }
+ }
+ return result;
+}
+
+/*
+ * vauth_dir_ingroup
+ *
+ * Description: Ask if a user is a member of the group owning the directory
+ *
+ * Parameters: vcp The vnode authorization context that
+ * contains the user and directory info
+ * vcp->flags_valid Valid flags
+ * vcp->flags Flags values
+ * vcp->dvap Dir vnode attributes
+ * vcp->ctx VFS Context (for user)
+ * ismember pointer to where to put the answer
+ * idontknow Return this if we can't get an answer
+ *
+ * Returns: 0 Success
+ * vauth_node_group:? Error from vauth_node_group()
+ *
+ * Implicit returns: *ismember 0 The user is not a group member
+ * 1 The user is a group member
+ */
+static int
+vauth_dir_ingroup(vauth_ctx vcp, int *ismember, int idontknow)
+{
+ int error;
+
+ /* Check for a cached answer first, to avoid the check if possible */
+ if (vcp->flags_valid & _VAC_IN_DIR_GROUP) {
+ *ismember = (vcp->flags & _VAC_IN_DIR_GROUP) ? 1 : 0;
+ error = 0;
+ } else {
+ /* Otherwise, go look for it */
+ error = vauth_node_group(vcp->dvap, vcp->ctx->vc_ucred, ismember, idontknow);
+
+ if (!error) {
+ /* cache our result */
+ vcp->flags_valid |= _VAC_IN_DIR_GROUP;
+ if (*ismember) {
+ vcp->flags |= _VAC_IN_DIR_GROUP;
+ } else {
+ vcp->flags &= ~_VAC_IN_DIR_GROUP;
+ }
+ }
+ }
+ return error;
+}
+
+/*
+ * Test the posix permissions in (vap) to determine whether (credential)
+ * may perform (action)
+ */
+static int
+vnode_authorize_posix(vauth_ctx vcp, int action, int on_dir)
+{
+ struct vnode_attr *vap;
+ int needed, error, owner_ok, group_ok, world_ok, ismember;
+#ifdef KAUTH_DEBUG_ENABLE
+ const char *where = "uninitialized";
+# define _SETWHERE(c) where = c;
+#else
+# define _SETWHERE(c)
+#endif
+
+ /* checking file or directory? */
+ if (on_dir) {
+ vap = vcp->dvap;
+ } else {
+ vap = vcp->vap;
+ }
+
+ error = 0;
+
+ /*
+ * We want to do as little work here as possible. So first we check
+ * which sets of permissions grant us the access we need, and avoid checking
+ * whether specific permissions grant access when more generic ones would.
+ */
+
+ /* owner permissions */
+ needed = 0;
+ if (action & VREAD) {
+ needed |= S_IRUSR;
+ }
+ if (action & VWRITE) {
+ needed |= S_IWUSR;
+ }
+ if (action & VEXEC) {
+ needed |= S_IXUSR;
+ }
+ owner_ok = (needed & vap->va_mode) == needed;
+
+ /* group permissions */
+ needed = 0;
+ if (action & VREAD) {
+ needed |= S_IRGRP;
+ }
+ if (action & VWRITE) {
+ needed |= S_IWGRP;
+ }
+ if (action & VEXEC) {
+ needed |= S_IXGRP;
+ }
+ group_ok = (needed & vap->va_mode) == needed;
+
+ /* world permissions */
+ needed = 0;
+ if (action & VREAD) {
+ needed |= S_IROTH;
+ }
+ if (action & VWRITE) {
+ needed |= S_IWOTH;
+ }
+ if (action & VEXEC) {
+ needed |= S_IXOTH;
+ }
+ world_ok = (needed & vap->va_mode) == needed;
+
+ /* If granted/denied by all three, we're done */
+ if (owner_ok && group_ok && world_ok) {
+ _SETWHERE("all");
+ goto out;
+ }
+ if (!owner_ok && !group_ok && !world_ok) {
+ _SETWHERE("all");
+ error = EACCES;
+ goto out;
+ }
+
+ /* Check ownership (relatively cheap) */
+ if ((on_dir && vauth_dir_owner(vcp)) ||
+ (!on_dir && vauth_file_owner(vcp))) {
+ _SETWHERE("user");
+ if (!owner_ok) {
+ error = EACCES;
+ }
+ goto out;
+ }
+
+ /* Not owner; if group and world both grant it we're done */
+ if (group_ok && world_ok) {
+ _SETWHERE("group/world");
+ goto out;
+ }
+ if (!group_ok && !world_ok) {
+ _SETWHERE("group/world");
+ error = EACCES;
+ goto out;
+ }
+
+ /* Check group membership (most expensive) */
+ ismember = 0; /* Default to allow, if the target has no group owner */
+
+ /*
+ * In the case we can't get an answer about the user from the call to
+ * vauth_dir_ingroup() or vauth_file_ingroup(), we want to fail on
+ * the side of caution, rather than simply granting access, or we will
+ * fail to correctly implement exclusion groups, so we set the third
+ * parameter on the basis of the state of 'group_ok'.
+ */
+ if (on_dir) {
+ error = vauth_dir_ingroup(vcp, &ismember, (!group_ok ? EACCES : 0));
+ } else {
+ error = vauth_file_ingroup(vcp, &ismember, (!group_ok ? EACCES : 0));
+ }
+ if (error) {
+ if (!group_ok) {
+ ismember = 1;
+ }
+ error = 0;
+ }
+ if (ismember) {
+ _SETWHERE("group");
+ if (!group_ok) {
+ error = EACCES;
+ }
+ goto out;
+ }
+
+ /* Not owner, not in group, use world result */
+ _SETWHERE("world");
+ if (!world_ok) {
+ error = EACCES;
+ }
+
+ /* FALLTHROUGH */
+
+out:
+ KAUTH_DEBUG("%p %s - posix %s permissions : need %s%s%s %x have %s%s%s%s%s%s%s%s%s UID = %d file = %d,%d",
+ vcp->vp, (error == 0) ? "ALLOWED" : "DENIED", where,
+ (action & VREAD) ? "r" : "-",
+ (action & VWRITE) ? "w" : "-",
+ (action & VEXEC) ? "x" : "-",
+ needed,
+ (vap->va_mode & S_IRUSR) ? "r" : "-",
+ (vap->va_mode & S_IWUSR) ? "w" : "-",
+ (vap->va_mode & S_IXUSR) ? "x" : "-",
+ (vap->va_mode & S_IRGRP) ? "r" : "-",
+ (vap->va_mode & S_IWGRP) ? "w" : "-",
+ (vap->va_mode & S_IXGRP) ? "x" : "-",
+ (vap->va_mode & S_IROTH) ? "r" : "-",
+ (vap->va_mode & S_IWOTH) ? "w" : "-",
+ (vap->va_mode & S_IXOTH) ? "x" : "-",
+ kauth_cred_getuid(vcp->ctx->vc_ucred),
+ on_dir ? vcp->dvap->va_uid : vcp->vap->va_uid,
+ on_dir ? vcp->dvap->va_gid : vcp->vap->va_gid);
+ return error;
+}
+
+/*
+ * Authorize the deletion of the node vp from the directory dvp.
+ *
+ * We assume that:
+ * - Neither the node nor the directory are immutable.
+ * - The user is not the superuser.
+ *
+ * The precedence of factors for authorizing or denying delete for a credential
+ *
+ * 1) Explicit ACE on the node. (allow or deny DELETE)
+ * 2) Explicit ACE on the directory (allow or deny DELETE_CHILD).
+ *
+ * If there are conflicting ACEs on the node and the directory, the node
+ * ACE wins.
+ *
+ * 3) Sticky bit on the directory.
+ * Deletion is not permitted if the directory is sticky and the caller is
+ * not owner of the node or directory. The sticky bit rules are like a deny
+ * delete ACE except lower in priority than ACL's either allowing or denying
+ * delete.
+ *
+ * 4) POSIX permisions on the directory.
+ *
+ * As an optimization, we cache whether or not delete child is permitted
+ * on directories. This enables us to skip directory ACL and POSIX checks
+ * as we already have the result from those checks. However, we always check the
+ * node ACL and, if the directory has the sticky bit set, we always check its
+ * ACL (even for a directory with an authorized delete child). Furthermore,
+ * caching the delete child authorization is independent of the sticky bit
+ * being set as it is only applicable in determining whether the node can be
+ * deleted or not.
+ */
+static int
+vnode_authorize_delete(vauth_ctx vcp, boolean_t cached_delete_child)
+{
+ struct vnode_attr *vap = vcp->vap;
+ struct vnode_attr *dvap = vcp->dvap;
+ kauth_cred_t cred = vcp->ctx->vc_ucred;
+ struct kauth_acl_eval eval;
+ int error, ismember;
+
+ /* Check the ACL on the node first */
+ if (VATTR_IS_NOT(vap, va_acl, NULL)) {
+ eval.ae_requested = KAUTH_VNODE_DELETE;
+ eval.ae_acl = &vap->va_acl->acl_ace[0];
+ eval.ae_count = vap->va_acl->acl_entrycount;
+ eval.ae_options = 0;
+ if (vauth_file_owner(vcp)) {
+ eval.ae_options |= KAUTH_AEVAL_IS_OWNER;
+ }
+ /*
+ * We use ENOENT as a marker to indicate we could not get
+ * information in order to delay evaluation until after we
+ * have the ACL evaluation answer. Previously, we would
+ * always deny the operation at this point.
+ */
+ if ((error = vauth_file_ingroup(vcp, &ismember, ENOENT)) != 0 && error != ENOENT) {
+ return error;
+ }
+ if (error == ENOENT) {
+ eval.ae_options |= KAUTH_AEVAL_IN_GROUP_UNKNOWN;
+ } else if (ismember) {
+ eval.ae_options |= KAUTH_AEVAL_IN_GROUP;
+ }
+ eval.ae_exp_gall = KAUTH_VNODE_GENERIC_ALL_BITS;
+ eval.ae_exp_gread = KAUTH_VNODE_GENERIC_READ_BITS;
+ eval.ae_exp_gwrite = KAUTH_VNODE_GENERIC_WRITE_BITS;
+ eval.ae_exp_gexec = KAUTH_VNODE_GENERIC_EXECUTE_BITS;
+
+ if ((error = kauth_acl_evaluate(cred, &eval)) != 0) {
+ KAUTH_DEBUG("%p ERROR during ACL processing - %d", vcp->vp, error);
+ return error;
+ }
+
+ switch (eval.ae_result) {
+ case KAUTH_RESULT_DENY:
+ KAUTH_DEBUG("%p DENIED - denied by ACL", vcp->vp);
+ return EACCES;
+ case KAUTH_RESULT_ALLOW:
+ KAUTH_DEBUG("%p ALLOWED - granted by ACL", vcp->vp);
+ return 0;
+ case KAUTH_RESULT_DEFER:
+ default:
+ /* Defer to directory */
+ KAUTH_DEBUG("%p DEFERRED - by file ACL", vcp->vp);
+ break;
+ }
+ }
+
+ /*
+ * Without a sticky bit, a previously authorized delete child is
+ * sufficient to authorize this delete.
+ *
+ * If the sticky bit is set, a directory ACL which allows delete child
+ * overrides a (potential) sticky bit deny. The authorized delete child
+ * cannot tell us if it was authorized because of an explicit delete
+ * child allow ACE or because of POSIX permisions so we have to check
+ * the directory ACL everytime if the directory has a sticky bit.
+ */
+ if (!(dvap->va_mode & S_ISTXT) && cached_delete_child) {
+ KAUTH_DEBUG("%p ALLOWED - granted by directory ACL or POSIX permissions and no sticky bit on directory", vcp->vp);
+ return 0;
+ }
+
+ /* check the ACL on the directory */
+ if (VATTR_IS_NOT(dvap, va_acl, NULL)) {
+ eval.ae_requested = KAUTH_VNODE_DELETE_CHILD;
+ eval.ae_acl = &dvap->va_acl->acl_ace[0];
+ eval.ae_count = dvap->va_acl->acl_entrycount;
+ eval.ae_options = 0;
+ if (vauth_dir_owner(vcp)) {
+ eval.ae_options |= KAUTH_AEVAL_IS_OWNER;
+ }
+ /*
+ * We use ENOENT as a marker to indicate we could not get
+ * information in order to delay evaluation until after we
+ * have the ACL evaluation answer. Previously, we would
+ * always deny the operation at this point.
+ */
+ if ((error = vauth_dir_ingroup(vcp, &ismember, ENOENT)) != 0 && error != ENOENT) {
+ return error;
+ }
+ if (error == ENOENT) {
+ eval.ae_options |= KAUTH_AEVAL_IN_GROUP_UNKNOWN;
+ } else if (ismember) {
+ eval.ae_options |= KAUTH_AEVAL_IN_GROUP;
+ }
+ eval.ae_exp_gall = KAUTH_VNODE_GENERIC_ALL_BITS;
+ eval.ae_exp_gread = KAUTH_VNODE_GENERIC_READ_BITS;
+ eval.ae_exp_gwrite = KAUTH_VNODE_GENERIC_WRITE_BITS;
+ eval.ae_exp_gexec = KAUTH_VNODE_GENERIC_EXECUTE_BITS;
+
+ /*
+ * If there is no entry, we are going to defer to other
+ * authorization mechanisms.
+ */
+ error = kauth_acl_evaluate(cred, &eval);
+
+ if (error != 0) {
+ KAUTH_DEBUG("%p ERROR during ACL processing - %d", vcp->vp, error);
+ return error;
+ }
+ switch (eval.ae_result) {
+ case KAUTH_RESULT_DENY:
+ KAUTH_DEBUG("%p DENIED - denied by directory ACL", vcp->vp);
+ return EACCES;
+ case KAUTH_RESULT_ALLOW:
+ KAUTH_DEBUG("%p ALLOWED - granted by directory ACL", vcp->vp);
+ if (!cached_delete_child && vcp->dvp) {
+ vnode_cache_authorized_action(vcp->dvp,
+ vcp->ctx, KAUTH_VNODE_DELETE_CHILD);
+ }
+ return 0;
+ case KAUTH_RESULT_DEFER:
+ default:
+ /* Deferred by directory ACL */
+ KAUTH_DEBUG("%p DEFERRED - directory ACL", vcp->vp);
+ break;
+ }
+ }
+
+ /*
+ * From this point, we can't explicitly allow and if we reach the end
+ * of the function without a denial, then the delete is authorized.
+ */
+ if (!cached_delete_child) {
+ if (vnode_authorize_posix(vcp, VWRITE, 1 /* on_dir */) != 0) {
+ KAUTH_DEBUG("%p DENIED - denied by posix permisssions", vcp->vp);
+ return EACCES;
+ }
+ /*
+ * Cache the authorized action on the vnode if allowed by the
+ * directory ACL or POSIX permissions. It is correct to cache
+ * this action even if sticky bit would deny deleting the node.
+ */
+ if (vcp->dvp) {
+ vnode_cache_authorized_action(vcp->dvp, vcp->ctx,
+ KAUTH_VNODE_DELETE_CHILD);
+ }
+ }
+
+ /* enforce sticky bit behaviour */
+ if ((dvap->va_mode & S_ISTXT) && !vauth_file_owner(vcp) && !vauth_dir_owner(vcp)) {
+ KAUTH_DEBUG("%p DENIED - sticky bit rules (user %d file %d dir %d)",
+ vcp->vp, cred->cr_posix.cr_uid, vap->va_uid, dvap->va_uid);
+ return EACCES;
+ }
+
+ /* not denied, must be OK */
+ return 0;
+}
+
+
+/*
+ * Authorize an operation based on the node's attributes.
+ */
+static int
+vnode_authorize_simple(vauth_ctx vcp, kauth_ace_rights_t acl_rights, kauth_ace_rights_t preauth_rights, boolean_t *found_deny)
+{
+ struct vnode_attr *vap = vcp->vap;
+ kauth_cred_t cred = vcp->ctx->vc_ucred;
+ struct kauth_acl_eval eval;
+ int error, ismember;
+ mode_t posix_action;
+
+ /*
+ * If we are the file owner, we automatically have some rights.
+ *
+ * Do we need to expand this to support group ownership?
+ */
+ if (vauth_file_owner(vcp)) {
+ acl_rights &= ~(KAUTH_VNODE_WRITE_SECURITY);
+ }
+
+ /*
+ * If we are checking both TAKE_OWNERSHIP and WRITE_SECURITY, we can
+ * mask the latter. If TAKE_OWNERSHIP is requested the caller is about to
+ * change ownership to themselves, and WRITE_SECURITY is implicitly
+ * granted to the owner. We need to do this because at this point
+ * WRITE_SECURITY may not be granted as the caller is not currently
+ * the owner.
+ */
+ if ((acl_rights & KAUTH_VNODE_TAKE_OWNERSHIP) &&
+ (acl_rights & KAUTH_VNODE_WRITE_SECURITY)) {
+ acl_rights &= ~KAUTH_VNODE_WRITE_SECURITY;
+ }
+
+ if (acl_rights == 0) {
+ KAUTH_DEBUG("%p ALLOWED - implicit or no rights required", vcp->vp);
+ return 0;
+ }
+
+ /* if we have an ACL, evaluate it */
+ if (VATTR_IS_NOT(vap, va_acl, NULL)) {
+ eval.ae_requested = acl_rights;
+ eval.ae_acl = &vap->va_acl->acl_ace[0];
+ eval.ae_count = vap->va_acl->acl_entrycount;
+ eval.ae_options = 0;
+ if (vauth_file_owner(vcp)) {
+ eval.ae_options |= KAUTH_AEVAL_IS_OWNER;
+ }
+ /*
+ * We use ENOENT as a marker to indicate we could not get
+ * information in order to delay evaluation until after we
+ * have the ACL evaluation answer. Previously, we would
+ * always deny the operation at this point.
+ */
+ if ((error = vauth_file_ingroup(vcp, &ismember, ENOENT)) != 0 && error != ENOENT) {
+ return error;
+ }
+ if (error == ENOENT) {
+ eval.ae_options |= KAUTH_AEVAL_IN_GROUP_UNKNOWN;
+ } else if (ismember) {
+ eval.ae_options |= KAUTH_AEVAL_IN_GROUP;
+ }
+ eval.ae_exp_gall = KAUTH_VNODE_GENERIC_ALL_BITS;
+ eval.ae_exp_gread = KAUTH_VNODE_GENERIC_READ_BITS;
+ eval.ae_exp_gwrite = KAUTH_VNODE_GENERIC_WRITE_BITS;
+ eval.ae_exp_gexec = KAUTH_VNODE_GENERIC_EXECUTE_BITS;
+
+ if ((error = kauth_acl_evaluate(cred, &eval)) != 0) {
+ KAUTH_DEBUG("%p ERROR during ACL processing - %d", vcp->vp, error);
+ return error;
+ }
+
+ switch (eval.ae_result) {
+ case KAUTH_RESULT_DENY:
+ KAUTH_DEBUG("%p DENIED - by ACL", vcp->vp);
+ return EACCES; /* deny, deny, counter-allege */
+ case KAUTH_RESULT_ALLOW:
+ KAUTH_DEBUG("%p ALLOWED - all rights granted by ACL", vcp->vp);
+ return 0;
+ case KAUTH_RESULT_DEFER:
+ default:
+ /* Effectively the same as !delete_child_denied */
+ KAUTH_DEBUG("%p DEFERRED - directory ACL", vcp->vp);
+ break;
+ }
+
+ *found_deny = eval.ae_found_deny;
+
+ /* fall through and evaluate residual rights */
+ } else {
+ /* no ACL, everything is residual */
+ eval.ae_residual = acl_rights;
+ }
+
+ /*
+ * Grant residual rights that have been pre-authorized.
+ */
+ eval.ae_residual &= ~preauth_rights;
+
+ /*
+ * We grant WRITE_ATTRIBUTES to the owner if it hasn't been denied.
+ */
+ if (vauth_file_owner(vcp)) {
+ eval.ae_residual &= ~KAUTH_VNODE_WRITE_ATTRIBUTES;
+ }
+
+ if (eval.ae_residual == 0) {
+ KAUTH_DEBUG("%p ALLOWED - rights already authorized", vcp->vp);
+ return 0;
+ }
+
+ /*
+ * Bail if we have residual rights that can't be granted by posix permissions,
+ * or aren't presumed granted at this point.
+ *
+ * XXX these can be collapsed for performance
+ */
+ if (eval.ae_residual & KAUTH_VNODE_CHANGE_OWNER) {
+ KAUTH_DEBUG("%p DENIED - CHANGE_OWNER not permitted", vcp->vp);
+ return EACCES;
+ }
+ if (eval.ae_residual & KAUTH_VNODE_WRITE_SECURITY) {
+ KAUTH_DEBUG("%p DENIED - WRITE_SECURITY not permitted", vcp->vp);
+ return EACCES;
+ }
+
+#if DIAGNOSTIC
+ if (eval.ae_residual & KAUTH_VNODE_DELETE) {
+ panic("vnode_authorize: can't be checking delete permission here");
+ }
+#endif
+
+ /*
+ * Compute the fallback posix permissions that will satisfy the remaining
+ * rights.
+ */
+ posix_action = 0;
+ if (eval.ae_residual & (KAUTH_VNODE_READ_DATA |
+ KAUTH_VNODE_LIST_DIRECTORY |
+ KAUTH_VNODE_READ_EXTATTRIBUTES)) {
+ posix_action |= VREAD;
+ }
+ if (eval.ae_residual & (KAUTH_VNODE_WRITE_DATA |
+ KAUTH_VNODE_ADD_FILE |
+ KAUTH_VNODE_ADD_SUBDIRECTORY |
+ KAUTH_VNODE_DELETE_CHILD |
+ KAUTH_VNODE_WRITE_ATTRIBUTES |
+ KAUTH_VNODE_WRITE_EXTATTRIBUTES)) {
+ posix_action |= VWRITE;
+ }
+ if (eval.ae_residual & (KAUTH_VNODE_EXECUTE |
+ KAUTH_VNODE_SEARCH)) {
+ posix_action |= VEXEC;
+ }
+
+ if (posix_action != 0) {
+ return vnode_authorize_posix(vcp, posix_action, 0 /* !on_dir */);
+ } else {
+ KAUTH_DEBUG("%p ALLOWED - residual rights %s%s%s%s%s%s%s%s%s%s%s%s%s%s granted due to no posix mapping",
+ vcp->vp,
+ (eval.ae_residual & KAUTH_VNODE_READ_DATA)
+ ? vnode_isdir(vcp->vp) ? " LIST_DIRECTORY" : " READ_DATA" : "",
+ (eval.ae_residual & KAUTH_VNODE_WRITE_DATA)
+ ? vnode_isdir(vcp->vp) ? " ADD_FILE" : " WRITE_DATA" : "",
+ (eval.ae_residual & KAUTH_VNODE_EXECUTE)
+ ? vnode_isdir(vcp->vp) ? " SEARCH" : " EXECUTE" : "",
+ (eval.ae_residual & KAUTH_VNODE_DELETE)
+ ? " DELETE" : "",
+ (eval.ae_residual & KAUTH_VNODE_APPEND_DATA)
+ ? vnode_isdir(vcp->vp) ? " ADD_SUBDIRECTORY" : " APPEND_DATA" : "",
+ (eval.ae_residual & KAUTH_VNODE_DELETE_CHILD)
+ ? " DELETE_CHILD" : "",
+ (eval.ae_residual & KAUTH_VNODE_READ_ATTRIBUTES)
+ ? " READ_ATTRIBUTES" : "",
+ (eval.ae_residual & KAUTH_VNODE_WRITE_ATTRIBUTES)
+ ? " WRITE_ATTRIBUTES" : "",
+ (eval.ae_residual & KAUTH_VNODE_READ_EXTATTRIBUTES)
+ ? " READ_EXTATTRIBUTES" : "",
+ (eval.ae_residual & KAUTH_VNODE_WRITE_EXTATTRIBUTES)
+ ? " WRITE_EXTATTRIBUTES" : "",
+ (eval.ae_residual & KAUTH_VNODE_READ_SECURITY)
+ ? " READ_SECURITY" : "",
+ (eval.ae_residual & KAUTH_VNODE_WRITE_SECURITY)
+ ? " WRITE_SECURITY" : "",
+ (eval.ae_residual & KAUTH_VNODE_CHECKIMMUTABLE)
+ ? " CHECKIMMUTABLE" : "",
+ (eval.ae_residual & KAUTH_VNODE_CHANGE_OWNER)
+ ? " CHANGE_OWNER" : "");
+ }
+
+ /*
+ * Lack of required Posix permissions implies no reason to deny access.
+ */
+ return 0;
+}
+
+/*
+ * Check for file immutability.
+ */
+static int
+vnode_authorize_checkimmutable(mount_t mp, struct vnode_attr *vap, int rights, int ignore)
+{
+ int error;
+ int append;
+
+ /*
+ * Perform immutability checks for operations that change data.
+ *
+ * Sockets, fifos and devices require special handling.
+ */
+ switch (vap->va_type) {
+ case VSOCK:
+ case VFIFO:
+ case VBLK:
+ case VCHR:
+ /*
+ * Writing to these nodes does not change the filesystem data,
+ * so forget that it's being tried.
+ */
+ rights &= ~KAUTH_VNODE_WRITE_DATA;
+ break;
+ default:
+ break;
+ }
+
+ error = 0;
+ if (rights & KAUTH_VNODE_WRITE_RIGHTS) {
+ /* check per-filesystem options if possible */
+ if (mp != NULL) {
+ /* check for no-EA filesystems */
+ if ((rights & KAUTH_VNODE_WRITE_EXTATTRIBUTES) &&
+ (vfs_flags(mp) & MNT_NOUSERXATTR)) {
+ KAUTH_DEBUG("%p DENIED - filesystem disallowed extended attributes", vap);
+ error = EACCES; /* User attributes disabled */
+ goto out;
+ }
+ }
+
+ /*
+ * check for file immutability. first, check if the requested rights are
+ * allowable for a UF_APPEND file.
+ */
+ append = 0;
+ if (vap->va_type == VDIR) {
+ if ((rights & (KAUTH_VNODE_ADD_FILE | KAUTH_VNODE_ADD_SUBDIRECTORY | KAUTH_VNODE_WRITE_EXTATTRIBUTES)) == rights) {
+ append = 1;
+ }
+ } else {
+ if ((rights & (KAUTH_VNODE_APPEND_DATA | KAUTH_VNODE_WRITE_EXTATTRIBUTES)) == rights) {
+ append = 1;
+ }
+ }
+ if ((error = vnode_immutable(vap, append, ignore)) != 0) {
+ KAUTH_DEBUG("%p DENIED - file is immutable", vap);
+ goto out;
+ }
+ }
+out:
+ return error;
+}
+
+/*
+ * Handle authorization actions for filesystems that advertise that the
+ * server will be enforcing.
+ *
+ * Returns: 0 Authorization should be handled locally
+ * 1 Authorization was handled by the FS
+ *
+ * Note: Imputed returns will only occur if the authorization request
+ * was handled by the FS.
+ *
+ * Imputed: *resultp, modified Return code from FS when the request is
+ * handled by the FS.
+ * VNOP_ACCESS:???
+ * VNOP_OPEN:???
+ */
+static int
+vnode_authorize_opaque(vnode_t vp, int *resultp, kauth_action_t action, vfs_context_t ctx)
+{
+ int error;
+
+ /*
+ * If the vp is a device node, socket or FIFO it actually represents a local
+ * endpoint, so we need to handle it locally.
+ */
+ switch (vp->v_type) {
+ case VBLK:
+ case VCHR:
+ case VSOCK:
+ case VFIFO:
+ return 0;
+ default:
+ break;
+ }
+
+ /*
+ * In the advisory request case, if the filesystem doesn't think it's reliable
+ * we will attempt to formulate a result ourselves based on VNOP_GETATTR data.
+ */
+ if ((action & KAUTH_VNODE_ACCESS) && !vfs_authopaqueaccess(vp->v_mount)) {
+ return 0;
+ }
+
+ /*
+ * Let the filesystem have a say in the matter. It's OK for it to not implemnent
+ * VNOP_ACCESS, as most will authorise inline with the actual request.
+ */
+ if ((error = VNOP_ACCESS(vp, action, ctx)) != ENOTSUP) {
+ *resultp = error;
+ KAUTH_DEBUG("%p DENIED - opaque filesystem VNOP_ACCESS denied access", vp);
+ return 1;
+ }
+
+ /*
+ * Typically opaque filesystems do authorisation in-line, but exec is a special case. In
+ * order to be reasonably sure that exec will be permitted, we try a bit harder here.
+ */
+ if ((action & KAUTH_VNODE_EXECUTE) && (vp->v_type == VREG)) {
+ /* try a VNOP_OPEN for readonly access */
+ if ((error = VNOP_OPEN(vp, FREAD, ctx)) != 0) {
+ *resultp = error;
+ KAUTH_DEBUG("%p DENIED - EXECUTE denied because file could not be opened readonly", vp);
+ return 1;
+ }
+ VNOP_CLOSE(vp, FREAD, ctx);
+ }
+
+ /*
+ * We don't have any reason to believe that the request has to be denied at this point,
+ * so go ahead and allow it.
+ */
+ *resultp = 0;
+ KAUTH_DEBUG("%p ALLOWED - bypassing access check for non-local filesystem", vp);
+ return 1;
+}
+
+
+
+
+/*
+ * Returns: KAUTH_RESULT_ALLOW
+ * KAUTH_RESULT_DENY
+ *
+ * Imputed: *arg3, modified Error code in the deny case
+ * EROFS Read-only file system
+ * EACCES Permission denied
+ * EPERM Operation not permitted [no execute]
+ * vnode_getattr:ENOMEM Not enough space [only if has filesec]
+ * vnode_getattr:???
+ * vnode_authorize_opaque:*arg2 ???
+ * vnode_authorize_checkimmutable:???
+ * vnode_authorize_delete:???
+ * vnode_authorize_simple:???
+ */
+
+
+static int
+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;
+ vnode_t vp, dvp;
+ int result = KAUTH_RESULT_DENY;
+ int parent_iocount = 0;
+ int parent_action; /* In case we need to use namedstream's data fork for cached rights*/
+
+ ctx = (vfs_context_t)arg0;
+ vp = (vnode_t)arg1;
+ dvp = (vnode_t)arg2;
+
+ /*
+ * if there are 2 vnodes passed in, we don't know at
+ * this point which rights to look at based on the
+ * combined action being passed in... defer until later...
+ * otherwise check the kauth 'rights' cache hung
+ * off of the vnode we're interested in... if we've already
+ * been granted the right we're currently interested in,
+ * we can just return success... otherwise we'll go through
+ * the process of authorizing the requested right(s)... if that
+ * succeeds, we'll add the right(s) to the cache.
+ * VNOP_SETATTR and VNOP_SETXATTR will invalidate this cache
+ */
+ if (dvp && vp) {
+ goto defer;
+ }
+ if (dvp) {
+ cvp = dvp;
+ } else {
+ /*
+ * For named streams on local-authorization volumes, rights are cached on the parent;
+ * authorization is determined by looking at the parent's properties anyway, so storing
+ * on the parent means that we don't recompute for the named stream and that if
+ * we need to flush rights (e.g. on VNOP_SETATTR()) we don't need to track down the
+ * stream to flush its cache separately. If we miss in the cache, then we authorize
+ * as if there were no cached rights (passing the named stream vnode and desired rights to
+ * vnode_authorize_callback_int()).
+ *
+ * On an opaquely authorized volume, we don't know the relationship between the
+ * data fork's properties and the rights granted on a stream. Thus, named stream vnodes
+ * on such a volume are authorized directly (rather than using the parent) and have their
+ * own caches. When a named stream vnode is created, we mark the parent as having a named
+ * stream. On a VNOP_SETATTR() for the parent that may invalidate cached authorization, we
+ * find the stream and flush its cache.
+ */
+ if (vnode_isnamedstream(vp) && (!vfs_authopaque(vp->v_mount))) {
+ cvp = vnode_getparent(vp);
+ if (cvp != NULLVP) {
+ parent_iocount = 1;
+ } else {
+ cvp = NULL;
+ goto defer; /* If we can't use the parent, take the slow path */
+ }
+
+ /* Have to translate some actions */
+ parent_action = action;
+ if (parent_action & KAUTH_VNODE_READ_DATA) {
+ parent_action &= ~KAUTH_VNODE_READ_DATA;
+ parent_action |= KAUTH_VNODE_READ_EXTATTRIBUTES;
+ }
+ if (parent_action & KAUTH_VNODE_WRITE_DATA) {
+ parent_action &= ~KAUTH_VNODE_WRITE_DATA;
+ parent_action |= KAUTH_VNODE_WRITE_EXTATTRIBUTES;
+ }
+ } else {
+ cvp = vp;
+ }
+ }
+
+ if (vnode_cache_is_authorized(cvp, ctx, parent_iocount ? parent_action : action) == TRUE) {
+ result = KAUTH_RESULT_ALLOW;
+ goto out;
+ }
+defer:
+ 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);
+ vnode_cache_authorized_action(cvp, ctx, action);
+ }
+
+out:
+ if (parent_iocount) {
+ vnode_put(cvp);
+ }
+
+ 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", vcp, vcp->vap->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", vcp);
+ }
+out:
+ return result;
+}
+
+static int
+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;
+ kauth_cred_t cred;
+ kauth_ace_rights_t rights;
+ struct vnode_attr va, dva;
+ int result;
+ int noimmutable;
+ boolean_t parent_authorized_for_delete_child = FALSE;
+ boolean_t found_deny = FALSE;
+ boolean_t parent_ref = FALSE;
+ boolean_t is_suser = FALSE;
+
+ vcp = &auth_context;
+ vcp->ctx = ctx;
+ vcp->vp = vp;
+ vcp->dvp = dvp;
+ /*
+ * Note that we authorize against the context, not the passed cred
+ * (the same thing anyway)
+ */
+ cred = ctx->vc_ucred;
+
+ VATTR_INIT(&va);
+ vcp->vap = &va;
+ VATTR_INIT(&dva);
+ vcp->dvap = &dva;
+
+ vcp->flags = vcp->flags_valid = 0;
+
+#if DIAGNOSTIC
+ if ((ctx == NULL) || (vp == NULL) || (cred == NULL)) {
+ panic("vnode_authorize: bad arguments (context %p vp %p cred %p)", ctx, vp, cred);
+ }
+#endif
+
+ KAUTH_DEBUG("%p AUTH - %s %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s on %s '%s' (0x%x:%p/%p)",
+ vp, vfs_context_proc(ctx)->p_comm,
+ (action & KAUTH_VNODE_ACCESS) ? "access" : "auth",
+ (action & KAUTH_VNODE_READ_DATA) ? vnode_isdir(vp) ? " LIST_DIRECTORY" : " READ_DATA" : "",
+ (action & KAUTH_VNODE_WRITE_DATA) ? vnode_isdir(vp) ? " ADD_FILE" : " WRITE_DATA" : "",
+ (action & KAUTH_VNODE_EXECUTE) ? vnode_isdir(vp) ? " SEARCH" : " EXECUTE" : "",
+ (action & KAUTH_VNODE_DELETE) ? " DELETE" : "",
+ (action & KAUTH_VNODE_APPEND_DATA) ? vnode_isdir(vp) ? " ADD_SUBDIRECTORY" : " APPEND_DATA" : "",
+ (action & KAUTH_VNODE_DELETE_CHILD) ? " DELETE_CHILD" : "",
+ (action & KAUTH_VNODE_READ_ATTRIBUTES) ? " READ_ATTRIBUTES" : "",
+ (action & KAUTH_VNODE_WRITE_ATTRIBUTES) ? " WRITE_ATTRIBUTES" : "",
+ (action & KAUTH_VNODE_READ_EXTATTRIBUTES) ? " READ_EXTATTRIBUTES" : "",
+ (action & KAUTH_VNODE_WRITE_EXTATTRIBUTES) ? " WRITE_EXTATTRIBUTES" : "",
+ (action & KAUTH_VNODE_READ_SECURITY) ? " READ_SECURITY" : "",
+ (action & KAUTH_VNODE_WRITE_SECURITY) ? " WRITE_SECURITY" : "",
+ (action & KAUTH_VNODE_CHANGE_OWNER) ? " CHANGE_OWNER" : "",
+ (action & KAUTH_VNODE_NOIMMUTABLE) ? " (noimmutable)" : "",
+ vnode_isdir(vp) ? "directory" : "file",
+ vp->v_name ? vp->v_name : "<NULL>", action, vp, dvp);
+
+ /*
+ * Extract the control bits from the action, everything else is
+ * requested rights.
+ */
+ noimmutable = (action & KAUTH_VNODE_NOIMMUTABLE) ? 1 : 0;
+ rights = action & ~(KAUTH_VNODE_ACCESS | KAUTH_VNODE_NOIMMUTABLE);
+
+ if (rights & KAUTH_VNODE_DELETE) {
+#if DIAGNOSTIC
+ if (dvp == NULL) {
+ panic("vnode_authorize: KAUTH_VNODE_DELETE test requires a directory");
+ }
+#endif
+ /*
+ * check to see if we've already authorized the parent
+ * directory for deletion of its children... if so, we
+ * can skip a whole bunch of work... we will still have to
+ * authorize that this specific child can be removed
+ */
+ if (vnode_cache_is_authorized(dvp, ctx, KAUTH_VNODE_DELETE_CHILD) == TRUE) {
+ parent_authorized_for_delete_child = TRUE;
+ }
+ } else {
+ vcp->dvp = NULLVP;
+ vcp->dvap = NULL;
+ }
+
+ /*
+ * Check for read-only filesystems.
+ */
+ if ((rights & KAUTH_VNODE_WRITE_RIGHTS) &&
+ (vp->v_mount->mnt_flag & MNT_RDONLY) &&
+ ((vp->v_type == VREG) || (vp->v_type == VDIR) ||
+ (vp->v_type == VLNK) || (vp->v_type == VCPLX) ||
+ (rights & KAUTH_VNODE_DELETE) || (rights & KAUTH_VNODE_DELETE_CHILD))) {
+ result = EROFS;
+ goto out;
+ }
+
+ /*
+ * Check for noexec filesystems.
+ */
+ if ((rights & KAUTH_VNODE_EXECUTE) && (vp->v_type == VREG) && (vp->v_mount->mnt_flag & MNT_NOEXEC)) {
+ result = EACCES;
+ goto out;
+ }
+
+ /*
+ * Handle cases related to filesystems with non-local enforcement.
+ * This call can return 0, in which case we will fall through to perform a
+ * check based on VNOP_GETATTR data. Otherwise it returns 1 and sets
+ * an appropriate result, at which point we can return immediately.
+ */
+ if ((vp->v_mount->mnt_kern_flag & MNTK_AUTH_OPAQUE) && vnode_authorize_opaque(vp, &result, action, ctx)) {
+ goto out;
+ }
+
+ /*
+ * If the vnode is a namedstream (extended attribute) data vnode (eg.
+ * a resource fork), *_DATA becomes *_EXTATTRIBUTES.
+ */
+ if (vnode_isnamedstream(vp)) {
+ if (rights & KAUTH_VNODE_READ_DATA) {
+ rights &= ~KAUTH_VNODE_READ_DATA;
+ rights |= KAUTH_VNODE_READ_EXTATTRIBUTES;
+ }
+ if (rights & KAUTH_VNODE_WRITE_DATA) {
+ rights &= ~KAUTH_VNODE_WRITE_DATA;
+ rights |= KAUTH_VNODE_WRITE_EXTATTRIBUTES;
+ }
+
+ /*
+ * Point 'vp' to the namedstream's parent for ACL checking
+ */
+ if ((vp->v_parent != NULL) &&
+ (vget_internal(vp->v_parent, 0, VNODE_NODEAD | VNODE_DRAINO) == 0)) {
+ parent_ref = TRUE;
+ vcp->vp = vp = vp->v_parent;
+ }
+ }
+
+ 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 success;
+ }
+
+ is_suser = TRUE;
+ }
+
+ /*
+ * Get vnode attributes and extended security information for the vnode
+ * and directory if required.
+ *
+ * If we're root we only want mode bits and flags for checking
+ * execute and immutability.
+ */
+ VATTR_WANTED(&va, va_mode);
+ VATTR_WANTED(&va, va_flags);
+ if (!is_suser) {
+ VATTR_WANTED(&va, va_uid);
+ VATTR_WANTED(&va, va_gid);
+ VATTR_WANTED(&va, va_acl);
+ }
+ if ((result = vnode_getattr(vp, &va, ctx)) != 0) {
+ KAUTH_DEBUG("%p ERROR - failed to get vnode attributes - %d", vp, result);
+ goto out;
+ }
+ 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_uid);
+ VATTR_WANTED(&dva, va_gid);
+ VATTR_WANTED(&dva, va_acl);
+ }
+ 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));
+ }
+
+ 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);
+ }
+ if (VATTR_IS_SUPPORTED(&dva, va_acl) && (dva.va_acl != NULL)) {
+ kauth_acl_free(dva.va_acl);
+ }
+
+ if (result) {
+ if (parent_ref) {
+ vnode_put(vp);
+ }
+ *errorp = result;
+ KAUTH_DEBUG("%p DENIED - auth denied", vp);
+ return KAUTH_RESULT_DENY;
+ }
+ if ((rights & KAUTH_VNODE_SEARCH) && found_deny == FALSE && vp->v_type == VDIR) {
+ /*
+ * if we were successfully granted the right to search this directory
+ * and there were NO ACL DENYs for search and the posix permissions also don't
+ * deny execute, we can synthesize a global right that allows anyone to
+ * traverse this directory during a pathname lookup without having to
+ * match the credential associated with this cache of rights.
+ *
+ * Note that we can correctly cache KAUTH_VNODE_SEARCHBYANYONE
+ * only if we actually check ACLs which we don't for root. As
+ * a workaround, the lookup fast path checks for root.
+ */
+ if (!VATTR_IS_SUPPORTED(&va, va_mode) ||
+ ((va.va_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) ==
+ (S_IXUSR | S_IXGRP | S_IXOTH))) {
+ vnode_cache_authorized_action(vp, ctx, KAUTH_VNODE_SEARCHBYANYONE);
+ }
+ }
+success:
+ if (parent_ref) {
+ vnode_put(vp);
+ }
+
+ /*
+ * Note that this implies that we will allow requests for no rights, as well as
+ * for rights that we do not recognise. There should be none of these.
+ */
+ KAUTH_DEBUG("%p ALLOWED - auth granted", vp);
+ 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)
+{
+ return vnode_authattr_new_internal(dvp, vap, noauth, NULL, ctx);
+}
+
+/*
+ * Check that the attribute information in vattr can be legally applied to
+ * a new file by the context.
+ */
+static int
+vnode_authattr_new_internal(vnode_t dvp, struct vnode_attr *vap, int noauth, uint32_t *defaulted_fieldsp, vfs_context_t ctx)
+{
+ int error;
+ int has_priv_suser, ismember, defaulted_owner, defaulted_group, defaulted_mode;
+ uint32_t inherit_flags;
+ kauth_cred_t cred;
+ guid_t changer;
+ mount_t dmp;
+ struct vnode_attr dva;
+
+ error = 0;
+
+ if (defaulted_fieldsp) {
+ *defaulted_fieldsp = 0;
+ }
+
+ defaulted_owner = defaulted_group = defaulted_mode = 0;
+
+ inherit_flags = 0;
+
+ /*
+ * Require that the filesystem support extended security to apply any.
+ */
+ if (!vfs_extendedsecurity(dvp->v_mount) &&
+ (VATTR_IS_ACTIVE(vap, va_acl) || VATTR_IS_ACTIVE(vap, va_uuuid) || VATTR_IS_ACTIVE(vap, va_guuid))) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * Default some fields.
+ */
+ dmp = dvp->v_mount;
+
+ /*
+ * If the filesystem is mounted IGNORE_OWNERSHIP and an explicit owner is set, that
+ * owner takes ownership of all new files.
+ */
+ if ((dmp->mnt_flag & MNT_IGNORE_OWNERSHIP) && (dmp->mnt_fsowner != KAUTH_UID_NONE)) {
+ VATTR_SET(vap, va_uid, dmp->mnt_fsowner);
+ defaulted_owner = 1;
+ } else {
+ if (!VATTR_IS_ACTIVE(vap, va_uid)) {
+ /* default owner is current user */
+ VATTR_SET(vap, va_uid, kauth_cred_getuid(vfs_context_ucred(ctx)));
+ defaulted_owner = 1;
+ }
+ }
+
+ /*
+ * We need the dvp's va_flags and *may* need the gid of the directory,
+ * we ask for both here.
+ */
+ VATTR_INIT(&dva);
+ VATTR_WANTED(&dva, va_gid);
+ VATTR_WANTED(&dva, va_flags);
+ if ((error = vnode_getattr(dvp, &dva, ctx)) != 0) {
+ goto out;
+ }
+
+ /*
+ * If the filesystem is mounted IGNORE_OWNERSHIP and an explicit grouo is set, that
+ * group takes ownership of all new files.
+ */
+ if ((dmp->mnt_flag & MNT_IGNORE_OWNERSHIP) && (dmp->mnt_fsgroup != KAUTH_GID_NONE)) {
+ VATTR_SET(vap, va_gid, dmp->mnt_fsgroup);
+ defaulted_group = 1;
+ } else {
+ if (!VATTR_IS_ACTIVE(vap, va_gid)) {
+ /* default group comes from parent object, fallback to current user */
+ if (VATTR_IS_SUPPORTED(&dva, va_gid)) {
+ VATTR_SET(vap, va_gid, dva.va_gid);
+ } else {
+ VATTR_SET(vap, va_gid, kauth_cred_getgid(vfs_context_ucred(ctx)));
+ }
+ defaulted_group = 1;
+ }
+ }
+
+ if (!VATTR_IS_ACTIVE(vap, va_flags)) {
+ VATTR_SET(vap, va_flags, 0);
+ }
+
+ /* Determine if SF_RESTRICTED should be inherited from the parent
+ * directory. */
+ if (VATTR_IS_SUPPORTED(&dva, va_flags)) {
+ inherit_flags = dva.va_flags & (UF_DATAVAULT | SF_RESTRICTED);
+ }
+
+ /* default mode is everything, masked with current umask */
+ if (!VATTR_IS_ACTIVE(vap, va_mode)) {
+ VATTR_SET(vap, va_mode, ACCESSPERMS & ~vfs_context_proc(ctx)->p_fd->fd_cmask);
+ KAUTH_DEBUG("ATTR - defaulting new file mode to %o from umask %o", vap->va_mode, vfs_context_proc(ctx)->p_fd->fd_cmask);
+ defaulted_mode = 1;
+ }
+ /* set timestamps to now */
+ if (!VATTR_IS_ACTIVE(vap, va_create_time)) {
+ nanotime(&vap->va_create_time);
+ VATTR_SET_ACTIVE(vap, va_create_time);
+ }
+
+ /*
+ * Check for attempts to set nonsensical fields.
+ */
+ if (vap->va_active & ~VNODE_ATTR_NEWOBJ) {
+ error = EINVAL;
+ KAUTH_DEBUG("ATTR - ERROR - attempt to set unsupported new-file attributes %llx",
+ vap->va_active & ~VNODE_ATTR_NEWOBJ);
+ goto out;
+ }
+
+ /*
+ * Quickly check for the applicability of any enforcement here.
+ * Tests below maintain the integrity of the local security model.
+ */
+ if (vfs_authopaque(dvp->v_mount)) {
+ goto out;
+ }
+
+ /*
+ * We need to know if the caller is the superuser, or if the work is
+ * otherwise already authorised.
+ */
+ cred = vfs_context_ucred(ctx);
+ if (noauth) {
+ /* doing work for the kernel */
+ has_priv_suser = 1;
+ } else {
+ has_priv_suser = vfs_context_issuser(ctx);
+ }
+
+
+ if (VATTR_IS_ACTIVE(vap, va_flags)) {
+ vap->va_flags &= ~SF_SYNTHETIC;
+ if (has_priv_suser) {
+ if ((vap->va_flags & (UF_SETTABLE | SF_SETTABLE)) != vap->va_flags) {
+ error = EPERM;
+ KAUTH_DEBUG(" DENIED - superuser attempt to set illegal flag(s)");
+ goto out;
+ }
+ } else {
+ if ((vap->va_flags & UF_SETTABLE) != vap->va_flags) {
+ error = EPERM;
+ KAUTH_DEBUG(" DENIED - user attempt to set illegal flag(s)");
+ goto out;
+ }
+ }
+ }
+
+ /* if not superuser, validate legality of new-item attributes */
+ if (!has_priv_suser) {
+ if (!defaulted_mode && VATTR_IS_ACTIVE(vap, va_mode)) {
+ /* setgid? */
+ if (vap->va_mode & S_ISGID) {
+ if ((error = kauth_cred_ismember_gid(cred, vap->va_gid, &ismember)) != 0) {
+ KAUTH_DEBUG("ATTR - ERROR: got %d checking for membership in %d", error, vap->va_gid);
+ goto out;
+ }
+ if (!ismember) {
+ KAUTH_DEBUG(" DENIED - can't set SGID bit, not a member of %d", vap->va_gid);
+ error = EPERM;
+ goto out;
+ }
+ }
+
+ /* setuid? */
+ if ((vap->va_mode & S_ISUID) && (vap->va_uid != kauth_cred_getuid(cred))) {
+ KAUTH_DEBUG("ATTR - ERROR: illegal attempt to set the setuid bit");
+ error = EPERM;
+ goto out;
+ }
+ }
+ if (!defaulted_owner && (vap->va_uid != kauth_cred_getuid(cred))) {
+ KAUTH_DEBUG(" DENIED - cannot create new item owned by %d", vap->va_uid);
+ error = EPERM;
+ goto out;
+ }
+ if (!defaulted_group) {
+ if ((error = kauth_cred_ismember_gid(cred, vap->va_gid, &ismember)) != 0) {
+ KAUTH_DEBUG(" ERROR - got %d checking for membership in %d", error, vap->va_gid);
+ goto out;
+ }
+ if (!ismember) {
+ KAUTH_DEBUG(" DENIED - cannot create new item with group %d - not a member", vap->va_gid);
+ error = EPERM;
+ goto out;
+ }
+ }
+
+ /* initialising owner/group UUID */
+ if (VATTR_IS_ACTIVE(vap, va_uuuid)) {
+ if ((error = kauth_cred_getguid(cred, &changer)) != 0) {
+ KAUTH_DEBUG(" ERROR - got %d trying to get caller UUID", error);
+ /* XXX ENOENT here - no GUID - should perhaps become EPERM */
+ goto out;
+ }
+ if (!kauth_guid_equal(&vap->va_uuuid, &changer)) {
+ KAUTH_DEBUG(" ERROR - cannot create item with supplied owner UUID - not us");
+ error = EPERM;
+ goto out;
+ }
+ }
+ if (VATTR_IS_ACTIVE(vap, va_guuid)) {
+ if ((error = kauth_cred_ismember_guid(cred, &vap->va_guuid, &ismember)) != 0) {
+ KAUTH_DEBUG(" ERROR - got %d trying to check group membership", error);
+ goto out;
+ }
+ if (!ismember) {
+ KAUTH_DEBUG(" ERROR - cannot create item with supplied group UUID - not a member");
+ error = EPERM;
+ goto out;
+ }
+ }
+ }
+out:
+ if (inherit_flags) {
+ /* Apply SF_RESTRICTED to the file if its parent directory was
+ * restricted. This is done at the end so that root is not
+ * required if this flag is only set due to inheritance. */
+ VATTR_SET(vap, va_flags, (vap->va_flags | inherit_flags));
+ }
+ if (defaulted_fieldsp) {
+ if (defaulted_mode) {
+ *defaulted_fieldsp |= VATTR_PREPARE_DEFAULTED_MODE;
+ }
+ if (defaulted_group) {
+ *defaulted_fieldsp |= VATTR_PREPARE_DEFAULTED_GID;
+ }
+ if (defaulted_owner) {
+ *defaulted_fieldsp |= VATTR_PREPARE_DEFAULTED_UID;
+ }
+ }
+ return error;
+}
+
+/*
+ * Check that the attribute information in vap can be legally written by the
+ * context.
+ *
+ * Call this when you're not sure about the vnode_attr; either its contents
+ * have come from an unknown source, or when they are variable.
+ *
+ * Returns errno, or zero and sets *actionp to the KAUTH_VNODE_* actions that
+ * must be authorized to be permitted to write the vattr.
+ */
+int
+vnode_authattr(vnode_t vp, struct vnode_attr *vap, kauth_action_t *actionp, vfs_context_t ctx)
+{
+ struct vnode_attr ova;
+ kauth_action_t required_action;
+ int error, has_priv_suser, ismember, chowner, chgroup, clear_suid, clear_sgid;
+ guid_t changer;
+ gid_t group;
+ uid_t owner;
+ mode_t newmode;
+ kauth_cred_t cred;
+ uint32_t fdelta;
+
+ VATTR_INIT(&ova);
+ required_action = 0;
+ error = 0;
+
+ /*
+ * Quickly check for enforcement applicability.
+ */
+ if (vfs_authopaque(vp->v_mount)) {
+ goto out;
+ }
+
+ /*
+ * Check for attempts to set nonsensical fields.
+ */
+ if (vap->va_active & VNODE_ATTR_RDONLY) {
+ KAUTH_DEBUG("ATTR - ERROR: attempt to set readonly attribute(s)");
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * We need to know if the caller is the superuser.
+ */
+ cred = vfs_context_ucred(ctx);
+ has_priv_suser = kauth_cred_issuser(cred);
+
+ /*
+ * If any of the following are changing, we need information from the old file:
+ * va_uid
+ * va_gid
+ * va_mode
+ * va_uuuid
+ * va_guuid
+ */
+ if (VATTR_IS_ACTIVE(vap, va_uid) ||
+ VATTR_IS_ACTIVE(vap, va_gid) ||
+ VATTR_IS_ACTIVE(vap, va_mode) ||
+ VATTR_IS_ACTIVE(vap, va_uuuid) ||
+ VATTR_IS_ACTIVE(vap, va_guuid)) {
+ VATTR_WANTED(&ova, va_mode);
+ VATTR_WANTED(&ova, va_uid);
+ VATTR_WANTED(&ova, va_gid);
+ VATTR_WANTED(&ova, va_uuuid);
+ VATTR_WANTED(&ova, va_guuid);
+ KAUTH_DEBUG("ATTR - security information changing, fetching existing attributes");
+ }
+
+ /*
+ * If timestamps are being changed, we need to know who the file is owned
+ * by.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_create_time) ||
+ VATTR_IS_ACTIVE(vap, va_change_time) ||
+ VATTR_IS_ACTIVE(vap, va_modify_time) ||
+ VATTR_IS_ACTIVE(vap, va_access_time) ||
+ VATTR_IS_ACTIVE(vap, va_backup_time) ||
+ VATTR_IS_ACTIVE(vap, va_addedtime)) {
+ VATTR_WANTED(&ova, va_uid);
+#if 0 /* enable this when we support UUIDs as official owners */
+ VATTR_WANTED(&ova, va_uuuid);
+#endif
+ KAUTH_DEBUG("ATTR - timestamps changing, fetching uid and GUID");
+ }
+
+ /*
+ * If flags are being changed, we need the old flags.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_flags)) {
+ KAUTH_DEBUG("ATTR - flags changing, fetching old flags");
+ VATTR_WANTED(&ova, va_flags);
+ }
+
+ /*
+ * If ACLs are being changed, we need the old ACLs.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_acl)) {
+ KAUTH_DEBUG("ATTR - acl changing, fetching old flags");
+ VATTR_WANTED(&ova, va_acl);
+ }
+
+ /*
+ * If the size is being set, make sure it's not a directory.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_data_size)) {
+ /* size is only meaningful on regular files, don't permit otherwise */
+ if (!vnode_isreg(vp)) {
+ KAUTH_DEBUG("ATTR - ERROR: size change requested on non-file");
+ error = vnode_isdir(vp) ? EISDIR : EINVAL;
+ goto out;
+ }
+ }
+
+ /*
+ * Get old data.
+ */
+ KAUTH_DEBUG("ATTR - fetching old attributes %016llx", ova.va_active);
+ if ((error = vnode_getattr(vp, &ova, ctx)) != 0) {
+ KAUTH_DEBUG(" ERROR - got %d trying to get attributes", error);
+ goto out;
+ }
+
+ /*
+ * Size changes require write access to the file data.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_data_size)) {
+ /* if we can't get the size, or it's different, we need write access */
+ KAUTH_DEBUG("ATTR - size change, requiring WRITE_DATA");
+ required_action |= KAUTH_VNODE_WRITE_DATA;
+ }
+
+ /*
+ * Changing timestamps?
+ *
+ * Note that we are only called to authorize user-requested time changes;
+ * side-effect time changes are not authorized. Authorisation is only
+ * required for existing files.
+ *
+ * Non-owners are not permitted to change the time on an existing
+ * file to anything other than the current time.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_create_time) ||
+ VATTR_IS_ACTIVE(vap, va_change_time) ||
+ VATTR_IS_ACTIVE(vap, va_modify_time) ||
+ VATTR_IS_ACTIVE(vap, va_access_time) ||
+ VATTR_IS_ACTIVE(vap, va_backup_time) ||
+ VATTR_IS_ACTIVE(vap, va_addedtime)) {
+ /*
+ * The owner and root may set any timestamps they like,
+ * provided that the file is not immutable. The owner still needs
+ * WRITE_ATTRIBUTES (implied by ownership but still deniable).
+ */
+ if (has_priv_suser || vauth_node_owner(&ova, cred)) {
+ KAUTH_DEBUG("ATTR - root or owner changing timestamps");
+ required_action |= KAUTH_VNODE_CHECKIMMUTABLE | KAUTH_VNODE_WRITE_ATTRIBUTES;
+ } else {
+ /* just setting the current time? */
+ if (vap->va_vaflags & VA_UTIMES_NULL) {
+ KAUTH_DEBUG("ATTR - non-root/owner changing timestamps, requiring WRITE_ATTRIBUTES");
+ required_action |= KAUTH_VNODE_WRITE_ATTRIBUTES;
+ } else {
+ KAUTH_DEBUG("ATTR - ERROR: illegal timestamp modification attempted");
+ error = EACCES;
+ goto out;
+ }
+ }
+ }
+
+ /*
+ * Changing file mode?
+ */
+ if (VATTR_IS_ACTIVE(vap, va_mode) && VATTR_IS_SUPPORTED(&ova, va_mode) && (ova.va_mode != vap->va_mode)) {
+ KAUTH_DEBUG("ATTR - mode change from %06o to %06o", ova.va_mode, vap->va_mode);
+
+ /*
+ * Mode changes always have the same basic auth requirements.
+ */
+ if (has_priv_suser) {
+ KAUTH_DEBUG("ATTR - superuser mode change, requiring immutability check");
+ required_action |= KAUTH_VNODE_CHECKIMMUTABLE;
+ } else {
+ /* need WRITE_SECURITY */
+ KAUTH_DEBUG("ATTR - non-superuser mode change, requiring WRITE_SECURITY");
+ required_action |= KAUTH_VNODE_WRITE_SECURITY;
+ }
+
+ /*
+ * Can't set the setgid bit if you're not in the group and not root. Have to have
+ * existing group information in the case we're not setting it right now.
+ */
+ if (vap->va_mode & S_ISGID) {
+ required_action |= KAUTH_VNODE_CHECKIMMUTABLE; /* always required */
+ if (!has_priv_suser) {
+ if (VATTR_IS_ACTIVE(vap, va_gid)) {
+ group = vap->va_gid;
+ } else if (VATTR_IS_SUPPORTED(&ova, va_gid)) {
+ group = ova.va_gid;
+ } else {
+ KAUTH_DEBUG("ATTR - ERROR: setgid but no gid available");
+ error = EINVAL;
+ goto out;
+ }
+ /*
+ * This might be too restrictive; WRITE_SECURITY might be implied by
+ * membership in this case, rather than being an additional requirement.
+ */
+ if ((error = kauth_cred_ismember_gid(cred, group, &ismember)) != 0) {
+ KAUTH_DEBUG("ATTR - ERROR: got %d checking for membership in %d", error, vap->va_gid);
+ goto out;
+ }
+ if (!ismember) {
+ KAUTH_DEBUG(" DENIED - can't set SGID bit, not a member of %d", group);
+ error = EPERM;
+ goto out;
+ }
+ }
+ }
+
+ /*
+ * Can't set the setuid bit unless you're root or the file's owner.
+ */
+ if (vap->va_mode & S_ISUID) {
+ required_action |= KAUTH_VNODE_CHECKIMMUTABLE; /* always required */
+ if (!has_priv_suser) {
+ if (VATTR_IS_ACTIVE(vap, va_uid)) {
+ owner = vap->va_uid;
+ } else if (VATTR_IS_SUPPORTED(&ova, va_uid)) {
+ owner = ova.va_uid;
+ } else {
+ KAUTH_DEBUG("ATTR - ERROR: setuid but no uid available");
+ error = EINVAL;
+ goto out;
+ }
+ if (owner != kauth_cred_getuid(cred)) {
+ /*
+ * We could allow this if WRITE_SECURITY is permitted, perhaps.
+ */
+ KAUTH_DEBUG("ATTR - ERROR: illegal attempt to set the setuid bit");
+ error = EPERM;
+ goto out;
+ }
+ }
+ }
+ }
+
+ /*
+ * Validate/mask flags changes. This checks that only the flags in
+ * the UF_SETTABLE mask are being set, and preserves the flags in
+ * the SF_SETTABLE case.
+ *
+ * Since flags changes may be made in conjunction with other changes,
+ * we will ask the auth code to ignore immutability in the case that
+ * the SF_* flags are not set and we are only manipulating the file flags.
+ *
+ */
+ if (VATTR_IS_ACTIVE(vap, va_flags)) {
+ /* compute changing flags bits */
+ vap->va_flags &= ~SF_SYNTHETIC;
+ ova.va_flags &= ~SF_SYNTHETIC;
+ if (VATTR_IS_SUPPORTED(&ova, va_flags)) {
+ fdelta = vap->va_flags ^ ova.va_flags;
+ } else {
+ fdelta = vap->va_flags;
+ }
+
+ if (fdelta != 0) {
+ KAUTH_DEBUG("ATTR - flags changing, requiring WRITE_SECURITY");
+ required_action |= KAUTH_VNODE_WRITE_SECURITY;
+
+ /* check that changing bits are legal */
+ if (has_priv_suser) {
+ /*
+ * The immutability check will prevent us from clearing the SF_*
+ * flags unless the system securelevel permits it, so just check
+ * for legal flags here.
+ */
+ if (fdelta & ~(UF_SETTABLE | SF_SETTABLE)) {
+ error = EPERM;
+ KAUTH_DEBUG(" DENIED - superuser attempt to set illegal flag(s)");
+ goto out;
+ }
+ } else {
+ if (fdelta & ~UF_SETTABLE) {
+ error = EPERM;
+ KAUTH_DEBUG(" DENIED - user attempt to set illegal flag(s)");
+ goto out;
+ }
+ }
+ /*
+ * If the caller has the ability to manipulate file flags,
+ * security is not reduced by ignoring them for this operation.
+ *
+ * A more complete test here would consider the 'after' states of the flags
+ * to determine whether it would permit the operation, but this becomes
+ * very complex.
+ *
+ * Ignoring immutability is conditional on securelevel; this does not bypass
+ * the SF_* flags if securelevel > 0.
+ */
+ required_action |= KAUTH_VNODE_NOIMMUTABLE;
+ }
+ }
+
+ /*
+ * Validate ownership information.
+ */
+ chowner = 0;
+ chgroup = 0;
+ clear_suid = 0;
+ clear_sgid = 0;
+
+ /*
+ * uid changing
+ * Note that if the filesystem didn't give us a UID, we expect that it doesn't
+ * support them in general, and will ignore it if/when we try to set it.
+ * We might want to clear the uid out of vap completely here.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_uid)) {
+ if (VATTR_IS_SUPPORTED(&ova, va_uid) && (vap->va_uid != ova.va_uid)) {
+ if (!has_priv_suser && (kauth_cred_getuid(cred) != vap->va_uid)) {
+ KAUTH_DEBUG(" DENIED - non-superuser cannot change ownershipt to a third party");
+ error = EPERM;
+ goto out;
+ }
+ chowner = 1;
+ }
+ clear_suid = 1;
+ }
+
+ /*
+ * gid changing
+ * Note that if the filesystem didn't give us a GID, we expect that it doesn't
+ * support them in general, and will ignore it if/when we try to set it.
+ * We might want to clear the gid out of vap completely here.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_gid)) {
+ if (VATTR_IS_SUPPORTED(&ova, va_gid) && (vap->va_gid != ova.va_gid)) {
+ if (!has_priv_suser) {
+ if ((error = kauth_cred_ismember_gid(cred, vap->va_gid, &ismember)) != 0) {
+ KAUTH_DEBUG(" ERROR - got %d checking for membership in %d", error, vap->va_gid);
+ goto out;
+ }
+ if (!ismember) {
+ KAUTH_DEBUG(" DENIED - group change from %d to %d but not a member of target group",
+ ova.va_gid, vap->va_gid);
+ error = EPERM;
+ goto out;
+ }
+ }
+ chgroup = 1;
+ }
+ clear_sgid = 1;
+ }
+
+ /*
+ * Owner UUID being set or changed.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_uuuid)) {
+ /* if the owner UUID is not actually changing ... */
+ if (VATTR_IS_SUPPORTED(&ova, va_uuuid)) {
+ if (kauth_guid_equal(&vap->va_uuuid, &ova.va_uuuid)) {
+ goto no_uuuid_change;
+ }
+
+ /*
+ * If the current owner UUID is a null GUID, check
+ * it against the UUID corresponding to the owner UID.
+ */
+ if (kauth_guid_equal(&ova.va_uuuid, &kauth_null_guid) &&
+ VATTR_IS_SUPPORTED(&ova, va_uid)) {
+ guid_t uid_guid;
+
+ if (kauth_cred_uid2guid(ova.va_uid, &uid_guid) == 0 &&
+ kauth_guid_equal(&vap->va_uuuid, &uid_guid)) {
+ goto no_uuuid_change;
+ }
+ }
+ }
+
+ /*
+ * The owner UUID cannot be set by a non-superuser to anything other than
+ * their own or a null GUID (to "unset" the owner UUID).
+ * Note that file systems must be prepared to handle the
+ * null UUID case in a manner appropriate for that file
+ * system.
+ */
+ if (!has_priv_suser) {
+ if ((error = kauth_cred_getguid(cred, &changer)) != 0) {
+ KAUTH_DEBUG(" ERROR - got %d trying to get caller UUID", error);
+ /* XXX ENOENT here - no UUID - should perhaps become EPERM */
+ goto out;
+ }
+ if (!kauth_guid_equal(&vap->va_uuuid, &changer) &&
+ !kauth_guid_equal(&vap->va_uuuid, &kauth_null_guid)) {
+ KAUTH_DEBUG(" ERROR - cannot set supplied owner UUID - not us / null");
+ error = EPERM;
+ goto out;
+ }
+ }
+ chowner = 1;
+ clear_suid = 1;
+ }
+no_uuuid_change:
+ /*
+ * Group UUID being set or changed.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_guuid)) {
+ /* if the group UUID is not actually changing ... */
+ if (VATTR_IS_SUPPORTED(&ova, va_guuid)) {
+ if (kauth_guid_equal(&vap->va_guuid, &ova.va_guuid)) {
+ goto no_guuid_change;
+ }
+
+ /*
+ * If the current group UUID is a null UUID, check
+ * it against the UUID corresponding to the group GID.
+ */
+ if (kauth_guid_equal(&ova.va_guuid, &kauth_null_guid) &&
+ VATTR_IS_SUPPORTED(&ova, va_gid)) {
+ guid_t gid_guid;
+
+ if (kauth_cred_gid2guid(ova.va_gid, &gid_guid) == 0 &&
+ kauth_guid_equal(&vap->va_guuid, &gid_guid)) {
+ goto no_guuid_change;
+ }
+ }
+ }
+
+ /*
+ * The group UUID cannot be set by a non-superuser to anything other than
+ * one of which they are a member or a null GUID (to "unset"
+ * the group UUID).
+ * Note that file systems must be prepared to handle the
+ * null UUID case in a manner appropriate for that file
+ * system.
+ */
+ if (!has_priv_suser) {
+ if (kauth_guid_equal(&vap->va_guuid, &kauth_null_guid)) {
+ ismember = 1;
+ } else if ((error = kauth_cred_ismember_guid(cred, &vap->va_guuid, &ismember)) != 0) {
+ KAUTH_DEBUG(" ERROR - got %d trying to check group membership", error);
+ goto out;
+ }
+ if (!ismember) {
+ KAUTH_DEBUG(" ERROR - cannot set supplied group UUID - not a member / null");
+ error = EPERM;
+ goto out;
+ }
+ }
+ chgroup = 1;
+ }
+no_guuid_change:
+
+ /*
+ * Compute authorisation for group/ownership changes.
+ */
+ if (chowner || chgroup || clear_suid || clear_sgid) {
+ if (has_priv_suser) {
+ KAUTH_DEBUG("ATTR - superuser changing file owner/group, requiring immutability check");
+ required_action |= KAUTH_VNODE_CHECKIMMUTABLE;
+ } else {
+ if (chowner) {
+ KAUTH_DEBUG("ATTR - ownership change, requiring TAKE_OWNERSHIP");
+ required_action |= KAUTH_VNODE_TAKE_OWNERSHIP;
+ }
+ if (chgroup && !chowner) {
+ KAUTH_DEBUG("ATTR - group change, requiring WRITE_SECURITY");
+ required_action |= KAUTH_VNODE_WRITE_SECURITY;
+ }
+ }
+
+ /*
+ * clear set-uid and set-gid bits. POSIX only requires this for
+ * non-privileged processes but we do it even for root.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_mode)) {
+ newmode = vap->va_mode;
+ } else if (VATTR_IS_SUPPORTED(&ova, va_mode)) {
+ newmode = ova.va_mode;
+ } else {
+ KAUTH_DEBUG("CHOWN - trying to change owner but cannot get mode from filesystem to mask setugid bits");
+ newmode = 0;
+ }
+
+ /* chown always clears setuid/gid bits. An exception is made for
+ * setattrlist which can set both at the same time: <uid, gid, mode> on a file:
+ * setattrlist is allowed to set the new mode on the file and change (chown)
+ * uid/gid.
+ */
+ if (newmode & (S_ISUID | S_ISGID)) {
+ if (!VATTR_IS_ACTIVE(vap, va_mode)) {
+ KAUTH_DEBUG("CHOWN - masking setugid bits from mode %o to %o",
+ newmode, newmode & ~(S_ISUID | S_ISGID));
+ newmode &= ~(S_ISUID | S_ISGID);
+ }
+ VATTR_SET(vap, va_mode, newmode);
+ }
+ }
+
+ /*
+ * Authorise changes in the ACL.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_acl)) {
+ /* no existing ACL */
+ if (!VATTR_IS_ACTIVE(&ova, va_acl) || (ova.va_acl == NULL)) {
+ /* adding an ACL */
+ if (vap->va_acl != NULL) {
+ required_action |= KAUTH_VNODE_WRITE_SECURITY;
+ KAUTH_DEBUG("CHMOD - adding ACL");
+ }
+
+ /* removing an existing ACL */
+ } else if (vap->va_acl == NULL) {
+ required_action |= KAUTH_VNODE_WRITE_SECURITY;
+ KAUTH_DEBUG("CHMOD - removing ACL");
+
+ /* updating an existing ACL */
+ } else {
+ if (vap->va_acl->acl_entrycount != ova.va_acl->acl_entrycount) {
+ /* entry count changed, must be different */
+ required_action |= KAUTH_VNODE_WRITE_SECURITY;
+ KAUTH_DEBUG("CHMOD - adding/removing ACL entries");
+ } else if (vap->va_acl->acl_entrycount > 0) {
+ /* both ACLs have the same ACE count, said count is 1 or more, bitwise compare ACLs */
+ if (memcmp(&vap->va_acl->acl_ace[0], &ova.va_acl->acl_ace[0],
+ sizeof(struct kauth_ace) * vap->va_acl->acl_entrycount)) {
+ required_action |= KAUTH_VNODE_WRITE_SECURITY;
+ KAUTH_DEBUG("CHMOD - changing ACL entries");
+ }
+ }
+ }
+ }
+
+ /*
+ * Other attributes that require authorisation.
+ */
+ if (VATTR_IS_ACTIVE(vap, va_encoding)) {
+ required_action |= KAUTH_VNODE_WRITE_ATTRIBUTES;
+ }
+
+out:
+ if (VATTR_IS_SUPPORTED(&ova, va_acl) && (ova.va_acl != NULL)) {
+ kauth_acl_free(ova.va_acl);
+ }
+ if (error == 0) {
+ *actionp = required_action;
+ }
+ return error;
+}
+
+static int
+setlocklocal_callback(struct vnode *vp, __unused void *cargs)
+{
+ vnode_lock_spin(vp);
+ vp->v_flag |= VLOCKLOCAL;
+ vnode_unlock(vp);
+
+ return VNODE_RETURNED;
+}
+
+void
+vfs_setlocklocal(mount_t mp)
+{
+ mount_lock_spin(mp);
+ mp->mnt_kern_flag |= MNTK_LOCK_LOCAL;
+ mount_unlock(mp);
+
+ /*
+ * The number of active vnodes is expected to be
+ * very small when vfs_setlocklocal is invoked.
+ */
+ vnode_iterate(mp, 0, setlocklocal_callback, NULL);
+}
+
+void
+vfs_setcompoundopen(mount_t mp)
+{
+ mount_lock_spin(mp);
+ mp->mnt_compound_ops |= COMPOUND_VNOP_OPEN;
+ mount_unlock(mp);
+}
+
+void
+vnode_setswapmount(vnode_t vp)
+{
+ mount_lock(vp->v_mount);
+ vp->v_mount->mnt_kern_flag |= MNTK_SWAP_MOUNT;
+ mount_unlock(vp->v_mount);
+}
+
+
+int64_t
+vnode_getswappin_avail(vnode_t vp)
+{
+ int64_t max_swappin_avail = 0;
+
+ mount_lock(vp->v_mount);
+ if (vp->v_mount->mnt_ioflags & MNT_IOFLAGS_SWAPPIN_SUPPORTED) {
+ max_swappin_avail = vp->v_mount->mnt_max_swappin_available;
+ }
+ mount_unlock(vp->v_mount);
+
+ return max_swappin_avail;
+}
+
+
+void
+vn_setunionwait(vnode_t vp)
+{
+ vnode_lock_spin(vp);
+ vp->v_flag |= VISUNION;
+ vnode_unlock(vp);
+}
+
+
+void
+vn_checkunionwait(vnode_t vp)
+{
+ vnode_lock_spin(vp);
+ while ((vp->v_flag & VISUNION) == VISUNION) {
+ msleep((caddr_t)&vp->v_flag, &vp->v_lock, 0, 0, 0);