+ 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) {