/* $NetBSD: genfs_vnops.c,v 1.220 2023/03/03 10:02:51 hannken Exp $ */ /*- * Copyright (c) 2008 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (c) 1982, 1986, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __KERNEL_RCSID(0, "$NetBSD: genfs_vnops.c,v 1.220 2023/03/03 10:02:51 hannken Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void filt_genfsdetach(struct knote *); static int filt_genfsread(struct knote *, long); static int filt_genfsvnode(struct knote *, long); /* * Find the end of the first path component in NAME and return its * length. */ int genfs_parsepath(void *v) { struct vop_parsepath_args /* { struct vnode *a_dvp; const char *a_name; size_t *a_ret; } */ *ap = v; const char *name = ap->a_name; size_t pos; (void)ap->a_dvp; pos = 0; while (name[pos] != '\0' && name[pos] != '/') { pos++; } *ap->a_retval = pos; return 0; } int genfs_poll(void *v) { struct vop_poll_args /* { struct vnode *a_vp; int a_events; struct lwp *a_l; } */ *ap = v; return (ap->a_events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)); } int genfs_seek(void *v) { struct vop_seek_args /* { struct vnode *a_vp; off_t a_oldoff; off_t a_newoff; kauth_cred_t cred; } */ *ap = v; if (ap->a_newoff < 0) return (EINVAL); return (0); } int genfs_abortop(void *v) { struct vop_abortop_args /* { struct vnode *a_dvp; struct componentname *a_cnp; } */ *ap = v; (void)ap; return (0); } int genfs_fcntl(void *v) { struct vop_fcntl_args /* { struct vnode *a_vp; u_int a_command; void *a_data; int a_fflag; kauth_cred_t a_cred; struct lwp *a_l; } */ *ap = v; if (ap->a_command == F_SETFL) return (0); else return (EOPNOTSUPP); } /*ARGSUSED*/ int genfs_badop(void *v) { panic("genfs: bad op"); } /*ARGSUSED*/ int genfs_nullop(void *v) { return (0); } /*ARGSUSED*/ int genfs_einval(void *v) { return (EINVAL); } int genfs_erofs_link(void *v) { /* also for symlink */ struct vop_link_v2_args /* { struct vnode *a_dvp; struct vnode **a_vpp; struct componentname *a_cnp; } */ *ap = v; VOP_ABORTOP(ap->a_dvp, ap->a_cnp); return EROFS; } /* * Called when an fs doesn't support a particular vop. * This takes care to vrele, vput, or vunlock passed in vnodes * and calls VOP_ABORTOP for a componentname (in non-rename VOP). */ int genfs_eopnotsupp(void *v) { struct vop_generic_args /* struct vnodeop_desc *a_desc; / * other random data follows, presumably * / } */ *ap = v; struct vnodeop_desc *desc = ap->a_desc; struct vnode *vp, *vp_last = NULL; int flags, i, j, offset_cnp, offset_vp; KASSERT(desc->vdesc_offset != VOP_LOOKUP_DESCOFFSET); KASSERT(desc->vdesc_offset != VOP_ABORTOP_DESCOFFSET); /* * Abort any componentname that lookup potentially left state in. * * As is logical, componentnames for VOP_RENAME are handled by * the caller of VOP_RENAME. Yay, rename! */ if (desc->vdesc_offset != VOP_RENAME_DESCOFFSET && (offset_vp = desc->vdesc_vp_offsets[0]) != VDESC_NO_OFFSET && (offset_cnp = desc->vdesc_componentname_offset) != VDESC_NO_OFFSET){ struct componentname *cnp; struct vnode *dvp; dvp = *VOPARG_OFFSETTO(struct vnode **, offset_vp, ap); cnp = *VOPARG_OFFSETTO(struct componentname **, offset_cnp, ap); VOP_ABORTOP(dvp, cnp); } flags = desc->vdesc_flags; for (i = 0; i < VDESC_MAX_VPS; flags >>=1, i++) { if ((offset_vp = desc->vdesc_vp_offsets[i]) == VDESC_NO_OFFSET) break; /* stop at end of list */ if ((j = flags & VDESC_VP0_WILLPUT)) { vp = *VOPARG_OFFSETTO(struct vnode **, offset_vp, ap); /* Skip if NULL */ if (!vp) continue; switch (j) { case VDESC_VP0_WILLPUT: /* Check for dvp == vp cases */ if (vp == vp_last) vrele(vp); else { vput(vp); vp_last = vp; } break; case VDESC_VP0_WILLRELE: vrele(vp); break; } } } return (EOPNOTSUPP); } /*ARGSUSED*/ int genfs_ebadf(void *v) { return (EBADF); } /* ARGSUSED */ int genfs_enoioctl(void *v) { return (EPASSTHROUGH); } /* * Eliminate all activity associated with the requested vnode * and with all vnodes aliased to the requested vnode. */ int genfs_revoke(void *v) { struct vop_revoke_args /* { struct vnode *a_vp; int a_flags; } */ *ap = v; #ifdef DIAGNOSTIC if ((ap->a_flags & REVOKEALL) == 0) panic("genfs_revoke: not revokeall"); #endif vrevoke(ap->a_vp); return (0); } /* * Lock the node (for deadfs). */ int genfs_deadlock(void *v) { struct vop_lock_args /* { struct vnode *a_vp; int a_flags; } */ *ap = v; vnode_t *vp = ap->a_vp; vnode_impl_t *vip = VNODE_TO_VIMPL(vp); int flags = ap->a_flags; krw_t op; if (! ISSET(flags, LK_RETRY)) return ENOENT; if (ISSET(flags, LK_DOWNGRADE)) { rw_downgrade(&vip->vi_lock); } else if (ISSET(flags, LK_UPGRADE)) { KASSERT(ISSET(flags, LK_NOWAIT)); if (!rw_tryupgrade(&vip->vi_lock)) { return EBUSY; } } else if ((flags & (LK_EXCLUSIVE | LK_SHARED)) != 0) { op = (ISSET(flags, LK_EXCLUSIVE) ? RW_WRITER : RW_READER); if (ISSET(flags, LK_NOWAIT)) { if (!rw_tryenter(&vip->vi_lock, op)) return EBUSY; } else { rw_enter(&vip->vi_lock, op); } } VSTATE_ASSERT_UNLOCKED(vp, VS_RECLAIMED); return 0; } /* * Unlock the node (for deadfs). */ int genfs_deadunlock(void *v) { struct vop_unlock_args /* { struct vnode *a_vp; } */ *ap = v; vnode_t *vp = ap->a_vp; vnode_impl_t *vip = VNODE_TO_VIMPL(vp); rw_exit(&vip->vi_lock); return 0; } /* * Lock the node. */ int genfs_lock(void *v) { struct vop_lock_args /* { struct vnode *a_vp; int a_flags; } */ *ap = v; vnode_t *vp = ap->a_vp; vnode_impl_t *vip = VNODE_TO_VIMPL(vp); int flags = ap->a_flags; krw_t op; if (ISSET(flags, LK_DOWNGRADE)) { rw_downgrade(&vip->vi_lock); } else if (ISSET(flags, LK_UPGRADE)) { KASSERT(ISSET(flags, LK_NOWAIT)); if (!rw_tryupgrade(&vip->vi_lock)) { return EBUSY; } } else if ((flags & (LK_EXCLUSIVE | LK_SHARED)) != 0) { op = (ISSET(flags, LK_EXCLUSIVE) ? RW_WRITER : RW_READER); if (ISSET(flags, LK_NOWAIT)) { if (!rw_tryenter(&vip->vi_lock, op)) return EBUSY; } else { rw_enter(&vip->vi_lock, op); } } VSTATE_ASSERT_UNLOCKED(vp, VS_ACTIVE); return 0; } /* * Unlock the node. */ int genfs_unlock(void *v) { struct vop_unlock_args /* { struct vnode *a_vp; } */ *ap = v; vnode_t *vp = ap->a_vp; vnode_impl_t *vip = VNODE_TO_VIMPL(vp); rw_exit(&vip->vi_lock); return 0; } /* * Return whether or not the node is locked. */ int genfs_islocked(void *v) { struct vop_islocked_args /* { struct vnode *a_vp; } */ *ap = v; vnode_t *vp = ap->a_vp; vnode_impl_t *vip = VNODE_TO_VIMPL(vp); if (rw_write_held(&vip->vi_lock)) return LK_EXCLUSIVE; if (rw_read_held(&vip->vi_lock)) return LK_SHARED; return 0; } int genfs_mmap(void *v) { return (0); } /* * VOP_PUTPAGES() for vnodes which never have pages. */ int genfs_null_putpages(void *v) { struct vop_putpages_args /* { struct vnode *a_vp; voff_t a_offlo; voff_t a_offhi; int a_flags; } */ *ap = v; struct vnode *vp = ap->a_vp; KASSERT(vp->v_uobj.uo_npages == 0); rw_exit(vp->v_uobj.vmobjlock); return (0); } void genfs_node_init(struct vnode *vp, const struct genfs_ops *ops) { struct genfs_node *gp = VTOG(vp); rw_init(&gp->g_glock); gp->g_op = ops; } void genfs_node_destroy(struct vnode *vp) { struct genfs_node *gp = VTOG(vp); rw_destroy(&gp->g_glock); } void genfs_size(struct vnode *vp, off_t size, off_t *eobp, int flags) { int bsize; bsize = 1 << vp->v_mount->mnt_fs_bshift; *eobp = (size + bsize - 1) & ~(bsize - 1); } static void filt_genfsdetach(struct knote *kn) { struct vnode *vp = (struct vnode *)kn->kn_hook; vn_knote_detach(vp, kn); } static int filt_genfsread(struct knote *kn, long hint) { struct vnode *vp = (struct vnode *)kn->kn_hook; int rv; /* * filesystem is gone, so set the EOF flag and schedule * the knote for deletion. */ switch (hint) { case NOTE_REVOKE: KASSERT(mutex_owned(vp->v_interlock)); knote_set_eof(kn, EV_ONESHOT); return (1); case 0: mutex_enter(vp->v_interlock); kn->kn_data = vp->v_size - ((file_t *)kn->kn_obj)->f_offset; rv = (kn->kn_data != 0); mutex_exit(vp->v_interlock); return rv; default: KASSERT(mutex_owned(vp->v_interlock)); kn->kn_data = vp->v_size - ((file_t *)kn->kn_obj)->f_offset; return (kn->kn_data != 0); } } static int filt_genfswrite(struct knote *kn, long hint) { struct vnode *vp = (struct vnode *)kn->kn_hook; /* * filesystem is gone, so set the EOF flag and schedule * the knote for deletion. */ switch (hint) { case NOTE_REVOKE: KASSERT(mutex_owned(vp->v_interlock)); knote_set_eof(kn, EV_ONESHOT); return (1); case 0: mutex_enter(vp->v_interlock); kn->kn_data = 0; mutex_exit(vp->v_interlock); return 1; default: KASSERT(mutex_owned(vp->v_interlock)); kn->kn_data = 0; return 1; } } static int filt_genfsvnode(struct knote *kn, long hint) { struct vnode *vp = (struct vnode *)kn->kn_hook; int fflags; switch (hint) { case NOTE_REVOKE: KASSERT(mutex_owned(vp->v_interlock)); knote_set_eof(kn, 0); if ((kn->kn_sfflags & hint) != 0) kn->kn_fflags |= hint; return (1); case 0: mutex_enter(vp->v_interlock); fflags = kn->kn_fflags; mutex_exit(vp->v_interlock); break; default: KASSERT(mutex_owned(vp->v_interlock)); if ((kn->kn_sfflags & hint) != 0) kn->kn_fflags |= hint; fflags = kn->kn_fflags; break; } return (fflags != 0); } static const struct filterops genfsread_filtops = { .f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE, .f_attach = NULL, .f_detach = filt_genfsdetach, .f_event = filt_genfsread, }; static const struct filterops genfswrite_filtops = { .f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE, .f_attach = NULL, .f_detach = filt_genfsdetach, .f_event = filt_genfswrite, }; static const struct filterops genfsvnode_filtops = { .f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE, .f_attach = NULL, .f_detach = filt_genfsdetach, .f_event = filt_genfsvnode, }; int genfs_kqfilter(void *v) { struct vop_kqfilter_args /* { struct vnode *a_vp; struct knote *a_kn; } */ *ap = v; struct vnode *vp; struct knote *kn; vp = ap->a_vp; kn = ap->a_kn; switch (kn->kn_filter) { case EVFILT_READ: kn->kn_fop = &genfsread_filtops; break; case EVFILT_WRITE: kn->kn_fop = &genfswrite_filtops; break; case EVFILT_VNODE: kn->kn_fop = &genfsvnode_filtops; break; default: return (EINVAL); } kn->kn_hook = vp; vn_knote_attach(vp, kn); return (0); } void genfs_node_wrlock(struct vnode *vp) { struct genfs_node *gp = VTOG(vp); rw_enter(&gp->g_glock, RW_WRITER); } void genfs_node_rdlock(struct vnode *vp) { struct genfs_node *gp = VTOG(vp); rw_enter(&gp->g_glock, RW_READER); } int genfs_node_rdtrylock(struct vnode *vp) { struct genfs_node *gp = VTOG(vp); return rw_tryenter(&gp->g_glock, RW_READER); } void genfs_node_unlock(struct vnode *vp) { struct genfs_node *gp = VTOG(vp); rw_exit(&gp->g_glock); } int genfs_node_wrlocked(struct vnode *vp) { struct genfs_node *gp = VTOG(vp); return rw_write_held(&gp->g_glock); } /* * Common filesystem object access control check routine. Accepts a * vnode, cred, uid, gid, mode, acl, requested access mode. * Returns 0 on success, or an errno on failure. */ int genfs_can_access(vnode_t *vp, kauth_cred_t cred, uid_t file_uid, gid_t file_gid, mode_t file_mode, struct acl *acl, accmode_t accmode) { accmode_t dac_granted; int error; KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND)) == 0); KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE)); /* * Look for a normal, non-privileged way to access the file/directory * as requested. If it exists, go with that. */ dac_granted = 0; /* Check the owner. */ if (kauth_cred_geteuid(cred) == file_uid) { dac_granted |= VADMIN; if (file_mode & S_IXUSR) dac_granted |= VEXEC; if (file_mode & S_IRUSR) dac_granted |= VREAD; if (file_mode & S_IWUSR) dac_granted |= (VWRITE | VAPPEND); goto privchk; } /* Otherwise, check the groups (first match) */ /* Otherwise, check the groups. */ error = kauth_cred_groupmember(cred, file_gid); if (error > 0) return error; if (error == 0) { if (file_mode & S_IXGRP) dac_granted |= VEXEC; if (file_mode & S_IRGRP) dac_granted |= VREAD; if (file_mode & S_IWGRP) dac_granted |= (VWRITE | VAPPEND); goto privchk; } /* Otherwise, check everyone else. */ if (file_mode & S_IXOTH) dac_granted |= VEXEC; if (file_mode & S_IROTH) dac_granted |= VREAD; if (file_mode & S_IWOTH) dac_granted |= (VWRITE | VAPPEND); privchk: if ((accmode & dac_granted) == accmode) return 0; return (accmode & VADMIN) ? EPERM : EACCES; } /* * Implement a version of genfs_can_access() that understands POSIX.1e ACL * semantics; * the access ACL has already been prepared for evaluation by the file system * and is passed via 'uid', 'gid', and 'acl'. Return 0 on success, else an * errno value. */ int genfs_can_access_acl_posix1e(vnode_t *vp, kauth_cred_t cred, uid_t file_uid, gid_t file_gid, mode_t file_mode, struct acl *acl, accmode_t accmode) { struct acl_entry *acl_other, *acl_mask; accmode_t dac_granted; accmode_t acl_mask_granted; int group_matched, i; int error; KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND)) == 0); KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE)); /* * The owner matches if the effective uid associated with the * credential matches that of the ACL_USER_OBJ entry. While we're * doing the first scan, also cache the location of the ACL_MASK and * ACL_OTHER entries, preventing some future iterations. */ acl_mask = acl_other = NULL; for (i = 0; i < acl->acl_cnt; i++) { struct acl_entry *ae = &acl->acl_entry[i]; switch (ae->ae_tag) { case ACL_USER_OBJ: if (kauth_cred_geteuid(cred) != file_uid) break; dac_granted = 0; dac_granted |= VADMIN; if (ae->ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (ae->ae_perm & ACL_READ) dac_granted |= VREAD; if (ae->ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); goto out; case ACL_MASK: acl_mask = ae; break; case ACL_OTHER: acl_other = ae; break; default: break; } } /* * An ACL_OTHER entry should always exist in a valid access ACL. If * it doesn't, then generate a serious failure. For now, this means * a debugging message and EPERM, but in the future should probably * be a panic. */ if (acl_other == NULL) { /* * XXX This should never happen */ printf("%s: ACL_OTHER missing\n", __func__); return EPERM; } /* * Checks against ACL_USER, ACL_GROUP_OBJ, and ACL_GROUP fields are * masked by an ACL_MASK entry, if any. As such, first identify the * ACL_MASK field, then iterate through identifying potential user * matches, then group matches. If there is no ACL_MASK, assume that * the mask allows all requests to succeed. */ if (acl_mask != NULL) { acl_mask_granted = 0; if (acl_mask->ae_perm & ACL_EXECUTE) acl_mask_granted |= VEXEC; if (acl_mask->ae_perm & ACL_READ) acl_mask_granted |= VREAD; if (acl_mask->ae_perm & ACL_WRITE) acl_mask_granted |= (VWRITE | VAPPEND); } else acl_mask_granted = VEXEC | VREAD | VWRITE | VAPPEND; /* * Check ACL_USER ACL entries. There will either be one or no * matches; if there is one, we accept or rejected based on the * match; otherwise, we continue on to groups. */ for (i = 0; i < acl->acl_cnt; i++) { struct acl_entry *ae = &acl->acl_entry[i]; switch (ae->ae_tag) { case ACL_USER: if (kauth_cred_geteuid(cred) != ae->ae_id) break; dac_granted = 0; if (ae->ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (ae->ae_perm & ACL_READ) dac_granted |= VREAD; if (ae->ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); dac_granted &= acl_mask_granted; goto out; } } /* * Group match is best-match, not first-match, so find a "best" * match. Iterate across, testing each potential group match. Make * sure we keep track of whether we found a match or not, so that we * know if we should try again with any available privilege, or if we * should move on to ACL_OTHER. */ group_matched = 0; for (i = 0; i < acl->acl_cnt; i++) { struct acl_entry *ae = &acl->acl_entry[i]; switch (ae->ae_tag) { case ACL_GROUP_OBJ: error = kauth_cred_groupmember(cred, file_gid); if (error > 0) return error; if (error) break; dac_granted = 0; if (ae->ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (ae->ae_perm & ACL_READ) dac_granted |= VREAD; if (ae->ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); dac_granted &= acl_mask_granted; if ((accmode & dac_granted) == accmode) return 0; group_matched = 1; break; case ACL_GROUP: error = kauth_cred_groupmember(cred, ae->ae_id); if (error > 0) return error; if (error) break; dac_granted = 0; if (ae->ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (ae->ae_perm & ACL_READ) dac_granted |= VREAD; if (ae->ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); dac_granted &= acl_mask_granted; if ((accmode & dac_granted) == accmode) return 0; group_matched = 1; break; default: break; } } if (group_matched == 1) { /* * There was a match, but it did not grant rights via pure * DAC. Try again, this time with privilege. */ for (i = 0; i < acl->acl_cnt; i++) { struct acl_entry *ae = &acl->acl_entry[i]; switch (ae->ae_tag) { case ACL_GROUP_OBJ: error = kauth_cred_groupmember(cred, file_gid); if (error > 0) return error; if (error) break; dac_granted = 0; if (ae->ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (ae->ae_perm & ACL_READ) dac_granted |= VREAD; if (ae->ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); dac_granted &= acl_mask_granted; goto out; case ACL_GROUP: error = kauth_cred_groupmember(cred, ae->ae_id); if (error > 0) return error; if (error) break; dac_granted = 0; if (ae->ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (ae->ae_perm & ACL_READ) dac_granted |= VREAD; if (ae->ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); dac_granted &= acl_mask_granted; goto out; default: break; } } /* * Even with privilege, group membership was not sufficient. * Return failure. */ dac_granted = 0; goto out; } /* * Fall back on ACL_OTHER. ACL_MASK is not applied to ACL_OTHER. */ dac_granted = 0; if (acl_other->ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (acl_other->ae_perm & ACL_READ) dac_granted |= VREAD; if (acl_other->ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); out: if ((accmode & dac_granted) == accmode) return 0; return (accmode & VADMIN) ? EPERM : EACCES; } static struct { accmode_t accmode; int mask; } accmode2mask[] = { { VREAD, ACL_READ_DATA }, { VWRITE, ACL_WRITE_DATA }, { VAPPEND, ACL_APPEND_DATA }, { VEXEC, ACL_EXECUTE }, { VREAD_NAMED_ATTRS, ACL_READ_NAMED_ATTRS }, { VWRITE_NAMED_ATTRS, ACL_WRITE_NAMED_ATTRS }, { VDELETE_CHILD, ACL_DELETE_CHILD }, { VREAD_ATTRIBUTES, ACL_READ_ATTRIBUTES }, { VWRITE_ATTRIBUTES, ACL_WRITE_ATTRIBUTES }, { VDELETE, ACL_DELETE }, { VREAD_ACL, ACL_READ_ACL }, { VWRITE_ACL, ACL_WRITE_ACL }, { VWRITE_OWNER, ACL_WRITE_OWNER }, { VSYNCHRONIZE, ACL_SYNCHRONIZE }, { 0, 0 }, }; static int _access_mask_from_accmode(accmode_t accmode) { int access_mask = 0, i; for (i = 0; accmode2mask[i].accmode != 0; i++) { if (accmode & accmode2mask[i].accmode) access_mask |= accmode2mask[i].mask; } /* * VAPPEND is just a modifier for VWRITE; if the caller asked * for 'VAPPEND | VWRITE', we want to check for ACL_APPEND_DATA only. */ if (access_mask & ACL_APPEND_DATA) access_mask &= ~ACL_WRITE_DATA; return (access_mask); } /* * Return 0, iff access is allowed, 1 otherwise. */ static int _acl_denies(const struct acl *aclp, int access_mask, kauth_cred_t cred, int file_uid, int file_gid, int *denied_explicitly) { int i, error; const struct acl_entry *ae; if (denied_explicitly != NULL) *denied_explicitly = 0; KASSERT(aclp->acl_cnt <= ACL_MAX_ENTRIES); for (i = 0; i < aclp->acl_cnt; i++) { ae = &(aclp->acl_entry[i]); if (ae->ae_entry_type != ACL_ENTRY_TYPE_ALLOW && ae->ae_entry_type != ACL_ENTRY_TYPE_DENY) continue; if (ae->ae_flags & ACL_ENTRY_INHERIT_ONLY) continue; switch (ae->ae_tag) { case ACL_USER_OBJ: if (kauth_cred_geteuid(cred) != file_uid) continue; break; case ACL_USER: if (kauth_cred_geteuid(cred) != ae->ae_id) continue; break; case ACL_GROUP_OBJ: error = kauth_cred_groupmember(cred, file_gid); if (error > 0) return error; if (error != 0) continue; break; case ACL_GROUP: error = kauth_cred_groupmember(cred, ae->ae_id); if (error > 0) return error; if (error != 0) continue; break; default: KASSERT(ae->ae_tag == ACL_EVERYONE); } if (ae->ae_entry_type == ACL_ENTRY_TYPE_DENY) { if (ae->ae_perm & access_mask) { if (denied_explicitly != NULL) *denied_explicitly = 1; return (1); } } access_mask &= ~(ae->ae_perm); if (access_mask == 0) return (0); } if (access_mask == 0) return (0); return (1); } int genfs_can_access_acl_nfs4(vnode_t *vp, kauth_cred_t cred, uid_t file_uid, gid_t file_gid, mode_t file_mode, struct acl *aclp, accmode_t accmode) { int denied, explicitly_denied, access_mask, is_directory, must_be_owner = 0; file_mode = 0; KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND | VEXPLICIT_DENY | VREAD_NAMED_ATTRS | VWRITE_NAMED_ATTRS | VDELETE_CHILD | VREAD_ATTRIBUTES | VWRITE_ATTRIBUTES | VDELETE | VREAD_ACL | VWRITE_ACL | VWRITE_OWNER | VSYNCHRONIZE)) == 0); KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE)); if (accmode & VADMIN) must_be_owner = 1; /* * Ignore VSYNCHRONIZE permission. */ accmode &= ~VSYNCHRONIZE; access_mask = _access_mask_from_accmode(accmode); if (vp && vp->v_type == VDIR) is_directory = 1; else is_directory = 0; /* * File owner is always allowed to read and write the ACL * and basic attributes. This is to prevent a situation * where user would change ACL in a way that prevents him * from undoing the change. */ if (kauth_cred_geteuid(cred) == file_uid) access_mask &= ~(ACL_READ_ACL | ACL_WRITE_ACL | ACL_READ_ATTRIBUTES | ACL_WRITE_ATTRIBUTES); /* * Ignore append permission for regular files; use write * permission instead. */ if (!is_directory && (access_mask & ACL_APPEND_DATA)) { access_mask &= ~ACL_APPEND_DATA; access_mask |= ACL_WRITE_DATA; } denied = _acl_denies(aclp, access_mask, cred, file_uid, file_gid, &explicitly_denied); if (must_be_owner) { if (kauth_cred_geteuid(cred) != file_uid) denied = EPERM; } /* * For VEXEC, ensure that at least one execute bit is set for * non-directories. We have to check the mode here to stay * consistent with execve(2). See the test in * exec_check_permissions(). */ __acl_nfs4_sync_mode_from_acl(&file_mode, aclp); if (!denied && !is_directory && (accmode & VEXEC) && (file_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) denied = EACCES; if (!denied) return (0); /* * Access failed. Iff it was not denied explicitly and * VEXPLICIT_DENY flag was specified, allow access. */ if ((accmode & VEXPLICIT_DENY) && explicitly_denied == 0) return (0); accmode &= ~VEXPLICIT_DENY; if (accmode & (VADMIN_PERMS | VDELETE_CHILD | VDELETE)) denied = EPERM; else denied = EACCES; return (denied); } /* * Common routine to check if chmod() is allowed. * * Policy: * - You must own the file, and * - You must not set the "sticky" bit (meaningless, see chmod(2)) * - You must be a member of the group if you're trying to set the * SGIDf bit * * vp - vnode of the file-system object * cred - credentials of the invoker * cur_uid, cur_gid - current uid/gid of the file-system object * new_mode - new mode for the file-system object * * Returns 0 if the change is allowed, or an error value otherwise. */ int genfs_can_chmod(vnode_t *vp, kauth_cred_t cred, uid_t cur_uid, gid_t cur_gid, mode_t new_mode) { int error; /* * To modify the permissions on a file, must possess VADMIN * for that file. */ if ((error = VOP_ACCESSX(vp, VWRITE_ACL, cred)) != 0) return (error); /* * Unprivileged users can't set the sticky bit on files. */ if ((vp->v_type != VDIR) && (new_mode & S_ISTXT)) return (EFTYPE); /* * If the invoker is trying to set the SGID bit on the file, * check group membership. */ if (new_mode & S_ISGID) { int ismember; error = kauth_cred_ismember_gid(cred, cur_gid, &ismember); if (error || !ismember) return (EPERM); } /* * Deny setting setuid if we are not the file owner. */ if ((new_mode & S_ISUID) && cur_uid != kauth_cred_geteuid(cred)) return (EPERM); return (0); } /* * Common routine to check if chown() is allowed. * * Policy: * - You must own the file, and * - You must not try to change ownership, and * - You must be member of the new group * * vp - vnode * cred - credentials of the invoker * cur_uid, cur_gid - current uid/gid of the file-system object * new_uid, new_gid - target uid/gid of the file-system object * * Returns 0 if the change is allowed, or an error value otherwise. */ int genfs_can_chown(vnode_t *vp, kauth_cred_t cred, uid_t cur_uid, gid_t cur_gid, uid_t new_uid, gid_t new_gid) { int error, ismember; /* * To modify the ownership of a file, must possess VADMIN for that * file. */ if ((error = VOP_ACCESSX(vp, VWRITE_OWNER, cred)) != 0) return (error); /* * You can only change ownership of a file if: * You own the file and... */ if (kauth_cred_geteuid(cred) == cur_uid) { /* * You don't try to change ownership, and... */ if (new_uid != cur_uid) return (EPERM); /* * You don't try to change group (no-op), or... */ if (new_gid == cur_gid) return (0); /* * Your effective gid is the new gid, or... */ if (kauth_cred_getegid(cred) == new_gid) return (0); /* * The new gid is one you're a member of. */ ismember = 0; error = kauth_cred_ismember_gid(cred, new_gid, &ismember); if (!error && ismember) return (0); } return (EPERM); } int genfs_can_chtimes(vnode_t *vp, kauth_cred_t cred, uid_t owner_uid, u_int vaflags) { int error; /* * Grant permission if the caller is the owner of the file, or * the super-user, or has ACL_WRITE_ATTRIBUTES permission on * on the file. If the time pointer is null, then write * permission on the file is also sufficient. * * From NFSv4.1, draft 21, 6.2.1.3.1, Discussion of Mask Attributes: * A user having ACL_WRITE_DATA or ACL_WRITE_ATTRIBUTES * will be allowed to set the times [..] to the current * server time. */ error = VOP_ACCESSX(vp, VWRITE_ATTRIBUTES, cred); if (error != 0 && (vaflags & VA_UTIMES_NULL) != 0) error = VOP_ACCESS(vp, VWRITE, cred); if (error) return (vaflags & VA_UTIMES_NULL) == 0 ? EPERM : EACCES; return 0; } /* * Common routine to check if chflags() is allowed. * * Policy: * - You must own the file, and * - You must not change system flags, and * - You must not change flags on character/block devices. * * vp - vnode * cred - credentials of the invoker * owner_uid - uid of the file-system object * changing_sysflags - true if the invoker wants to change system flags */ int genfs_can_chflags(vnode_t *vp, kauth_cred_t cred, uid_t owner_uid, bool changing_sysflags) { /* The user must own the file. */ if (kauth_cred_geteuid(cred) != owner_uid) { return EPERM; } if (changing_sysflags) { return EPERM; } /* * Unprivileged users cannot change the flags on devices, even if they * own them. */ if (vp->v_type == VCHR || vp->v_type == VBLK) { return EPERM; } return 0; } /* * Common "sticky" policy. * * When a directory is "sticky" (as determined by the caller), this * function may help implementing the following policy: * - Renaming a file in it is only possible if the user owns the directory * or the file being renamed. * - Deleting a file from it is only possible if the user owns the * directory or the file being deleted. */ int genfs_can_sticky(vnode_t *vp, kauth_cred_t cred, uid_t dir_uid, uid_t file_uid) { if (kauth_cred_geteuid(cred) != dir_uid && kauth_cred_geteuid(cred) != file_uid) return EPERM; return 0; } int genfs_can_extattr(vnode_t *vp, kauth_cred_t cred, accmode_t accmode, int attrnamespace) { /* * Kernel-invoked always succeeds. */ if (cred == NOCRED) return 0; switch (attrnamespace) { case EXTATTR_NAMESPACE_SYSTEM: return kauth_authorize_system(cred, KAUTH_SYSTEM_FS_EXTATTR, 0, vp->v_mount, NULL, NULL); case EXTATTR_NAMESPACE_USER: return VOP_ACCESS(vp, accmode, cred); default: return EPERM; } } int genfs_access(void *v) { struct vop_access_args *ap = v; KASSERT((ap->a_accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND)) == 0); return VOP_ACCESSX(ap->a_vp, ap->a_accmode, ap->a_cred); } int genfs_accessx(void *v) { struct vop_accessx_args *ap = v; int error; accmode_t accmode = ap->a_accmode; error = vfs_unixify_accmode(&accmode); if (error != 0) return error; if (accmode == 0) return 0; return VOP_ACCESS(ap->a_vp, accmode, ap->a_cred); } /* * genfs_pathconf: * * Standard implementation of POSIX pathconf, to get information about limits * for a filesystem. * Override per filesystem for the case where the filesystem has smaller * limits. */ int genfs_pathconf(void *v) { struct vop_pathconf_args *ap = v; switch (ap->a_name) { case _PC_PATH_MAX: *ap->a_retval = PATH_MAX; return 0; case _PC_ACL_EXTENDED: case _PC_ACL_NFS4: *ap->a_retval = 0; return 0; default: return EINVAL; } }