Introduce support for POSIX.1e ACLs on UFS-based file systems. This
implementation is still experimental, and while fairly broadly tested, is not yet intended for production use. Support for POSIX.1e ACLs on UFS will not be MFC'd to RELENG_4. This implementation works by providing implementations of VOP_[GS]ETACL() for FFS, as well as modifying the appropriate access control and file creation routines. In this implementation, ACLs are backed into extended attributes; the base ACL (owner, group, other) permissions remain in the inode for performance and compatibility reasons, so only the extended and default ACLs are placed in extended attributes. The logic for ACL evaluation is provided by the fs-independent kern/kern_acl.c. o Introduce UFS_ACL, a compile-time configuration option that enables support for ACLs on FFS (and potentially other UFS-based file systems). o Introduce ufs_getacl(), ufs_setacl(), ufs_aclcheck(), which respectively get, set, and check the ACLs on the passed vnode. o Introduce ufs_sync_acl_from_inode(), ufs_sync_inode_from_acl() to maintain access control information between inode permissions and extended attribute data. o Modify ufs_access() to load a file access ACL and invoke vaccess_acl_posix1e() if ACLs are available on the file system o Modify ufs_mkdir() and ufs_makeinode() to associate ACLs with newly created directories and files, inheriting from the parent directory's default ACL. o Enable these new vnode operations and conditionally compiled code paths if UFS_ACL is defined. A few notes: o This implementation is fairly widely tested, but still should be considered experimental. o Currently, ACLs are not exported via NFS, instead, the summarizing file mode/etc from the inode is. This results in conservative protection behavior, similar to the behavior of ACL-nonaware programs acting locally. o It is possible that underlying binary data formats associated with this implementation may change. Consumers of the implementation should expect to find their local configuration obsoleted in the next few months, resulting in possible loss of ACL data during an upgrade. o The extended attributes interface and implementation is still undergoing modification to address portable interface concerns, as well as performance. o Many applications do not yet correctly handle ACLs. In general, due to the POSIX.1e ACL model, behavior of ACL-unaware applications will be conservative with respects to file protection; some caution is recommended. o Instructions for configuring and maintaining ACLs on UFS will be committed in the near future; in the mean time it is possible to reference the README included in the last UFS ACL distribution placed in the TrustedBSD web site: http://www.TrustedBSD.org/downloads/ Substantial debugging, hardware, travel, or connectivity support for this project was provided by: BSDi, Safeport Network Services, and NAI Labs. Significant coding contributions were made by Chris Faulhaber. Additional support was provided by Brian Feldman, Thomas Moestl, and Ilmar Habibulin. Reviewed by: jedgar, keichii, mckusick, trustedbsd-discuss, freebsd-fs Obtained from: TrustedBSD Project
This commit is contained in:
parent
57c6e666cc
commit
a70f27470f
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=74822
@ -1187,6 +1187,7 @@ ufs/ffs/ffs_vnops.c optional ifs
|
||||
ufs/ffs/ffs_vnops.c optional mfs
|
||||
ufs/mfs/mfs_vfsops.c optional mfs
|
||||
ufs/mfs/mfs_vnops.c optional mfs
|
||||
ufs/ufs/ufs_acl.c standard
|
||||
ufs/ufs/ufs_bmap.c standard
|
||||
ufs/ufs/ufs_extattr.c standard
|
||||
ufs/ufs/ufs_ihash.c standard
|
||||
|
@ -143,6 +143,11 @@ NFS
|
||||
# otherwise a STUB module will be compiled in.
|
||||
SOFTUPDATES opt_ffs.h
|
||||
|
||||
# Enabling this option turns on support for Access Control Lists in UFS,
|
||||
# which can be used to support high security configurations. Depends on
|
||||
# UFS_EXTATTR.
|
||||
UFS_ACL opt_ufs.h
|
||||
|
||||
# Enabling this option turns on support for extended attributes in UFS-based
|
||||
# file systems, which can be used to support high security configurations
|
||||
# as well as new file system features.
|
||||
|
47
sys/ufs/ufs/acl.h
Normal file
47
sys/ufs/ufs/acl.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*-
|
||||
* Copyright (c) 1999, 2000, 2001 Robert N. M. Watson
|
||||
* 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 AUTHOR 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 AUTHOR 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
/*
|
||||
* TrustedBSD Project - ACL support for the UFS file system.
|
||||
*/
|
||||
|
||||
#ifndef _UFS_UFS_ACL_H_
|
||||
#define _UFS_UFS_ACL_H_
|
||||
|
||||
#ifdef _KERNEL
|
||||
|
||||
void ufs_sync_acl_from_inode(struct inode *ip, struct acl *acl);
|
||||
void ufs_sync_inode_from_acl(struct acl *acl, struct inode *ip,
|
||||
mode_t preserve_mask);
|
||||
|
||||
int ufs_getacl __P((struct vop_getacl_args *));
|
||||
int ufs_setacl __P((struct vop_setacl_args *));
|
||||
int ufs_aclcheck __P((struct vop_aclcheck_args *));
|
||||
|
||||
#endif /* !_KERNEL */
|
||||
|
||||
#endif /* !_UFS_UFS_ACL_H */
|
559
sys/ufs/ufs/ufs_acl.c
Normal file
559
sys/ufs/ufs/ufs_acl.c
Normal file
@ -0,0 +1,559 @@
|
||||
/*-
|
||||
* Copyright (c) 1999, 2000, 2001 Robert N. M. Watson
|
||||
* 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 AUTHOR 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 AUTHOR 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
/*
|
||||
* Developed by the TrustedBSD Project.
|
||||
* Support for POSIX.1e access control lists: UFS-specific support functions.
|
||||
*/
|
||||
|
||||
#include "opt_ufs.h"
|
||||
#include "opt_quota.h"
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/vnode.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/acl.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/extattr.h>
|
||||
|
||||
#include <ufs/ufs/quota.h>
|
||||
#include <ufs/ufs/inode.h>
|
||||
#include <ufs/ufs/acl.h>
|
||||
#include <ufs/ufs/extattr.h>
|
||||
#include <ufs/ufs/dir.h>
|
||||
#include <ufs/ufs/ufsmount.h>
|
||||
#include <ufs/ufs/ufs_extern.h>
|
||||
|
||||
#define VN_KNOTE(vp, b) \
|
||||
KNOTE(&vp->v_pollinfo.vpi_selinfo.si_note, (b))
|
||||
|
||||
#ifdef UFS_ACL
|
||||
|
||||
/*
|
||||
* Synchronize an ACL and an inode by copying over appropriate inode fields
|
||||
* to the passed ACL. Assumes an ACL that would satisfy acl_posix1e_check(),
|
||||
* and may panic if not.
|
||||
*/
|
||||
void
|
||||
ufs_sync_acl_from_inode(struct inode *ip, struct acl *acl)
|
||||
{
|
||||
struct acl_entry *acl_mask, *acl_group_obj;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Update ACL_USER_OBJ, ACL_OTHER, but simply identify ACL_MASK
|
||||
* and ACL_GROUP_OBJ for use after we know whether ACL_MASK is
|
||||
* present.
|
||||
*/
|
||||
acl_mask = NULL;
|
||||
acl_group_obj = NULL;
|
||||
for (i = 0; i < acl->acl_cnt; i++) {
|
||||
switch (acl->acl_entry[i].ae_tag) {
|
||||
case ACL_USER_OBJ:
|
||||
acl->acl_entry[i].ae_perm = acl_posix1e_mode_to_perm(
|
||||
ACL_USER_OBJ, ip->i_mode);
|
||||
acl->acl_entry[i].ae_id = ip->i_uid;
|
||||
break;
|
||||
|
||||
case ACL_GROUP_OBJ:
|
||||
acl_group_obj = &acl->acl_entry[i];
|
||||
acl->acl_entry[i].ae_id = ip->i_gid;
|
||||
break;
|
||||
|
||||
case ACL_OTHER:
|
||||
acl->acl_entry[i].ae_perm = acl_posix1e_mode_to_perm(
|
||||
ACL_OTHER, ip->i_mode);
|
||||
acl->acl_entry[i].ae_id = 0;
|
||||
break;
|
||||
|
||||
case ACL_MASK:
|
||||
acl_mask = &acl->acl_entry[i];
|
||||
acl->acl_entry[i].ae_id = 0;
|
||||
break;
|
||||
|
||||
case ACL_USER:
|
||||
case ACL_GROUP:
|
||||
break;
|
||||
|
||||
default:
|
||||
panic("ufs_sync_acl_from_inode(): bad ae_tag");
|
||||
}
|
||||
}
|
||||
|
||||
if (acl_group_obj == NULL)
|
||||
panic("ufs_sync_acl_from_inode(): no ACL_GROUP_OBJ");
|
||||
|
||||
if (acl_mask == NULL) {
|
||||
/*
|
||||
* There is no ACL_MASK, so update ACL_GROUP_OBJ.
|
||||
*/
|
||||
acl_group_obj->ae_perm = acl_posix1e_mode_to_perm(
|
||||
ACL_GROUP_OBJ, ip->i_mode);
|
||||
} else {
|
||||
/*
|
||||
* Update the ACL_MASK entry instead of ACL_GROUP_OBJ.
|
||||
*/
|
||||
acl_mask->ae_perm = acl_posix1e_mode_to_perm(ACL_GROUP_OBJ,
|
||||
ip->i_mode);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Synchronize an inode and an ACL by copying over appropriate ACL fields to
|
||||
* the passed inode. Assumes an ACL that would satisfy acl_posix1e_check(),
|
||||
* and may panic if not. This code will preserve existing use of the
|
||||
* sticky, setugid, and non-permission bits in the mode field. It may
|
||||
* be that the caller wishes to have previously authorized these changes,
|
||||
* and may also want to clear the setugid bits in some situations.
|
||||
*/
|
||||
void
|
||||
ufs_sync_inode_from_acl(struct acl *acl, struct inode *ip,
|
||||
mode_t preserve_mask)
|
||||
{
|
||||
struct acl_entry *acl_mask, *acl_user_obj, *acl_group_obj;
|
||||
struct acl_entry *acl_other;
|
||||
mode_t preserve_mode;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Preserve old mode so we can restore appropriate bits of it.
|
||||
*/
|
||||
preserve_mode = (ip->i_mode & preserve_mask);
|
||||
|
||||
/*
|
||||
* Identify the ACL_MASK and all other entries appearing in the
|
||||
* inode mode.
|
||||
*/
|
||||
acl_user_obj = NULL;
|
||||
acl_group_obj = NULL;
|
||||
acl_other = NULL;
|
||||
acl_mask = NULL;
|
||||
for (i = 0; i < acl->acl_cnt; i++) {
|
||||
switch (acl->acl_entry[i].ae_tag) {
|
||||
case ACL_USER_OBJ:
|
||||
acl_user_obj = &acl->acl_entry[i];
|
||||
ip->i_uid = acl->acl_entry[i].ae_id;
|
||||
break;
|
||||
|
||||
case ACL_GROUP_OBJ:
|
||||
acl_group_obj = &acl->acl_entry[i];
|
||||
ip->i_gid = acl->acl_entry[i].ae_id;
|
||||
break;
|
||||
|
||||
case ACL_OTHER:
|
||||
acl_other = &acl->acl_entry[i];
|
||||
break;
|
||||
|
||||
case ACL_MASK:
|
||||
acl_mask = &acl->acl_entry[i];
|
||||
break;
|
||||
|
||||
case ACL_USER:
|
||||
case ACL_GROUP:
|
||||
break;
|
||||
|
||||
default:
|
||||
panic("ufs_sync_inode_from_acl(): bad ae_tag");
|
||||
}
|
||||
}
|
||||
|
||||
if (acl_user_obj == NULL || acl_group_obj == NULL || acl_other == NULL)
|
||||
panic("ufs_sync_inode_from_acl(): missing ae_tags");
|
||||
|
||||
if (acl_mask == NULL) {
|
||||
/*
|
||||
* There is no ACL_MASK, so use the ACL_GROUP_OBJ entry.
|
||||
*/
|
||||
ip->i_mode &= ~ALLPERMS;
|
||||
ip->i_mode |= acl_posix1e_perms_to_mode(acl_user_obj,
|
||||
acl_group_obj, acl_other);
|
||||
} else {
|
||||
/*
|
||||
* Use the ACL_MASK entry.
|
||||
*/
|
||||
ip->i_mode &= ~ALLPERMS;
|
||||
ip->i_mode |= acl_posix1e_perms_to_mode(acl_user_obj,
|
||||
acl_mask, acl_other);
|
||||
}
|
||||
ip->i_mode |= preserve_mode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the ACL on a file.
|
||||
*
|
||||
* As part of the ACL is stored in the inode, and the rest in an EA,
|
||||
* assemble both into a final ACL product. Right now this is not done
|
||||
* very efficiently.
|
||||
*/
|
||||
int
|
||||
ufs_getacl(ap)
|
||||
struct vop_getacl_args /* {
|
||||
struct vnode *vp;
|
||||
struct acl_type_t type;
|
||||
struct acl *aclp;
|
||||
struct ucred *cred;
|
||||
struct proc *p;
|
||||
} */ *ap;
|
||||
{
|
||||
struct inode *ip = VTOI(ap->a_vp);
|
||||
int error, len;
|
||||
|
||||
|
||||
/*
|
||||
* Attempt to retrieve the ACL based on the ACL type.
|
||||
*/
|
||||
bzero(ap->a_aclp, sizeof(*ap->a_aclp));
|
||||
switch(ap->a_type) {
|
||||
case ACL_TYPE_ACCESS:
|
||||
/*
|
||||
* ACL_TYPE_ACCESS ACLs may or may not be stored in the
|
||||
* EA, as they are in fact a combination of the inode
|
||||
* ownership/permissions and the EA contents. If the
|
||||
* EA is present, merge the two in a temporary ACL
|
||||
* storage, otherwise just return the inode contents.
|
||||
*/
|
||||
len = sizeof(*ap->a_aclp);
|
||||
error = vn_extattr_get(ap->a_vp, IO_NODELOCKED,
|
||||
POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE,
|
||||
POSIX1E_ACL_ACCESS_EXTATTR_NAME, &len, (char *) ap->a_aclp,
|
||||
ap->a_p);
|
||||
switch (error) {
|
||||
/* XXX: Will be ENOATTR. */
|
||||
/* XXX: If ufs_getacl() should work on file systems without
|
||||
* the EA configured, add case EOPNOTSUPP here. */
|
||||
case ENOENT:
|
||||
/*
|
||||
* Legitimately no ACL set on object, purely
|
||||
* emulate it through the inode. These fields will
|
||||
* be updated when the ACL is synchronized with
|
||||
* the inode later.
|
||||
*/
|
||||
ap->a_aclp->acl_cnt = 3;
|
||||
ap->a_aclp->acl_entry[0].ae_tag = ACL_USER_OBJ;
|
||||
ap->a_aclp->acl_entry[0].ae_id = 0;
|
||||
ap->a_aclp->acl_entry[0].ae_perm = 0;
|
||||
ap->a_aclp->acl_entry[1].ae_tag = ACL_GROUP_OBJ;
|
||||
ap->a_aclp->acl_entry[1].ae_id = 0;
|
||||
ap->a_aclp->acl_entry[1].ae_perm = 0;
|
||||
ap->a_aclp->acl_entry[2].ae_tag = ACL_OTHER;
|
||||
ap->a_aclp->acl_entry[2].ae_id = 0;
|
||||
ap->a_aclp->acl_entry[2].ae_perm = 0;
|
||||
ufs_sync_acl_from_inode(ip, ap->a_aclp);
|
||||
error = 0;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
if (len != sizeof(*ap->a_aclp)) {
|
||||
/*
|
||||
* A short (or long) read, meaning that for
|
||||
* some reason the ACL is corrupted. Return
|
||||
* EPERM since the object DAC protections
|
||||
* are unsafe.
|
||||
*/
|
||||
printf("ufs_getacl(): Loaded invalid ACL ("
|
||||
"%d bytes)\n", len);
|
||||
return (EPERM);
|
||||
}
|
||||
ufs_sync_acl_from_inode(ip, ap->a_aclp);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
break;
|
||||
|
||||
case ACL_TYPE_DEFAULT:
|
||||
if (ap->a_vp->v_type != VDIR) {
|
||||
error = EINVAL;
|
||||
break;
|
||||
}
|
||||
bzero(ap->a_aclp, sizeof(*ap->a_aclp));
|
||||
error = vn_extattr_get(ap->a_vp, IO_NODELOCKED,
|
||||
POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE,
|
||||
POSIX1E_ACL_DEFAULT_EXTATTR_NAME, &len,
|
||||
(char *) ap->a_aclp, ap->a_p);
|
||||
/*
|
||||
* Unlike ACL_TYPE_ACCESS, there is no relationship between
|
||||
* the inode contents and the ACL, and it is therefore
|
||||
* possible for the request for the ACL to fail since the
|
||||
* ACL is undefined. In this situation, return success
|
||||
* and an empty ACL, as required by POSIX.1e.
|
||||
*/
|
||||
switch (error) {
|
||||
/* XXX: Will be ENOATTR. */
|
||||
/* XXX: If ufs_getacl() should work on file systems without
|
||||
* the EA configured, add case EOPNOTSUPP here. */
|
||||
case ENOENT:
|
||||
bzero(ap->a_aclp, sizeof(*ap->a_aclp));
|
||||
ap->a_aclp->acl_cnt = 0;
|
||||
error = 0;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error = EINVAL;
|
||||
}
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the ACL on a file.
|
||||
*
|
||||
* As part of the ACL is stored in the inode, and the rest in an EA,
|
||||
* this is necessarily non-atomic, and has complex authorization.
|
||||
* As ufs_setacl() includes elements of ufs_chown() and ufs_chmod(),
|
||||
* a fair number of different access checks may be required to go ahead
|
||||
* with the operation at all.
|
||||
*/
|
||||
int
|
||||
ufs_setacl(ap)
|
||||
struct vop_setacl_args /* {
|
||||
struct vnode *vp;
|
||||
acl_type_t type;
|
||||
struct acl *aclp;
|
||||
struct ucred *cred;
|
||||
struct proc *p;
|
||||
} */ *ap;
|
||||
{
|
||||
struct inode *ip = VTOI(ap->a_vp);
|
||||
struct acl_entry *acl_user_obj, *acl_group_obj, *acl_other;
|
||||
mode_t old_mode, preserve_mask;
|
||||
uid_t old_uid, new_uid = 0;
|
||||
gid_t old_gid, new_gid = 0;
|
||||
int error, i;
|
||||
|
||||
/*
|
||||
* If this is a set operation rather than a delete operation,
|
||||
* invoke VOP_ACLCHECK() on the passed ACL to determine if it is
|
||||
* valid for the target. This will include a check on ap->a_type.
|
||||
*/
|
||||
if (ap->a_aclp != NULL) {
|
||||
/*
|
||||
* Set operation.
|
||||
*/
|
||||
error = VOP_ACLCHECK(ap->a_vp, ap->a_type, ap->a_aclp,
|
||||
ap->a_cred, ap->a_p);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
} else {
|
||||
/*
|
||||
* Delete operation.
|
||||
* POSIX.1e allows only deletion of the default ACL on a
|
||||
* directory (ACL_TYPE_DEFAULT).
|
||||
*/
|
||||
if (ap->a_type != ACL_TYPE_DEFAULT)
|
||||
return (EINVAL);
|
||||
if (ap->a_vp->v_type != VDIR)
|
||||
return (ENOTDIR);
|
||||
}
|
||||
|
||||
if (ap->a_vp->v_mount->mnt_flag & MNT_RDONLY)
|
||||
return (EROFS);
|
||||
|
||||
/*
|
||||
* Authorize the ACL operation.
|
||||
*/
|
||||
if (ip->i_flags & (IMMUTABLE | APPEND))
|
||||
return (EPERM);
|
||||
|
||||
/*
|
||||
* Must hold VADMIN (be file owner) or have appropriate privilege.
|
||||
*/
|
||||
if ((error = VOP_ACCESS(ap->a_vp, VADMIN, ap->a_cred, ap->a_p)))
|
||||
return (error);
|
||||
|
||||
/*
|
||||
* ACL_TYPE_ACCESS may involve the changing of ownership, sticky
|
||||
* bit, setugid bits on the file or directory. As such, it requires
|
||||
* special handling to identify these changes, and to authorize
|
||||
* them.
|
||||
* ACL_TYPE_DEFAULT does not require this, and ap->a_aclp should
|
||||
* not be dereferenced without a NULL check, as it may be a delete
|
||||
* operation.
|
||||
*/
|
||||
switch(ap->a_type) {
|
||||
case ACL_TYPE_ACCESS:
|
||||
/*
|
||||
* Identify ACL_USER_OBJ, ACL_GROUP_OBJ, and determine if
|
||||
* they have changed. If so, authorize in the style of
|
||||
* ufs_chown(). While we're at it, identify ACL_OTHER.
|
||||
*/
|
||||
acl_user_obj = acl_group_obj = acl_other = NULL;
|
||||
for (i = 0; i < ap->a_aclp->acl_cnt; i++)
|
||||
switch(ap->a_aclp->acl_entry[i].ae_tag) {
|
||||
case ACL_USER_OBJ:
|
||||
acl_user_obj = &ap->a_aclp->acl_entry[i];
|
||||
new_uid = acl_user_obj->ae_id;
|
||||
break;
|
||||
case ACL_GROUP_OBJ:
|
||||
acl_group_obj = &ap->a_aclp->acl_entry[i];
|
||||
new_gid = acl_group_obj->ae_id;
|
||||
break;
|
||||
case ACL_OTHER:
|
||||
acl_other = &ap->a_aclp->acl_entry[i];
|
||||
break;
|
||||
default:
|
||||
}
|
||||
old_uid = ip->i_uid;
|
||||
old_gid = ip->i_gid;
|
||||
|
||||
/*
|
||||
* Authorize changes to base object ownership in the style
|
||||
* of ufs_chown().
|
||||
*/
|
||||
if (new_uid != old_uid && (error = suser_xxx(ap->a_cred,
|
||||
ap->a_p, PRISON_ROOT)))
|
||||
return (error);
|
||||
if (new_gid != old_gid && !groupmember(new_gid, ap->a_cred) &&
|
||||
(error = suser_xxx(ap->a_cred, ap->a_p, PRISON_ROOT)))
|
||||
return (error);
|
||||
|
||||
case ACL_TYPE_DEFAULT:
|
||||
/*
|
||||
* ACL_TYPE_DEFAULT can literally be written straight into
|
||||
* the EA unhindered, as it has gone through sanity checking
|
||||
* already.
|
||||
*/
|
||||
break;
|
||||
|
||||
default:
|
||||
panic("ufs_setacl(): unknown acl type\n");
|
||||
}
|
||||
|
||||
switch(ap->a_type) {
|
||||
case ACL_TYPE_ACCESS:
|
||||
error = vn_extattr_set(ap->a_vp, IO_NODELOCKED,
|
||||
POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE,
|
||||
POSIX1E_ACL_ACCESS_EXTATTR_NAME, sizeof(*ap->a_aclp),
|
||||
(char *) ap->a_aclp, ap->a_p);
|
||||
break;
|
||||
|
||||
case ACL_TYPE_DEFAULT:
|
||||
if (ap->a_aclp == NULL) {
|
||||
error = vn_extattr_rm(ap->a_vp, IO_NODELOCKED,
|
||||
POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE,
|
||||
POSIX1E_ACL_DEFAULT_EXTATTR_NAME, ap->a_p);
|
||||
/*
|
||||
* Attempting to delete a non-present default ACL
|
||||
* will return success for portability purposes.
|
||||
* (TRIX)
|
||||
*/
|
||||
/* XXX: the ENOENT here will eventually be ENOATTR. */
|
||||
if (error == EINVAL)
|
||||
error = 0;
|
||||
} else
|
||||
error = vn_extattr_set(ap->a_vp, IO_NODELOCKED,
|
||||
POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE,
|
||||
POSIX1E_ACL_DEFAULT_EXTATTR_NAME,
|
||||
sizeof(*ap->a_aclp), (char *) ap->a_aclp, ap->a_p);
|
||||
break;
|
||||
|
||||
default:
|
||||
error = EINVAL;
|
||||
}
|
||||
/*
|
||||
* Map lack of attribute definition in UFS_EXTATTR into lack of
|
||||
* support for ACLs on the file system.
|
||||
*/
|
||||
/* XXX: ENOENT here will eventually be ENOATTR. */
|
||||
if (error == ENOENT)
|
||||
return (EOPNOTSUPP);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
||||
if (ap->a_type == ACL_TYPE_ACCESS) {
|
||||
/*
|
||||
* Now that the EA is successfully updated, update the
|
||||
* inode and mark it as changed.
|
||||
*/
|
||||
old_uid = ip->i_uid;
|
||||
old_gid = ip->i_gid;
|
||||
old_mode = ip->i_mode;
|
||||
preserve_mask = ISVTX | ISGID | ISUID;
|
||||
ufs_sync_inode_from_acl(ap->a_aclp, ip, preserve_mask);
|
||||
|
||||
/*
|
||||
* Clear the ISGID and ISUID bits if the ownership has
|
||||
* changed, or appropriate privilege is not available.
|
||||
* XXX: This should probably be a check for broadening
|
||||
* availability of the bits, but it's not clear from the
|
||||
* spec.
|
||||
*/
|
||||
if (suser_xxx(ap->a_cred, NULL, PRISON_ROOT) &&
|
||||
(ip->i_gid != old_gid || ip->i_uid != old_uid))
|
||||
ip->i_mode &= ~(ISUID | ISGID);
|
||||
ip->i_flag |= IN_CHANGE;
|
||||
}
|
||||
|
||||
VN_KNOTE(ap->a_vp, NOTE_ATTRIB);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the validity of an ACL for a file.
|
||||
*/
|
||||
int
|
||||
ufs_aclcheck(ap)
|
||||
struct vop_aclcheck_args /* {
|
||||
struct vnode *vp;
|
||||
acl_type_t type;
|
||||
struct acl *aclp;
|
||||
struct ucred *cred;
|
||||
struct proc *p;
|
||||
} */ *ap;
|
||||
{
|
||||
|
||||
/*
|
||||
* Verify we understand this type of ACL, and that it applies
|
||||
* to this kind of object.
|
||||
* Rely on the acl_posix1e_check() routine to verify the contents.
|
||||
*/
|
||||
switch(ap->a_type) {
|
||||
case ACL_TYPE_ACCESS:
|
||||
break;
|
||||
|
||||
case ACL_TYPE_DEFAULT:
|
||||
if (ap->a_vp->v_type != VDIR)
|
||||
return (EINVAL);
|
||||
break;
|
||||
|
||||
default:
|
||||
return (EINVAL);
|
||||
}
|
||||
return (acl_posix1e_check(ap->a_aclp));
|
||||
}
|
||||
|
||||
#endif /* !UFS_ACL */
|
@ -41,6 +41,7 @@
|
||||
|
||||
#include "opt_quota.h"
|
||||
#include "opt_suiddir.h"
|
||||
#include "opt_ufs.h"
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
@ -52,12 +53,13 @@
|
||||
#include <sys/buf.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/vnode.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/vnode.h>
|
||||
#include <sys/dirent.h>
|
||||
#include <sys/lockf.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/conf.h>
|
||||
#include <sys/acl.h>
|
||||
|
||||
#include <machine/mutex.h>
|
||||
|
||||
@ -68,6 +70,7 @@
|
||||
|
||||
#include <miscfs/fifofs/fifo.h>
|
||||
|
||||
#include <ufs/ufs/acl.h>
|
||||
#include <ufs/ufs/extattr.h>
|
||||
#include <ufs/ufs/quota.h>
|
||||
#include <ufs/ufs/inode.h>
|
||||
@ -308,8 +311,10 @@ ufs_access(ap)
|
||||
struct vnode *vp = ap->a_vp;
|
||||
struct inode *ip = VTOI(vp);
|
||||
mode_t mode = ap->a_mode;
|
||||
#ifdef QUOTA
|
||||
int error;
|
||||
#ifdef UFS_ACL
|
||||
struct acl *acl;
|
||||
int len;
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -338,8 +343,35 @@ ufs_access(ap)
|
||||
if ((mode & VWRITE) && (ip->i_flags & (IMMUTABLE | SF_SNAPSHOT)))
|
||||
return (EPERM);
|
||||
|
||||
return (vaccess(vp->v_type, ip->i_mode, ip->i_uid, ip->i_gid,
|
||||
ap->a_mode, ap->a_cred, NULL));
|
||||
#ifdef UFS_ACL
|
||||
MALLOC(acl, struct acl *, sizeof(*acl), M_ACL, M_WAITOK);
|
||||
len = sizeof(*acl);
|
||||
error = VOP_GETACL(vp, ACL_TYPE_ACCESS, acl, ap->a_cred, ap->a_p);
|
||||
switch (error) {
|
||||
case EOPNOTSUPP:
|
||||
error = vaccess(vp->v_type, ip->i_mode, ip->i_uid, ip->i_gid,
|
||||
ap->a_mode, ap->a_cred, NULL);
|
||||
break;
|
||||
case 0:
|
||||
error = vaccess_acl_posix1e(vp->v_type, acl, ap->a_mode,
|
||||
ap->a_cred, NULL);
|
||||
break;
|
||||
default:
|
||||
printf("ufs_access(): Error retrieving ACL on object (%d).\n",
|
||||
error);
|
||||
/*
|
||||
* XXX: Fall back until debugged. Should eventually
|
||||
* possibly log an error, and return EPERM for safety.
|
||||
*/
|
||||
error = vaccess(vp->v_type, ip->i_mode, ip->i_uid, ip->i_gid,
|
||||
ap->a_mode, ap->a_cred, NULL);
|
||||
}
|
||||
FREE(acl, M_ACL);
|
||||
#else
|
||||
error = vaccess(vp->v_type, ip->i_mode, ip->i_uid, ip->i_gid,
|
||||
ap->a_mode, ap->a_cred, NULL);
|
||||
#endif
|
||||
return (error);
|
||||
}
|
||||
|
||||
/* ARGSUSED */
|
||||
@ -1279,6 +1311,9 @@ ufs_mkdir(ap)
|
||||
struct buf *bp;
|
||||
struct dirtemplate dirtemplate, *dtp;
|
||||
struct direct newdir;
|
||||
#ifdef UFS_ACL
|
||||
struct acl *acl, *dacl;
|
||||
#endif
|
||||
int error, dmode;
|
||||
long blkoff;
|
||||
|
||||
@ -1360,7 +1395,54 @@ ufs_mkdir(ap)
|
||||
#endif
|
||||
#endif /* !SUIDDIR */
|
||||
ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
|
||||
#ifdef UFS_ACL
|
||||
MALLOC(acl, struct acl *, sizeof(*acl), M_ACL, M_WAITOK);
|
||||
MALLOC(dacl, struct acl *, sizeof(*acl), M_ACL, M_WAITOK);
|
||||
|
||||
/*
|
||||
* Retrieve default ACL from parent, if any.
|
||||
*/
|
||||
error = VOP_GETACL(dvp, ACL_TYPE_DEFAULT, acl, cnp->cn_cred,
|
||||
cnp->cn_proc);
|
||||
switch (error) {
|
||||
case 0:
|
||||
/*
|
||||
* Retrieved a default ACL, so merge mode and ACL if
|
||||
* necessary.
|
||||
*/
|
||||
if (acl->acl_cnt != 0) {
|
||||
/*
|
||||
* Two possible ways for default ACL to not be
|
||||
* present. First, the EA can be undefined,
|
||||
* or second, the default ACL can be blank.
|
||||
* If it's blank, fall through to the it's
|
||||
* not defined case.
|
||||
*/
|
||||
ip->i_mode = dmode;
|
||||
*dacl = *acl;
|
||||
ufs_sync_acl_from_inode(ip, acl);
|
||||
break;
|
||||
}
|
||||
/* FALLTHROUGH */
|
||||
|
||||
case EOPNOTSUPP:
|
||||
/*
|
||||
* Just use the mode as-is.
|
||||
*/
|
||||
ip->i_mode = dmode;
|
||||
FREE(acl, M_ACL);
|
||||
FREE(dacl, M_ACL);
|
||||
dacl = acl = NULL;
|
||||
break;
|
||||
|
||||
default:
|
||||
UFS_VFREE(tvp, ip->i_number, dmode);
|
||||
vput(tvp);
|
||||
return (error);
|
||||
}
|
||||
#else /* !UFS_ACL */
|
||||
ip->i_mode = dmode;
|
||||
#endif /* !UFS_ACL */
|
||||
tvp->v_type = VDIR; /* Rest init'd in getnewvnode(). */
|
||||
ip->i_effnlink = 2;
|
||||
ip->i_nlink = 2;
|
||||
@ -1382,6 +1464,41 @@ ufs_mkdir(ap)
|
||||
error = UFS_UPDATE(tvp, !(DOINGSOFTDEP(dvp) | DOINGASYNC(dvp)));
|
||||
if (error)
|
||||
goto bad;
|
||||
#ifdef UFS_ACL
|
||||
if (acl != NULL) {
|
||||
/*
|
||||
* XXX: If we abort now, will Soft Updates notify the extattr
|
||||
* code that the EAs for the file need to be released?
|
||||
*/
|
||||
error = VOP_SETACL(tvp, ACL_TYPE_ACCESS, acl, cnp->cn_cred,
|
||||
cnp->cn_proc);
|
||||
if (error == 0)
|
||||
error = VOP_SETACL(tvp, ACL_TYPE_DEFAULT, dacl,
|
||||
cnp->cn_cred, cnp->cn_proc);
|
||||
switch (error) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case EOPNOTSUPP:
|
||||
/*
|
||||
* XXX: This should not happen, as EOPNOTSUPP above
|
||||
* was supposed to free acl.
|
||||
*/
|
||||
printf("ufs_mkdir: VOP_GETACL() but no VOP_SETACL()\n");
|
||||
/*
|
||||
panic("ufs_mkdir: VOP_GETACL() but no VOP_SETACL()");
|
||||
*/
|
||||
break;
|
||||
|
||||
default:
|
||||
FREE(acl, M_ACL);
|
||||
FREE(dacl, M_ACL);
|
||||
goto bad;
|
||||
}
|
||||
FREE(acl, M_ACL);
|
||||
FREE(dacl, M_ACL);
|
||||
}
|
||||
#endif /* !UFS_ACL */
|
||||
|
||||
/*
|
||||
* Initialize directory with "." and ".." from static template.
|
||||
@ -2049,6 +2166,7 @@ ufs_vinit(mntp, specops, fifoops, vpp)
|
||||
|
||||
/*
|
||||
* Allocate a new inode.
|
||||
* Vnode dvp must be locked.
|
||||
*/
|
||||
int
|
||||
ufs_makeinode(mode, dvp, vpp, cnp)
|
||||
@ -2060,6 +2178,9 @@ ufs_makeinode(mode, dvp, vpp, cnp)
|
||||
register struct inode *ip, *pdir;
|
||||
struct direct newdir;
|
||||
struct vnode *tvp;
|
||||
#ifdef UFS_ACL
|
||||
struct acl *acl;
|
||||
#endif
|
||||
int error;
|
||||
|
||||
pdir = VTOI(dvp);
|
||||
@ -2132,7 +2253,49 @@ ufs_makeinode(mode, dvp, vpp, cnp)
|
||||
#endif
|
||||
#endif /* !SUIDDIR */
|
||||
ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
|
||||
#ifdef UFS_ACL
|
||||
MALLOC(acl, struct acl *, sizeof(*acl), M_ACL, M_WAITOK);
|
||||
/*
|
||||
* Retrieve default ACL for parent, if any.
|
||||
*/
|
||||
error = VOP_GETACL(dvp, ACL_TYPE_DEFAULT, acl, cnp->cn_cred,
|
||||
cnp->cn_proc);
|
||||
switch (error) {
|
||||
case 0:
|
||||
/*
|
||||
* Retrieved a default ACL, so merge mode and ACL if
|
||||
* necessary.
|
||||
*/
|
||||
if (acl->acl_cnt != 0) {
|
||||
/*
|
||||
* Two possible ways for default ACL to not be
|
||||
* present. First, the EA can be undefined,
|
||||
* or second, the default ACL can be blank.
|
||||
* If it's blank, fall through to the it's
|
||||
* not defined case.
|
||||
*/
|
||||
ip->i_mode = mode;
|
||||
ufs_sync_acl_from_inode(ip, acl);
|
||||
break;
|
||||
}
|
||||
|
||||
case EOPNOTSUPP:
|
||||
/*
|
||||
* Just use the mode as-is.
|
||||
*/
|
||||
ip->i_mode = mode;
|
||||
FREE(acl, M_ACL);
|
||||
acl = NULL;
|
||||
break;
|
||||
|
||||
default:
|
||||
UFS_VFREE(tvp, ip->i_number, mode);
|
||||
vput(tvp);
|
||||
return (error);
|
||||
}
|
||||
#else /* !UFS_ACL */
|
||||
ip->i_mode = mode;
|
||||
#endif /* !UFS_ACL */
|
||||
tvp->v_type = IFTOVT(mode); /* Rest init'd in getnewvnode(). */
|
||||
ip->i_effnlink = 1;
|
||||
ip->i_nlink = 1;
|
||||
@ -2151,6 +2314,36 @@ ufs_makeinode(mode, dvp, vpp, cnp)
|
||||
error = UFS_UPDATE(tvp, !(DOINGSOFTDEP(tvp) | DOINGASYNC(tvp)));
|
||||
if (error)
|
||||
goto bad;
|
||||
#ifdef UFS_ACL
|
||||
if (acl != NULL) {
|
||||
/*
|
||||
* XXX: If we abort now, will Soft Updates notify the extattr
|
||||
* code that the EAs for the file need to be released?
|
||||
*/
|
||||
error = VOP_SETACL(tvp, ACL_TYPE_ACCESS, acl, cnp->cn_cred,
|
||||
cnp->cn_proc);
|
||||
switch (error) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case EOPNOTSUPP:
|
||||
/*
|
||||
* XXX: This should not happen, as EOPNOTSUPP above was
|
||||
* supposed to free acl.
|
||||
*/
|
||||
printf("ufs_makeinode: VOP_GETACL() but no "
|
||||
"VOP_SETACL()\n");
|
||||
/* panic("ufs_makeinode: VOP_GETACL() but no "
|
||||
"VOP_SETACL()"); */
|
||||
break;
|
||||
|
||||
default:
|
||||
FREE(acl, M_ACL);
|
||||
goto bad;
|
||||
}
|
||||
FREE(acl, M_ACL);
|
||||
}
|
||||
#endif /* !UFS_ACL */
|
||||
ufs_makedirentry(ip, cnp, &newdir);
|
||||
error = ufs_direnter(dvp, tvp, &newdir, cnp, NULL);
|
||||
if (error)
|
||||
@ -2299,6 +2492,11 @@ static struct vnodeopv_entry_desc ufs_vnodeop_entries[] = {
|
||||
{ &vop_symlink_desc, (vop_t *) ufs_symlink },
|
||||
{ &vop_unlock_desc, (vop_t *) vop_stdunlock },
|
||||
{ &vop_whiteout_desc, (vop_t *) ufs_whiteout },
|
||||
#ifdef UFS_ACL
|
||||
{ &vop_getacl_desc, (vop_t *) ufs_getacl },
|
||||
{ &vop_setacl_desc, (vop_t *) ufs_setacl },
|
||||
{ &vop_aclcheck_desc, (vop_t *) ufs_aclcheck },
|
||||
#endif
|
||||
{ NULL, NULL }
|
||||
};
|
||||
static struct vnodeopv_desc ufs_vnodeop_opv_desc =
|
||||
@ -2320,7 +2518,12 @@ static struct vnodeopv_entry_desc ufs_specop_entries[] = {
|
||||
{ &vop_setattr_desc, (vop_t *) ufs_setattr },
|
||||
{ &vop_unlock_desc, (vop_t *) vop_stdunlock },
|
||||
{ &vop_write_desc, (vop_t *) ufsspec_write },
|
||||
{ NULL, NULL }
|
||||
#ifdef UFS_ACL
|
||||
{ &vop_getacl_desc, (vop_t *) ufs_getacl },
|
||||
{ &vop_setacl_desc, (vop_t *) ufs_setacl },
|
||||
{ &vop_aclcheck_desc, (vop_t *) ufs_aclcheck },
|
||||
#endif
|
||||
{NULL, NULL}
|
||||
};
|
||||
static struct vnodeopv_desc ufs_specop_opv_desc =
|
||||
{ &ufs_specop_p, ufs_specop_entries };
|
||||
@ -2341,6 +2544,11 @@ static struct vnodeopv_entry_desc ufs_fifoop_entries[] = {
|
||||
{ &vop_setattr_desc, (vop_t *) ufs_setattr },
|
||||
{ &vop_unlock_desc, (vop_t *) vop_stdunlock },
|
||||
{ &vop_write_desc, (vop_t *) ufsfifo_write },
|
||||
#ifdef UFS_ACL
|
||||
{ &vop_getacl_desc, (vop_t *) ufs_getacl },
|
||||
{ &vop_setacl_desc, (vop_t *) ufs_setacl },
|
||||
{ &vop_aclcheck_desc, (vop_t *) ufs_aclcheck },
|
||||
#endif
|
||||
{ NULL, NULL }
|
||||
};
|
||||
static struct vnodeopv_desc ufs_fifoop_opv_desc =
|
||||
|
Loading…
Reference in New Issue
Block a user