/* * On most/all systems it can be expected that a task with root * permissions also is a member of the root group, Since the * test suite is always run as root we check first that CRED() is * a member of the root group, and secondly that it is not a member * of our fake group. This test will break is someone happens to * create group number NGROUPS_MAX-1 and then added root to it. */ static int splat_cred_test3(struct file *file, void *arg) { gid_t root_gid, fake_gid; int rc; root_gid = 0; fake_gid = NGROUPS_MAX-1; rc = groupmember(root_gid, CRED()); if (!rc) { splat_vprint(file, SPLAT_CRED_TEST3_NAME, "Failed root git %d expected to be member " "of CRED() groups: %d\n", root_gid, rc); return -EIDRM; } rc = groupmember(fake_gid, CRED()); if (rc) { splat_vprint(file, SPLAT_CRED_TEST3_NAME, "Failed fake git %d expected not to be member " "of CRED() groups: %d\n", fake_gid, rc); return -EIDRM; } splat_vprint(file, SPLAT_CRED_TEST3_NAME, "Success root gid " "is a member of the expected groups: %d\n", rc); return rc; } /* splat_cred_test3() */
/* * Check to see if id is a groupmember. If cred * has ksid info then sidlist is checked first * and if still not found then POSIX groups are checked * * Will use a straight FUID compare when possible. */ boolean_t zfs_groupmember(zfsvfs_t *zfsvfs, uint64_t id, cred_t *cr) { ksid_t *ksid = crgetsid(cr, KSID_GROUP); uid_t gid; if (ksid) { int i; ksid_t *ksid_groups; ksidlist_t *ksidlist = crgetsidlist(cr); uint32_t idx = FUID_INDEX(id); uint32_t rid = FUID_RID(id); ASSERT(ksidlist); ksid_groups = ksidlist->ksl_sids; for (i = 0; i != ksidlist->ksl_nsid; i++) { if (idx == 0) { if (id != IDMAP_WK_CREATOR_GROUP_GID && id == ksid_groups[i].ks_id) { return (B_TRUE); } } else { char *domain; domain = zfs_fuid_find_by_idx(zfsvfs, idx); ASSERT(domain != NULL); if (strcmp(domain, IDMAP_WK_CREATOR_SID_AUTHORITY) == 0) return (B_FALSE); if ((strcmp(domain, ksid_groups[i].ks_domain->kd_name) == 0) && rid == ksid_groups[i].ks_rid) return (B_TRUE); } } } /* * Not found in ksidlist, check posix groups */ gid = zfs_fuid_map_id(zfsvfs, id, cr, ZFS_GROUP); #ifdef __APPLE__ return (groupmember(gid, (kauth_cred_t)cr)); #else return (groupmember(gid, cr)); #endif }
static int shm_chown(struct file *fp, uid_t uid, gid_t gid, struct ucred *active_cred, struct thread *td) { struct shmfd *shmfd; int error; error = 0; shmfd = fp->f_data; mtx_lock(&shm_timestamp_lock); #ifdef MAC error = mac_posixshm_check_setowner(active_cred, shmfd, uid, gid); if (error != 0) goto out; #endif if (uid == (uid_t)-1) uid = shmfd->shm_uid; if (gid == (gid_t)-1) gid = shmfd->shm_gid; if (((uid != shmfd->shm_uid && uid != active_cred->cr_uid) || (gid != shmfd->shm_gid && !groupmember(gid, active_cred))) && (error = priv_check_cred(active_cred, PRIV_VFS_CHOWN, 0))) goto out; shmfd->shm_uid = uid; shmfd->shm_gid = gid; out: mtx_unlock(&shm_timestamp_lock); return (error); }
static int seeotheruids_check(struct ucred *cr1, struct ucred *cr2) { if (!seeotheruids_enabled) return (0); if (primarygroup_enabled) { if (cr1->cr_rgid == cr2->cr_rgid) return (0); } if (specificgid_enabled) { if (cr1->cr_rgid == specificgid || groupmember(specificgid, cr1)) return (0); } if (cr1->cr_ruid == cr2->cr_ruid) return (0); if (suser_privileged) { if (priv_check_cred(cr1, PRIV_SEEOTHERUIDS, 0) == 0) return (0); } return (ESRCH); }
static int ksem_chown(struct file *fp, uid_t uid, gid_t gid, struct ucred *active_cred, struct thread *td) { struct ksem *ks; int error; error = 0; ks = fp->f_data; mtx_lock(&sem_lock); #ifdef MAC error = mac_posixsem_check_setowner(active_cred, ks, uid, gid); if (error != 0) goto out; #endif if (uid == (uid_t)-1) uid = ks->ks_uid; if (gid == (gid_t)-1) gid = ks->ks_gid; if (((uid != ks->ks_uid && uid != active_cred->cr_uid) || (gid != ks->ks_gid && !groupmember(gid, active_cred))) && (error = priv_check_cred(active_cred, PRIV_VFS_CHOWN, 0))) goto out; ks->ks_uid = uid; ks->ks_gid = gid; out: mtx_unlock(&sem_lock); return (error); }
/* * Change the mode on a file. * Inode must be locked before calling. */ static int ext2_chmod(struct vnode *vp, int mode, struct ucred *cred, struct thread *td) { struct inode *ip = VTOI(vp); int error; /* * To modify the permissions on a file, must possess VADMIN * for that file. */ if ((error = VOP_ACCESS(vp, VADMIN, cred, td))) return (error); /* * Privileged processes may set the sticky bit on non-directories, * as well as set the setgid bit on a file with a group that the * process is not a member of. */ if (vp->v_type != VDIR && (mode & S_ISTXT)) { error = priv_check_cred(cred, PRIV_VFS_STICKYFILE, 0); if (error) return (EFTYPE); } if (!groupmember(ip->i_gid, cred) && (mode & ISGID)) { error = priv_check_cred(cred, PRIV_VFS_SETGID, 0); if (error) return (error); } ip->i_mode &= ~ALLPERMS; ip->i_mode |= (mode & ALLPERMS); ip->i_flag |= IN_CHANGE; return (0); }
/* * Perform chown operation on inode ip; * inode must be locked prior to call. */ static int ext2fs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred, struct proc *p) { struct inode *ip = VTOI(vp); uid_t ouid; gid_t ogid; int error = 0; if (uid == (uid_t)VNOVAL) uid = ip->i_e2fs_uid; if (gid == (gid_t)VNOVAL) gid = ip->i_e2fs_gid; /* * If we don't own the file, are trying to change the owner * of the file, or are not a member of the target group, * the caller must be superuser or the call fails. */ if ((cred->cr_uid != ip->i_e2fs_uid || uid != ip->i_e2fs_uid || (gid != ip->i_e2fs_gid && !groupmember((gid_t)gid, cred))) && (error = suser_ucred(cred))) return (error); ogid = ip->i_e2fs_gid; ouid = ip->i_e2fs_uid; ip->i_e2fs_gid = gid; ip->i_e2fs_uid = uid; if (ouid != uid || ogid != gid) ip->i_flag |= IN_CHANGE; if (ouid != uid && cred->cr_uid != 0) ip->i_e2fs_mode &= ~ISUID; if (ogid != gid && cred->cr_uid != 0) ip->i_e2fs_mode &= ~ISGID; return (0); }
int secpolicy_vnode_setids_setgids(kauth_cred_t cred, gid_t gid) { if (groupmember(gid, cred)) return (0); return kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL); }
int /* ERRNO if error, 0 if successful. */ sam_access_ino( sam_node_t *ip, /* pointer to inode. */ int mode, /* mode of access to be verified */ boolean_t locked, /* is ip->inode_rwl held by caller? */ cred_t *credp) /* credentials pointer. */ { int shift = 0; ASSERT(!locked || RW_LOCK_HELD(&ip->inode_rwl)); /* * If requesting write access, and read only filesystem or WORM file * return error. */ if (mode & S_IWRITE) { if (ip->mp->mt.fi_mflag & MS_RDONLY) { return (EROFS); } if (ip->di.status.b.worm_rdonly && !S_ISDIR(ip->di.mode)) { return (EROFS); } } if (!locked) { RW_LOCK_OS(&ip->inode_rwl, RW_READER); } /* Use ACL, if present, to check access. */ if (ip->di.status.b.acl) { int error; error = sam_acl_access(ip, mode, credp); if (!locked) { RW_UNLOCK_OS(&ip->inode_rwl, RW_READER); } return (error); } if (!locked) { RW_UNLOCK_OS(&ip->inode_rwl, RW_READER); } if (crgetuid(credp) != ip->di.uid) { shift += 3; if (!groupmember((uid_t)ip->di.gid, credp)) { shift += 3; } } mode &= ~(ip->di.mode << shift); if (mode == 0) { return (0); } return (secpolicy_vnode_access(credp, SAM_ITOV(ip), ip->di.uid, mode)); }
/*ARGSUSED*/ static int bootfs_access(vnode_t *vp, int mode, int flags, cred_t *cr, caller_context_t *ct) { int shift = 0; bootfs_node_t *bpn = (bootfs_node_t *)vp->v_data; if (crgetuid(cr) != bpn->bvn_attr.va_uid) { shift += 3; if (groupmember(bpn->bvn_attr.va_gid, cr) == 0) shift += 3; } return (secpolicy_vnode_access2(cr, vp, bpn->bvn_attr.va_uid, bpn->bvn_attr.va_mode << shift, mode)); }
/* * Standard access() like check. Figure out which mode bits apply * to the caller then pass the missing mode bits to the secpolicy function. */ static int nm_access_unlocked(void *vnp, int mode, cred_t *crp) { struct namenode *nodep = vnp; int shift = 0; if (crgetuid(crp) != nodep->nm_vattr.va_uid) { shift += 3; if (!groupmember(nodep->nm_vattr.va_gid, crp)) shift += 3; } return (secpolicy_vnode_access2(crp, NMTOV(nodep), nodep->nm_vattr.va_uid, nodep->nm_vattr.va_mode << shift, mode)); }
/* ARGSUSED */ static int auto_access( vnode_t *vp, int mode, int flags, cred_t *cred, caller_context_t *ct) { fnnode_t *fnp = vntofn(vp); vnode_t *newvp; int error; AUTOFS_DPRINT((4, "auto_access: vp=%p\n", (void *)vp)); if (error = auto_trigger_mount(vp, cred, &newvp)) goto done; if (newvp != NULL) { /* * Node is mounted on. */ error = VOP_ACCESS(newvp, mode, 0, cred, ct); VN_RELE(newvp); } else { int shift = 0; /* * really interested in the autofs node, check the * access on it */ ASSERT(error == 0); if (crgetuid(cred) != fnp->fn_uid) { shift += 3; if (groupmember(fnp->fn_gid, cred) == 0) shift += 3; } error = secpolicy_vnode_access2(cred, vp, fnp->fn_uid, fnp->fn_mode << shift, mode); } done: AUTOFS_DPRINT((5, "auto_access: error=%d\n", error)); return (error); }
/* * This helper may be used by VFSs to implement unix chown semantics. */ int vop_helper_chown(struct vnode *vp, uid_t new_uid, gid_t new_gid, struct ucred *cred, uid_t *cur_uidp, gid_t *cur_gidp, mode_t *cur_modep) { gid_t ogid; uid_t ouid; int error; if (new_uid == (uid_t)VNOVAL) new_uid = *cur_uidp; if (new_gid == (gid_t)VNOVAL) new_gid = *cur_gidp; /* * If we don't own the file, are trying to change the owner * of the file, or are not a member of the target group, * the caller must be privileged or the call fails. */ if ((cred->cr_uid != *cur_uidp || new_uid != *cur_uidp || (new_gid != *cur_gidp && !(cred->cr_gid == new_gid || groupmember(new_gid, cred)))) && (error = priv_check_cred(cred, PRIV_VFS_CHOWN, 0))) { return (error); } ogid = *cur_gidp; ouid = *cur_uidp; /* XXX QUOTA CODE */ *cur_uidp = new_uid; *cur_gidp = new_gid; /* XXX QUOTA CODE */ /* * DragonFly clears both SUID and SGID if either the owner or * group is changed and root isn't doing it. If root is doing * it we do not clear SUID/SGID. */ if (cred->cr_uid != 0 && (ouid != new_uid || ogid != new_gid)) *cur_modep &= ~(S_ISUID | S_ISGID); return(0); }
/* * Perform chown operation on inode ip; * inode must be locked prior to call. */ static int ext2_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred, struct thread *td) { struct inode *ip = VTOI(vp); uid_t ouid; gid_t ogid; int error = 0; if (uid == (uid_t)VNOVAL) uid = ip->i_uid; if (gid == (gid_t)VNOVAL) gid = ip->i_gid; /* * To modify the ownership of a file, must possess VADMIN * for that file. */ if ((error = VOP_ACCESS(vp, VADMIN, cred, td))) return (error); /* * To change the owner of a file, or change the group of a file * to a group of which we are not a member, the caller must * have privilege. */ if (uid != ip->i_uid || (gid != ip->i_gid && !groupmember(gid, cred))) { error = priv_check_cred(cred, PRIV_VFS_CHOWN, 0); if (error) return (error); } ogid = ip->i_gid; ouid = ip->i_uid; ip->i_gid = gid; ip->i_uid = uid; ip->i_flag |= IN_CHANGE; if ((ip->i_mode & (ISUID | ISGID)) && (ouid != uid || ogid != gid)) { if (priv_check_cred(cred, PRIV_VFS_RETAINSUGID, 0) != 0) ip->i_mode &= ~(ISUID | ISGID); } return (0); }
/* * This helper may be used by VFSs to implement unix chmod semantics. */ int vop_helper_chmod(struct vnode *vp, mode_t new_mode, struct ucred *cred, uid_t cur_uid, gid_t cur_gid, mode_t *cur_modep) { int error; if (cred->cr_uid != cur_uid) { error = priv_check_cred(cred, PRIV_VFS_CHMOD, 0); if (error) return (error); } if (cred->cr_uid) { if (vp->v_type != VDIR && (*cur_modep & S_ISTXT)) return (EFTYPE); if (!groupmember(cur_gid, cred) && (*cur_modep & S_ISGID)) return (EPERM); } *cur_modep &= ~ALLPERMS; *cur_modep |= new_mode & ALLPERMS; return(0); }
/* * Change the mode on a file. * Inode must be locked before calling. */ static int ext2fs_chmod(struct vnode *vp, mode_t mode, struct ucred *cred, struct proc *p) { struct inode *ip = VTOI(vp); int error; if (cred->cr_uid != ip->i_e2fs_uid && (error = suser_ucred(cred))) return (error); if (cred->cr_uid) { if (vp->v_type != VDIR && (mode & S_ISTXT)) return (EFTYPE); if (!groupmember(ip->i_e2fs_gid, cred) && (mode & ISGID)) return (EPERM); } ip->i_e2fs_mode &= ~ALLPERMS; ip->i_e2fs_mode |= (mode & ALLPERMS); ip->i_flag |= IN_CHANGE; if ((vp->v_flag & VTEXT) && (ip->i_e2fs_mode & S_ISTXT) == 0) (void) uvm_vnp_uncache(vp); return (0); }
/* * Change the mode on a file. * Inode must be locked before calling. */ int ufs_chmod(struct vnode *vp, int mode, struct ucred *cred, struct proc *p) { struct inode *ip = VTOI(vp); int error; if (cred->cr_uid != DIP(ip, uid) && (error = suser_ucred(cred))) return (error); if (cred->cr_uid) { if (vp->v_type != VDIR && (mode & S_ISTXT)) return (EFTYPE); if (!groupmember(DIP(ip, gid), cred) && (mode & ISGID)) return (EPERM); } DIP_AND(ip, mode, ~ALLPERMS); DIP_OR(ip, mode, mode & ALLPERMS); ip->i_flag |= IN_CHANGE; if ((vp->v_flag & VTEXT) && (DIP(ip, mode) & S_ISTXT) == 0) (void) uvm_vnp_uncache(vp); return (0); }
/* * This function is used for secpolicy_setattr(). It must call an * access() like function while it is already holding the * dv_contents lock. We only care about this when dv_attr != NULL; * so the unlocked access call only concerns itself with that * particular branch of devfs_access(). */ static int devfs_unlocked_access(void *vdv, int mode, struct cred *cr) { struct dv_node *dv = vdv; int shift = 0; uid_t owner = dv->dv_attr->va_uid; /* Check access based on owner, group and public permissions. */ if (crgetuid(cr) != owner) { shift += 3; if (groupmember(dv->dv_attr->va_gid, cr) == 0) shift += 3; } /* compute missing mode bits */ mode &= ~(dv->dv_attr->va_mode << shift); if (mode == 0) return (0); return (secpolicy_vnode_access(cr, DVTOV(dv), owner, mode)); }
int tmp_taccess(void *vtp, int mode, struct cred *cred) { struct tmpnode *tp = vtp; int shift = 0; /* * Check access based on owner, group and * public permissions in tmpnode. */ if (crgetuid(cred) != tp->tn_uid) { shift += MODESHIFT; if (groupmember(tp->tn_gid, cred) == 0) shift += MODESHIFT; } /* compute missing mode bits */ mode &= ~(tp->tn_mode << shift); if (mode == 0) return (0); return (secpolicy_vnode_access(cred, TNTOV(tp), tp->tn_uid, mode)); }
/* * uid and gid in sffs determine owner and group for all files. */ static int sfnode_access(sfnode_t *node, mode_t mode, cred_t *cr) { sffs_data_t *sffs = node->sf_sffs; mode_t m; int shift = 0; int error; vnode_t *vp; ASSERT(MUTEX_HELD(&sffs_lock)); /* get the mode from the cache or provider */ if (sfnode_stat_cached(node)) error = 0; else error = sfnode_update_stat_cache(node); m = (error == 0) ? node->sf_stat.sf_mode : 0; /* * mask off the permissions based on uid/gid */ if (crgetuid(cr) != sffs->sf_uid) { shift += 3; if (groupmember(sffs->sf_gid, cr) == 0) shift += 3; } mode &= ~(m << shift); if (mode == 0) { error = 0; } else { vp = sfnode_get_vnode(node); error = secpolicy_vnode_access(cr, vp, sffs->sf_uid, mode); VN_RELE(vp); } return (error); }
/* * Q_GETQUOTA - return current values in a dqblk structure. */ static int _getquota(struct thread *td, struct mount *mp, u_long id, int type, struct dqblk64 *dqb) { struct dquot *dq; int error; switch (type) { case USRQUOTA: if ((td->td_ucred->cr_uid != id) && !unprivileged_get_quota) { error = priv_check(td, PRIV_VFS_GETQUOTA); if (error) return (error); } break; case GRPQUOTA: if (!groupmember(id, td->td_ucred) && !unprivileged_get_quota) { error = priv_check(td, PRIV_VFS_GETQUOTA); if (error) return (error); } break; default: return (EINVAL); } dq = NODQUOT; error = dqget(NULLVP, id, VFSTOUFS(mp), type, &dq); if (error) return (error); *dqb = dq->dq_dqb; dqrele(NULLVP, dq); return (error); }
int /* ERRNO if error, 0 if successful. */ sam_setattr_ino( sam_node_t *ip, /* pointer to inode. */ vattr_t *vap, /* vattr pointer. */ int flags, /* flags. */ cred_t *credp) /* credentials pointer. */ { uint_t mask; int error = 0; vnode_t *vp; sam_mode_t oldmode, mode; timespec_t system_time; vattr_t oldva; oldva.va_mode = ip->di.mode; oldva.va_uid = ip->di.uid; oldva.va_gid = ip->di.gid; vp = SAM_ITOV(ip); if (vap->va_mask & AT_NOSET) { return (EINVAL); } mode = vap->va_mode & ~S_IFMT; SAM_HRESTIME(&system_time); /* * Enforce the "read only" portion of WORM files. */ if (ip->di.status.b.worm_rdonly && !S_ISDIR(ip->di.mode)) { error = sam_chk_worm(mode, vap->va_mask, ip); if (error) { return (error); } } /* * Generic setattr security policy check. */ if (error = secpolicy_vnode_setattr(credp, vp, vap, &oldva, flags, sam_access_ino_ul, ip)) { return (error); } mask = vap->va_mask; if (mask & AT_SIZE) { /* -----Change size */ if (error == 0) { /* Can only truncate a regular file */ if (S_ISREQ(ip->di.mode)) { error = EINVAL; goto out; } else if (SAM_PRIVILEGE_INO(ip->di.version, ip->di.id.ino)) { error = EPERM; /* Can't trunc priv'ed inodes */ goto out; } if (S_ISSEGI(&ip->di) && (vap->va_size != 0)) { /* * If file is segment access and not truncating * to zero--fix. */ error = EINVAL; goto out; } /* * Might need to do TRANS_ITRUNC here for LQFS.... */ if ((error = sam_clear_ino(ip, (offset_t)vap->va_size, STALE_ARCHIVE, credp))) { goto out; } } } if (mask & AT_MODE) { /* -----Change mode */ /* Cannot change .inodes file */ if (ip->di.id.ino == SAM_INO_INO) { error = EPERM; goto out; } oldmode = ip->di.mode; ip->di.mode &= S_IFMT; ip->di.mode |= vap->va_mode & ~S_IFMT; if (ip->di.status.b.worm_rdonly) { if (!S_ISDIR(ip->di.mode)) { ip->di.mode &= ~WMASK; } if (oldmode & S_ISUID) { ip->di.mode |= S_ISUID; } } /* * In 4.6 there are two modes of WORM trigger operation. * One is compatible with the 53xx SUN NAS series. This * mode uses the SUID bit by itself. The second mode is * called compatibility mode. This mode uses the transition * from a writeable mode as the trigger. Note, copying a * read-only file to a WORM capable volume does *NOT* * initiate the WORM trigger in this mode. */ if (samgt.license.license.lic_u.b.WORM_fs && (ip->di.version >= SAM_INODE_VERS_2) && (((vap->va_mode == S_ISUID) && (ip->mp->mt.fi_config & MT_ALLWORM)) || ((ip->mp->mt.fi_config & MT_ALLEMUL) && (((oldmode & RWXALLMASK) == RWXALLMASK) || ((vap->va_mode != S_ISUID) && (oldmode & WMASK) && !(ip->di.mode & WMASK)))))) { error = sam_worm_trigger(ip, oldmode, system_time); if (error) { ip->di.mode = oldmode; goto out; } else if ((ip->mp->mt.fi_config & MT_ALLEMUL) && ((oldmode & RWXALLMASK) == RWXALLMASK)) { ip->di.mode = oldmode; } else if ((vap->va_mode == S_ISUID) && (!S_ISDIR(ip->di.mode))) { if (ip->mp->mt.fi_config & MT_ALLEMUL) { ip->di.mode = oldmode & (S_IFMT | RMASK); } else { ip->di.mode = S_ISUID | (oldmode & (S_IFMT | RMASK)); } } } TRANS_INODE(ip->mp, ip); if (S_ISATTRDIR(oldmode)) { ip->di.mode |= S_IFATTRDIR; } sam_mark_ino(ip, SAM_CHANGED); } if (mask & (AT_UID | AT_GID)) { /* -----Change uid/gid */ int ouid, ogid; if (vap->va_mask & AT_MODE) { ip->di.mode = (ip->di.mode & S_IFMT) | (vap->va_mode & ~S_IFMT); } /* * To change file ownership, a process must have * privilege if: * * If it is not the owner of the file, or * if doing restricted chown semantics and * either changing the ownership to someone else or * changing the group to a group that we are not * currently in. */ if (crgetuid(credp) != ip->di.uid || (rstchown && (((mask & AT_UID) && vap->va_uid != ip->di.uid) || ((mask & AT_GID) && !groupmember(vap->va_gid, credp))))) { error = secpolicy_vnode_owner(credp, vap->va_uid); if (error) { goto out; } } ouid = ip->di.uid; ogid = ip->di.gid; if (error = sam_quota_chown(ip->mp, ip, (mask&AT_UID) ? vap->va_uid : ouid, (mask&AT_GID) ? vap->va_gid : ogid, credp)) { goto out; } if (mask & AT_UID) ip->di.uid = vap->va_uid; if (mask & AT_GID) ip->di.gid = vap->va_gid; ip->di.status.b.archdone = 0; TRANS_INODE(ip->mp, ip); sam_mark_ino(ip, SAM_CHANGED); /* * Notify arfind and event daemon of setattr. */ sam_send_to_arfind(ip, AE_change, 0); if (ip->mp->ms.m_fsev_buf) { sam_send_event(ip->mp, &ip->di, ev_change, 0, 0, ip->di.change_time.tv_sec); } } if (mask & (AT_ATIME | AT_MTIME)) { /* -----Modify times */ /* * Synchronously flush pages so dates do not get changed after * utime. If staging, finish stage and then flush pages. */ if (ip->flags.b.staging) { /* * Might need to do TRANS_ITRUNC or similar here * for LQFS */ if ((error = sam_clear_file(ip, ip->di.rm.size, MAKE_ONLINE, credp))) { goto out; } } sam_flush_pages(ip, 0); if (mask & AT_ATIME) { /* * The access time field is used by WORM operations to * store the retention timestamp. This is intercepted * here and stored in the inode's retention period * time fields if either the field hasn't been set or * the provided value exceeds what is currently there * (i.e. we're extending the period). */ error = sam_check_worm_capable(ip, TRUE); if (!error) { boolean_t lite_mode = ((ip->mp->mt.fi_config & MT_LITE_WORM) != 0); boolean_t is_priv = (secpolicy_fs_config(credp, ip->mp->mi.m_vfsp) == 0); if (S_ISREG(ip->di.mode) && !WORM(ip)) { /* * Regular file in WORM capable * directory. Set access time per the * request. */ ip->di.access_time.tv_sec = vap->va_atime.tv_sec; ip->di.access_time.tv_nsec = vap->va_atime.tv_nsec; ip->flags.b.accessed = 1; } else if (WORM(ip) && (ip->di.version >= SAM_INODE_VERS_2)) { boolean_t extend_period; /* * Extend the retention period if so * requested. If lite mode and a * privileged user or a directory allow * the retention period to be shortened. */ if (vap->va_atime.tv_sec > ip->di2.rperiod_start_time + ip->di2.rperiod_duration * 60) { extend_period = 1; } else { extend_period = 0; } if (S_ISREG(ip->di.mode) && extend_period) { error = sam_set_rperiod(ip, vap, is_priv && lite_mode); } else if (S_ISDIR(ip->di.mode) || (S_ISREG(ip->di.mode) && is_priv && lite_mode)) { /* * If the requested time would * result in a non- negative * retention period, set the * period to the difference of * the request and current time. * A negative retention period * is not allowed. */ if (vap->va_atime.tv_sec > system_time.tv_sec) { if (vap->va_atime.tv_sec == INT_MAX) { ip->di2.rperiod_duration = 0; } else { ip->di2.rperiod_duration = 1 + (vap->va_atime.tv_sec - system_time.tv_sec)/60; } } else { error = EINVAL; } } if (error) { goto out; } } else { /* * Shouldn't get here, invalid request. */ error = EINVAL; goto out; } TRANS_INODE(ip->mp, ip); sam_mark_ino(ip, SAM_CHANGED); } else { error = 0; ip->di.access_time.tv_sec = vap->va_atime.tv_sec; ip->di.access_time.tv_nsec = vap->va_atime.tv_nsec; ip->flags.b.accessed = 1; TRANS_INODE(ip->mp, ip); } } if (mask & AT_MTIME) { if (!ip->di.status.b.worm_rdonly) { ip->di.modify_time.tv_sec = vap->va_mtime.tv_sec; ip->di.modify_time.tv_nsec = vap->va_mtime.tv_nsec; ip->di.change_time.tv_sec = system_time.tv_sec; ip->di.change_time.tv_nsec = system_time.tv_nsec; ip->flags.b.updated = 1; /* Modify time has been set */ ip->flags.b.dirty = 1; } TRANS_INODE(ip->mp, ip); } } /* * Check for and apply ACL info, if present. */ if (ip->di.status.b.acl && !ip->di.status.b.worm_rdonly) { if (SAM_IS_SHARED_FS(ip->mp) && SAM_IS_SHARED_SERVER(ip->mp)) { RW_UNLOCK_OS(&ip->inode_rwl, RW_WRITER); sam_callout_acl(ip, ip->mp->ms.m_client_ord); RW_LOCK_OS(&ip->inode_rwl, RW_WRITER); } if (error = sam_acl_setattr(ip, vap)) { goto out; } } if (ip->mp->mt.fi_config & MT_SHARED_WRITER) { if ((error == 0) && (ip->flags.bits & (SAM_ACCESSED|SAM_UPDATED|SAM_CHANGED))) { (void) sam_update_inode(ip, SAM_SYNC_ONE, FALSE); } } out: return (error); }
int secpolicy_vnode_setattr(kauth_cred_t cred, struct vnode *vp, struct vattr *vap, const struct vattr *ovap, int flags, int unlocked_access(void *, int, kauth_cred_t), void *node) { int mask = vap->va_mask; int error; if (mask & AT_SIZE) { if (vp->v_type == VDIR) return (EISDIR); error = unlocked_access(node, VWRITE, cred); if (error) return (error); } if (mask & AT_MODE) { /* * If not the owner of the file then check privilege * for two things: the privilege to set the mode at all * and, if we're setting setuid, we also need permissions * to add the set-uid bit, if we're not the owner. * In the specific case of creating a set-uid root * file, we need even more permissions. */ error = secpolicy_vnode_setdac(cred, ovap->va_uid); if (error) return (error); error = secpolicy_setid_setsticky_clear(vp, vap, ovap, cred); if (error) return (error); } else { vap->va_mode = ovap->va_mode; } if (mask & (AT_UID | AT_GID)) { error = secpolicy_vnode_setdac(cred, ovap->va_uid); if (error) return (error); /* * To change the owner of a file, or change the group of a file to a * group of which we are not a member, the caller must have * privilege. */ if (((mask & AT_UID) && vap->va_uid != ovap->va_uid) || ((mask & AT_GID) && vap->va_gid != ovap->va_gid && !groupmember(vap->va_gid, cred))) { error = priv_check_cred(cred, PRIV_VFS_CHOWN, 0); if (error) return (error); } if (((mask & AT_UID) && vap->va_uid != ovap->va_uid) || ((mask & AT_GID) && vap->va_gid != ovap->va_gid)) { secpolicy_setid_clear(vap, cred); } } if (mask & (AT_ATIME | AT_MTIME)) { /* * From utimes(2): * If times is NULL, ... The caller must be the owner of * the file, have permission to write the file, or be the * super-user. * If times is non-NULL, ... The caller must be the owner of * the file or be the super-user. */ error = secpolicy_vnode_setdac(cred, ovap->va_uid); if (error && (vap->va_vaflags & VA_UTIMES_NULL)) error = unlocked_access(node, VWRITE, cred); if (error) return (error); } return (0); }
/* * XXX Copied from illumos. Should not be here; should be under * external/cddl/osnet/dist. Not sure why it is even in illumos's * policy.c rather than somewhere in vnode.c or something. */ int secpolicy_vnode_setattr(kauth_cred_t cred, struct vnode *vp, struct vattr *vap, const struct vattr *ovap, int flags, int unlocked_access(void *, int, kauth_cred_t), void *node) { int mask = vap->va_mask; int error = 0; boolean_t skipaclchk = (flags & ATTR_NOACLCHECK) ? B_TRUE : B_FALSE; if (mask & AT_SIZE) { if (vp->v_type == VDIR) { error = EISDIR; goto out; } /* * If ATTR_NOACLCHECK is set in the flags, then we don't * perform the secondary unlocked_access() call since the * ACL (if any) is being checked there. */ if (skipaclchk == B_FALSE) { error = unlocked_access(node, VWRITE, cred); if (error) goto out; } } if (mask & AT_MODE) { /* * If not the owner of the file then check privilege * for two things: the privilege to set the mode at all * and, if we're setting setuid, we also need permissions * to add the set-uid bit, if we're not the owner. * In the specific case of creating a set-uid root * file, we need even more permissions. */ if ((error = secpolicy_vnode_setdac(cred, ovap->va_uid)) != 0) goto out; if ((error = secpolicy_setid_setsticky_clear(vp, vap, ovap, cred)) != 0) goto out; } else vap->va_mode = ovap->va_mode; if (mask & (AT_UID|AT_GID)) { boolean_t checkpriv = B_FALSE; /* * Chowning files. * * If you are the file owner: * chown to other uid FILE_CHOWN_SELF * chown to gid (non-member) FILE_CHOWN_SELF * chown to gid (member) <none> * * Instead of PRIV_FILE_CHOWN_SELF, FILE_CHOWN is also * acceptable but the first one is reported when debugging. * * If you are not the file owner: * chown from root PRIV_FILE_CHOWN + zone * chown from other to any PRIV_FILE_CHOWN * */ if (kauth_cred_getuid(cred) != ovap->va_uid) { checkpriv = B_TRUE; } else { if (((mask & AT_UID) && vap->va_uid != ovap->va_uid) || ((mask & AT_GID) && vap->va_gid != ovap->va_gid && !groupmember(vap->va_gid, cred))) { checkpriv = B_TRUE; } } /* * If necessary, check privilege to see if update can be done. */ if (checkpriv && (error = secpolicy_vnode_chown(cred, ovap->va_uid)) != 0) { goto out; } /* * If the file has either the set UID or set GID bits * set and the caller can set the bits, then leave them. */ secpolicy_setid_clear(vap, cred); } if (mask & (AT_ATIME|AT_MTIME)) { /* * If not the file owner and not otherwise privileged, * always return an error when setting the * time other than the current (ATTR_UTIME flag set). * If setting the current time (ATTR_UTIME not set) then * unlocked_access will check permissions according to policy. */ if (kauth_cred_getuid(cred) != ovap->va_uid) { if (flags & ATTR_UTIME) error = secpolicy_vnode_utime_modify(cred); else if (skipaclchk == B_FALSE) { error = unlocked_access(node, VWRITE, cred); if (error == EACCES && secpolicy_vnode_utime_modify(cred) == 0) error = 0; } if (error) goto out; } } /* * Check for optional attributes here by checking the following: */ if (mask & AT_XVATTR) error = secpolicy_xvattr((xvattr_t *)vap, ovap->va_uid, cred, vp->v_type); out: return (error); }
/* * Allocate a new inode. */ int ext2fs_makeinode(int mode, struct vnode *dvp, struct vnode **vpp, struct componentname *cnp) { struct inode *ip, *pdir; struct vnode *tvp; int error; pdir = VTOI(dvp); #ifdef DIAGNOSTIC if ((cnp->cn_flags & HASBUF) == 0) panic("ext2fs_makeinode: no name"); #endif *vpp = NULL; if ((mode & IFMT) == 0) mode |= IFREG; if ((error = ext2fs_inode_alloc(pdir, mode, cnp->cn_cred, &tvp)) != 0) { pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); return (error); } ip = VTOI(tvp); ip->i_e2fs_gid = pdir->i_e2fs_gid; ip->i_e2fs_uid = cnp->cn_cred->cr_uid; ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; ip->i_e2fs_mode = mode; tvp->v_type = IFTOVT(mode); /* Rest init'd in getnewvnode(). */ ip->i_e2fs_nlink = 1; if ((ip->i_e2fs_mode & ISGID) && !groupmember(ip->i_e2fs_gid, cnp->cn_cred) && suser_ucred(cnp->cn_cred)) ip->i_e2fs_mode &= ~ISGID; /* * Make sure inode goes to disk before directory entry. */ if ((error = ext2fs_update(ip, NULL, NULL, 1)) != 0) goto bad; error = ext2fs_direnter(ip, dvp, cnp); if (error != 0) goto bad; if ((cnp->cn_flags & SAVESTART) == 0) pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); *vpp = tvp; return (0); bad: /* * Write error occurred trying to update the inode * or the directory so must deallocate the inode. */ pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); ip->i_e2fs_nlink = 0; ip->i_flag |= IN_CHANGE; tvp->v_type = VNON; vput(tvp); return (error); }
/* * Allocate a new inode. */ int ufs_makeinode(int mode, struct vnode *dvp, struct vnode **vpp, struct componentname *cnp) { struct inode *ip, *pdir; struct direct newdir; struct vnode *tvp; int error; pdir = VTOI(dvp); #ifdef DIAGNOSTIC if ((cnp->cn_flags & HASBUF) == 0) panic("ufs_makeinode: no name"); #endif *vpp = NULL; if ((mode & IFMT) == 0) mode |= IFREG; if ((error = UFS_INODE_ALLOC(pdir, mode, cnp->cn_cred, &tvp)) != 0) { pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); return (error); } ip = VTOI(tvp); DIP_ASSIGN(ip, gid, DIP(pdir, gid)); DIP_ASSIGN(ip, uid, cnp->cn_cred->cr_uid); if ((error = getinoquota(ip)) || (error = ufs_quota_alloc_inode(ip, cnp->cn_cred))) { pool_put(&namei_pool, cnp->cn_pnbuf); UFS_INODE_FREE(ip, ip->i_number, mode); vput(tvp); vput(dvp); return (error); } ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; DIP_ASSIGN(ip, mode, mode); tvp->v_type = IFTOVT(mode); /* Rest init'd in getnewvnode(). */ ip->i_effnlink = 1; DIP_ASSIGN(ip, nlink, 1); if (DOINGSOFTDEP(tvp)) softdep_change_linkcnt(ip, 0); if ((DIP(ip, mode) & ISGID) && !groupmember(DIP(ip, gid), cnp->cn_cred) && suser_ucred(cnp->cn_cred)) DIP_AND(ip, mode, ~ISGID); /* * Make sure inode goes to disk before directory entry. */ if ((error = UFS_UPDATE(ip, !DOINGSOFTDEP(tvp))) != 0) goto bad; ufs_makedirentry(ip, cnp, &newdir); if ((error = ufs_direnter(dvp, tvp, &newdir, cnp, NULL)) != 0) goto bad; if ((cnp->cn_flags & SAVESTART) == 0) pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); *vpp = tvp; return (0); bad: /* * Write error occurred trying to update the inode * or the directory so must deallocate the inode. */ pool_put(&namei_pool, cnp->cn_pnbuf); vput(dvp); ip->i_effnlink = 0; DIP_ASSIGN(ip, nlink, 0); ip->i_flag |= IN_CHANGE; if (DOINGSOFTDEP(tvp)) softdep_change_linkcnt(ip, 0); tvp->v_type = VNON; vput(tvp); return (error); }
/* * Allocate a new inode. */ static int ext2_makeinode(int mode, struct vnode *dvp, struct vnode **vpp, struct componentname *cnp) { struct inode *ip, *pdir; struct vnode *tvp; int error; pdir = VTOI(dvp); #ifdef DIAGNOSTIC if ((cnp->cn_flags & HASBUF) == 0) panic("ext2_makeinode: no name"); #endif *vpp = NULL; if ((mode & IFMT) == 0) mode |= IFREG; error = ext2_valloc(dvp, mode, cnp->cn_cred, &tvp); if (error) { return (error); } ip = VTOI(tvp); ip->i_gid = pdir->i_gid; #ifdef SUIDDIR { /* * if we are * not the owner of the directory, * and we are hacking owners here, (only do this where told to) * and we are not giving it TOO root, (would subvert quotas) * then go ahead and give it to the other user. * Note that this drops off the execute bits for security. */ if ( (dvp->v_mount->mnt_flag & MNT_SUIDDIR) && (pdir->i_mode & ISUID) && (pdir->i_uid != cnp->cn_cred->cr_uid) && pdir->i_uid) { ip->i_uid = pdir->i_uid; mode &= ~07111; } else { ip->i_uid = cnp->cn_cred->cr_uid; } } #else ip->i_uid = cnp->cn_cred->cr_uid; #endif ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE; ip->i_mode = mode; tvp->v_type = IFTOVT(mode); /* Rest init'd in getnewvnode(). */ ip->i_nlink = 1; if ((ip->i_mode & ISGID) && !groupmember(ip->i_gid, cnp->cn_cred)) { if (priv_check_cred(cnp->cn_cred, PRIV_VFS_RETAINSUGID, 0)) ip->i_mode &= ~ISGID; } if (cnp->cn_flags & ISWHITEOUT) ip->i_flags |= UF_OPAQUE; /* * Make sure inode goes to disk before directory entry. */ error = ext2_update(tvp, !DOINGASYNC(tvp)); if (error) goto bad; error = ext2_direnter(ip, dvp, cnp); if (error) goto bad; *vpp = tvp; return (0); bad: /* * Write error occurred trying to update the inode * or the directory so must deallocate the inode. */ ip->i_nlink = 0; ip->i_flag |= IN_CHANGE; vput(tvp); return (error); }
static int ugidfw_rulecheck(struct mac_bsdextended_rule *rule, struct ucred *cred, struct vnode *vp, struct vattr *vap, int acc_mode) { int mac_granted, match, priv_granted; int i; /* * Is there a subject match? */ mtx_assert(&ugidfw_mtx, MA_OWNED); if (rule->mbr_subject.mbs_flags & MBS_UID_DEFINED) { match = ((cred->cr_uid <= rule->mbr_subject.mbs_uid_max && cred->cr_uid >= rule->mbr_subject.mbs_uid_min) || (cred->cr_ruid <= rule->mbr_subject.mbs_uid_max && cred->cr_ruid >= rule->mbr_subject.mbs_uid_min) || (cred->cr_svuid <= rule->mbr_subject.mbs_uid_max && cred->cr_svuid >= rule->mbr_subject.mbs_uid_min)); if (rule->mbr_subject.mbs_neg & MBS_UID_DEFINED) match = !match; if (!match) return (0); } if (rule->mbr_subject.mbs_flags & MBS_GID_DEFINED) { match = ((cred->cr_rgid <= rule->mbr_subject.mbs_gid_max && cred->cr_rgid >= rule->mbr_subject.mbs_gid_min) || (cred->cr_svgid <= rule->mbr_subject.mbs_gid_max && cred->cr_svgid >= rule->mbr_subject.mbs_gid_min)); if (!match) { for (i = 0; i < cred->cr_ngroups; i++) { if (cred->cr_groups[i] <= rule->mbr_subject.mbs_gid_max && cred->cr_groups[i] >= rule->mbr_subject.mbs_gid_min) { match = 1; break; } } } if (rule->mbr_subject.mbs_neg & MBS_GID_DEFINED) match = !match; if (!match) return (0); } if (rule->mbr_subject.mbs_flags & MBS_PRISON_DEFINED) { match = (cred->cr_prison->pr_id == rule->mbr_subject.mbs_prison); if (rule->mbr_subject.mbs_neg & MBS_PRISON_DEFINED) match = !match; if (!match) return (0); } /* * Is there an object match? */ if (rule->mbr_object.mbo_flags & MBO_UID_DEFINED) { match = (vap->va_uid <= rule->mbr_object.mbo_uid_max && vap->va_uid >= rule->mbr_object.mbo_uid_min); if (rule->mbr_object.mbo_neg & MBO_UID_DEFINED) match = !match; if (!match) return (0); } if (rule->mbr_object.mbo_flags & MBO_GID_DEFINED) { match = (vap->va_gid <= rule->mbr_object.mbo_gid_max && vap->va_gid >= rule->mbr_object.mbo_gid_min); if (rule->mbr_object.mbo_neg & MBO_GID_DEFINED) match = !match; if (!match) return (0); } if (rule->mbr_object.mbo_flags & MBO_FSID_DEFINED) { match = (bcmp(&(vp->v_mount->mnt_stat.f_fsid), &(rule->mbr_object.mbo_fsid), sizeof(rule->mbr_object.mbo_fsid)) == 0); if (rule->mbr_object.mbo_neg & MBO_FSID_DEFINED) match = !match; if (!match) return (0); } if (rule->mbr_object.mbo_flags & MBO_SUID) { match = (vap->va_mode & S_ISUID); if (rule->mbr_object.mbo_neg & MBO_SUID) match = !match; if (!match) return (0); } if (rule->mbr_object.mbo_flags & MBO_SGID) { match = (vap->va_mode & S_ISGID); if (rule->mbr_object.mbo_neg & MBO_SGID) match = !match; if (!match) return (0); } if (rule->mbr_object.mbo_flags & MBO_UID_SUBJECT) { match = (vap->va_uid == cred->cr_uid || vap->va_uid == cred->cr_ruid || vap->va_uid == cred->cr_svuid); if (rule->mbr_object.mbo_neg & MBO_UID_SUBJECT) match = !match; if (!match) return (0); } if (rule->mbr_object.mbo_flags & MBO_GID_SUBJECT) { match = (groupmember(vap->va_gid, cred) || vap->va_gid == cred->cr_rgid || vap->va_gid == cred->cr_svgid); if (rule->mbr_object.mbo_neg & MBO_GID_SUBJECT) match = !match; if (!match) return (0); } if (rule->mbr_object.mbo_flags & MBO_TYPE_DEFINED) { switch (vap->va_type) { case VREG: match = (rule->mbr_object.mbo_type & MBO_TYPE_REG); break; case VDIR: match = (rule->mbr_object.mbo_type & MBO_TYPE_DIR); break; case VBLK: match = (rule->mbr_object.mbo_type & MBO_TYPE_BLK); break; case VCHR: match = (rule->mbr_object.mbo_type & MBO_TYPE_CHR); break; case VLNK: match = (rule->mbr_object.mbo_type & MBO_TYPE_LNK); break; case VSOCK: match = (rule->mbr_object.mbo_type & MBO_TYPE_SOCK); break; case VFIFO: match = (rule->mbr_object.mbo_type & MBO_TYPE_FIFO); break; default: match = 0; } if (rule->mbr_object.mbo_neg & MBO_TYPE_DEFINED) match = !match; if (!match) return (0); } /* * MBI_APPEND should not be here as it should get converted to * MBI_WRITE. */ priv_granted = 0; mac_granted = rule->mbr_mode; if ((acc_mode & MBI_ADMIN) && (mac_granted & MBI_ADMIN) == 0 && priv_check_cred(cred, PRIV_VFS_ADMIN, 0) == 0) priv_granted |= MBI_ADMIN; if ((acc_mode & MBI_EXEC) && (mac_granted & MBI_EXEC) == 0 && priv_check_cred(cred, (vap->va_type == VDIR) ? PRIV_VFS_LOOKUP : PRIV_VFS_EXEC, 0) == 0) priv_granted |= MBI_EXEC; if ((acc_mode & MBI_READ) && (mac_granted & MBI_READ) == 0 && priv_check_cred(cred, PRIV_VFS_READ, 0) == 0) priv_granted |= MBI_READ; if ((acc_mode & MBI_STAT) && (mac_granted & MBI_STAT) == 0 && priv_check_cred(cred, PRIV_VFS_STAT, 0) == 0) priv_granted |= MBI_STAT; if ((acc_mode & MBI_WRITE) && (mac_granted & MBI_WRITE) == 0 && priv_check_cred(cred, PRIV_VFS_WRITE, 0) == 0) priv_granted |= MBI_WRITE; /* * Is the access permitted? */ if (((mac_granted | priv_granted) & acc_mode) != acc_mode) { if (ugidfw_logging) log(LOG_AUTHPRIV, "mac_bsdextended: %d:%d request %d" " on %d:%d failed. \n", cred->cr_ruid, cred->cr_rgid, acc_mode, vap->va_uid, vap->va_gid); return (EACCES); } /* * If the rule matched, permits access, and first match is enabled, * return success. */ if (ugidfw_firstmatch_enabled) return (EJUSTRETURN); else return (0); }
/* * Implement a version of vaccess() 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 vaccess_acl_posix1e(enum vtype type, uid_t file_uid, gid_t file_gid, struct acl *acl, accmode_t accmode, struct ucred *cred, int *privused) { struct acl_entry *acl_other, *acl_mask; accmode_t dac_granted; accmode_t priv_granted; accmode_t acl_mask_granted; int group_matched, i; KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND)) == 0, ("invalid bit in accmode")); KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE), ("VAPPEND without VWRITE")); /* * Look for a normal, non-privileged way to access the file/directory * as requested. If it exists, go with that. Otherwise, attempt to * use privileges granted via priv_granted. In some cases, which * privileges to use may be ambiguous due to "best match", in which * case fall back on first match for the time being. */ if (privused != NULL) *privused = 0; /* * Determine privileges now, but don't apply until we've found a DAC * entry that matches but has failed to allow access. * * XXXRW: Ideally, we'd determine the privileges required before * asking for them. */ priv_granted = 0; if (type == VDIR) { if ((accmode & VEXEC) && !priv_check_cred(cred, PRIV_VFS_LOOKUP, 0)) priv_granted |= VEXEC; } else { /* * Ensure that at least one execute bit is on. Otherwise, * a privileged user will always succeed, and we don't want * this to happen unless the file really is executable. */ if ((accmode & VEXEC) && (acl_posix1e_acl_to_mode(acl) & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0 && !priv_check_cred(cred, PRIV_VFS_EXEC, 0)) priv_granted |= VEXEC; } if ((accmode & VREAD) && !priv_check_cred(cred, PRIV_VFS_READ, 0)) priv_granted |= VREAD; if (((accmode & VWRITE) || (accmode & VAPPEND)) && !priv_check_cred(cred, PRIV_VFS_WRITE, 0)) priv_granted |= (VWRITE | VAPPEND); if ((accmode & VADMIN) && !priv_check_cred(cred, PRIV_VFS_ADMIN, 0)) priv_granted |= VADMIN; /* * 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++) { switch (acl->acl_entry[i].ae_tag) { case ACL_USER_OBJ: if (file_uid != cred->cr_uid) break; dac_granted = 0; dac_granted |= VADMIN; if (acl->acl_entry[i].ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (acl->acl_entry[i].ae_perm & ACL_READ) dac_granted |= VREAD; if (acl->acl_entry[i].ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); if ((accmode & dac_granted) == accmode) return (0); /* * XXXRW: Do privilege lookup here. */ if ((accmode & (dac_granted | priv_granted)) == accmode) { if (privused != NULL) *privused = 1; return (0); } goto error; case ACL_MASK: acl_mask = &acl->acl_entry[i]; break; case ACL_OTHER: acl_other = &acl->acl_entry[i]; 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("vaccess_acl_posix1e: ACL_OTHER missing\n"); 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++) { switch (acl->acl_entry[i].ae_tag) { case ACL_USER: if (acl->acl_entry[i].ae_id != cred->cr_uid) break; dac_granted = 0; if (acl->acl_entry[i].ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (acl->acl_entry[i].ae_perm & ACL_READ) dac_granted |= VREAD; if (acl->acl_entry[i].ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); dac_granted &= acl_mask_granted; if ((accmode & dac_granted) == accmode) return (0); /* * XXXRW: Do privilege lookup here. */ if ((accmode & (dac_granted | priv_granted)) != accmode) goto error; if (privused != NULL) *privused = 1; return (0); } } /* * 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++) { switch (acl->acl_entry[i].ae_tag) { case ACL_GROUP_OBJ: if (!groupmember(file_gid, cred)) break; dac_granted = 0; if (acl->acl_entry[i].ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (acl->acl_entry[i].ae_perm & ACL_READ) dac_granted |= VREAD; if (acl->acl_entry[i].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: if (!groupmember(acl->acl_entry[i].ae_id, cred)) break; dac_granted = 0; if (acl->acl_entry[i].ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (acl->acl_entry[i].ae_perm & ACL_READ) dac_granted |= VREAD; if (acl->acl_entry[i].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++) { switch (acl->acl_entry[i].ae_tag) { case ACL_GROUP_OBJ: if (!groupmember(file_gid, cred)) break; dac_granted = 0; if (acl->acl_entry[i].ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (acl->acl_entry[i].ae_perm & ACL_READ) dac_granted |= VREAD; if (acl->acl_entry[i].ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); dac_granted &= acl_mask_granted; /* * XXXRW: Do privilege lookup here. */ if ((accmode & (dac_granted | priv_granted)) != accmode) break; if (privused != NULL) *privused = 1; return (0); case ACL_GROUP: if (!groupmember(acl->acl_entry[i].ae_id, cred)) break; dac_granted = 0; if (acl->acl_entry[i].ae_perm & ACL_EXECUTE) dac_granted |= VEXEC; if (acl->acl_entry[i].ae_perm & ACL_READ) dac_granted |= VREAD; if (acl->acl_entry[i].ae_perm & ACL_WRITE) dac_granted |= (VWRITE | VAPPEND); dac_granted &= acl_mask_granted; /* * XXXRW: Do privilege lookup here. */ if ((accmode & (dac_granted | priv_granted)) != accmode) break; if (privused != NULL) *privused = 1; return (0); default: break; } } /* * Even with privilege, group membership was not sufficient. * Return failure. */ goto error; } /* * 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); if ((accmode & dac_granted) == accmode) return (0); /* * XXXRW: Do privilege lookup here. */ if ((accmode & (dac_granted | priv_granted)) == accmode) { if (privused != NULL) *privused = 1; return (0); } error: return ((accmode & VADMIN) ? EPERM : EACCES); }
/* * Perform chown operation on inode ip; * inode must be locked prior to call. */ int ufs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred, struct proc *p) { struct inode *ip = VTOI(vp); uid_t ouid; gid_t ogid; int error = 0; daddr64_t change; enum ufs_quota_flags quota_flags = 0; if (uid == (uid_t)VNOVAL) uid = DIP(ip, uid); if (gid == (gid_t)VNOVAL) gid = DIP(ip, gid); /* * If we don't own the file, are trying to change the owner * of the file, or are not a member of the target group, * the caller must be superuser or the call fails. */ if ((cred->cr_uid != DIP(ip, uid) || uid != DIP(ip, uid) || (gid != DIP(ip, gid) && !groupmember((gid_t)gid, cred))) && (error = suser_ucred(cred))) return (error); ogid = DIP(ip, gid); ouid = DIP(ip, uid); change = DIP(ip, blocks); if (ouid == uid) quota_flags |= UFS_QUOTA_NOUID; if (ogid == gid) quota_flags |= UFS_QUOTA_NOGID; if ((error = getinoquota(ip)) != 0) return (error); (void) ufs_quota_free_blocks2(ip, change, cred, quota_flags); (void) ufs_quota_free_inode2(ip, cred, quota_flags); (void) ufs_quota_delete(ip); DIP_ASSIGN(ip, gid, gid); DIP_ASSIGN(ip, uid, uid); if ((error = getinoquota(ip)) != 0) goto error; if ((error = ufs_quota_alloc_blocks2(ip, change, cred, quota_flags)) != 0) goto error; if ((error = ufs_quota_alloc_inode2(ip, cred , quota_flags)) != 0) { (void)ufs_quota_free_blocks2(ip, change, cred, quota_flags); goto error; } if (getinoquota(ip)) panic("chown: lost quota"); if (ouid != uid || ogid != gid) ip->i_flag |= IN_CHANGE; if (ouid != uid && cred->cr_uid != 0) DIP_AND(ip, mode, ~ISUID); if (ogid != gid && cred->cr_uid != 0) DIP_AND(ip, mode, ~ISGID); return (0); error: (void) ufs_quota_delete(ip); DIP_ASSIGN(ip, gid, ogid); DIP_ASSIGN(ip, uid, ouid); if (getinoquota(ip) == 0) { (void) ufs_quota_alloc_blocks2(ip, change, cred, quota_flags | UFS_QUOTA_FORCE); (void) ufs_quota_alloc_inode2(ip, cred, quota_flags | UFS_QUOTA_FORCE); (void) getinoquota(ip); } return (error); }