/*-
* 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.
*
*/
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;
/*
* 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;
/*
* 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;
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;
}
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;
/*
* 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;
/*
* 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);
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);
}
/*
* 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;
}
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);
/*
* 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;
/*
* 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);
/*
* 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;
/*
* 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;
}
}