zfs_acl.c revision 2bd6c4de2748ab3787a6e1ab244159d738d831e0
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
#include <sys/sysmacros.h>
#include <sys/resource.h>
#include <sys/zfs_znode.h>
#include <sys/zfs_fuid.h>
#include <sys/zfs_vfsops.h>
#include <acl/acl_common.h>
#define ALLOW ACE_ACCESS_ALLOWED_ACE_TYPE
#define DENY ACE_ACCESS_DENIED_ACE_TYPE
#define MIN_ACE_TYPE ALLOW
static uint16_t
zfs_ace_v0_get_type(void *acep)
{
}
static uint16_t
zfs_ace_v0_get_flags(void *acep)
{
}
static uint32_t
zfs_ace_v0_get_mask(void *acep)
{
}
static uint64_t
zfs_ace_v0_get_who(void *acep)
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
/*ARGSUSED*/
static size_t
zfs_ace_v0_size(void *acep)
{
return (sizeof (zfs_oldace_t));
}
static size_t
zfs_ace_v0_abstract_size(void)
{
return (sizeof (zfs_oldace_t));
}
static int
zfs_ace_v0_mask_off(void)
{
}
/*ARGSUSED*/
static int
{
return (0);
}
static acl_ops_t zfs_acl_v0_ops = {
};
static uint16_t
zfs_ace_fuid_get_type(void *acep)
{
}
static uint16_t
zfs_ace_fuid_get_flags(void *acep)
{
}
static uint32_t
zfs_ace_fuid_get_mask(void *acep)
{
}
static uint64_t
zfs_ace_fuid_get_who(void *args)
{
return (-1);
}
static void
{
}
static void
{
}
static void
{
}
static void
{
return;
}
static size_t
zfs_ace_fuid_size(void *acep)
{
return (sizeof (zfs_object_ace_t));
case ALLOW:
case DENY:
if (entry_type == ACE_OWNER ||
entry_type == OWNING_GROUP ||
return (sizeof (zfs_ace_hdr_t));
/*FALLTHROUGH*/
default:
return (sizeof (zfs_ace_t));
}
}
static size_t
{
return (sizeof (zfs_ace_hdr_t));
}
static int
zfs_ace_fuid_mask_off(void)
{
}
static int
{
return (sizeof (zfs_object_ace_t) - sizeof (zfs_ace_t));
default:
return (0);
}
}
static acl_ops_t zfs_acl_fuid_ops = {
};
/*
* The following three functions are provided for compatibility with
* older ZPL version in order to determine if the file use to have
* an external ACL and what version of ACL previously existed on the
* file. Would really be nice to not need this, sigh.
*/
{
int error;
return (0);
/*
* Need to deal with a potential
* race where zfs_sa_upgrade could cause
* z_isa_sa to change.
*
* If the lookup fails then the state of z_is_sa should have
* changed.
*/
return (acl_phys.z_acl_extern_obj);
else {
/*
* after upgrade the SA_ZPL_ZNODE_ACL should have been
* removed
*/
return (0);
}
}
/*
* Determine size of ACL in bytes
*
* This is more complicated than it should be since we have to deal
* with old external ACLs.
*/
static int
{
int size;
int error;
&size)) != 0)
return (error);
return (error);
} else {
return (error);
} else {
}
}
return (0);
}
int
{
return (ZFS_ACL_VERSION_FUID);
else {
int error;
/*
* Need to deal with a potential
* race where zfs_sa_upgrade could cause
* z_isa_sa to change.
*
* If the lookup fails then the state of z_is_sa should have
* changed.
*/
return (acl_phys.z_acl_version);
else {
/*
* After upgrade SA_ZPL_ZNODE_ACL should have
* been removed.
*/
return (ZFS_ACL_VERSION_FUID);
}
}
}
static int
zfs_acl_version(int version)
{
if (version < ZPL_VERSION_FUID)
return (ZFS_ACL_VERSION_INITIAL);
else
return (ZFS_ACL_VERSION_FUID);
}
static int
{
}
zfs_acl_alloc(int vers)
{
if (vers == ZFS_ACL_VERSION_FUID)
else
return (aclp);
}
{
if (bytes) {
}
return (aclnode);
}
static void
{
if (aclnode->z_allocsize)
}
static void
{
}
aclp->z_acl_count = 0;
aclp->z_acl_bytes = 0;
}
void
{
}
static boolean_t
{
switch (type) {
case ALLOW:
case DENY:
return (entry_type == ACE_OWNER ||
entry_type == OWNING_GROUP ||
default:
return (B_TRUE);
}
return (B_FALSE);
}
static boolean_t
{
/*
* first check type of entry
*/
return (B_FALSE);
switch (type) {
return (B_FALSE);
}
/*
* next check inheritance level flags
*/
if ((iflags & (ACE_FILE_INHERIT_ACE|
ACE_DIRECTORY_INHERIT_ACE)) == 0) {
return (B_FALSE);
}
}
return (B_TRUE);
}
static void *
{
return (NULL);
}
return (NULL);
return (NULL);
else {
}
}
/*
* Make sure we don't overstep our bounds
*/
return (NULL);
}
return ((void *)acep);
}
return (NULL);
}
/*ARGSUSED*/
static uint64_t
{
}
static zfs_acl_node_t *
{
return (aclp->z_curr_node);
}
/*
* Copy ACE to internal ZFS format.
* While processing the ACL each ACE will be validated for correctness.
* ACE FUIDs will be created later.
*/
int
{
int i;
for (i = 0; i != aclcnt; i++) {
entry_type != ACE_EVERYONE) {
cr, (entry_type == 0) ?
}
/*
* Make sure ACE is valid
*/
return (EINVAL);
sizeof (aceobjp->a_obj_type));
sizeof (aceobjp->a_inherit_obj_type));
break;
default:
}
}
return (0);
}
/*
* Copy ZFS ACEs to fixed size ace_t layout
*/
static void
{
switch (type) {
if (filter) {
continue;
}
sizeof (zobjacep->z_object_type));
sizeof (zobjacep->z_inherit_type));
ace_size = sizeof (ace_object_t);
break;
default:
break;
}
if ((entry_type != ACE_OWNER &&
entry_type != OWNING_GROUP &&
entry_type != ACE_EVERYONE)) {
} else {
}
}
}
static int
{
int i;
/*
* Make sure ACE is valid
*/
return (EINVAL);
}
return (0);
}
/*
* convert old ACL format to new
*/
void
{
int i;
/*
* First create the ACE in a contiguous piece of memory
* for zfs_copy_ace_2_fuid().
*
* We only convert an ACL once, so this won't happen
* everytime.
*/
KM_SLEEP);
i = 0;
}
sizeof (zfs_object_ace_t));
/*
* Release all previous ACL nodes
*/
}
/*
* Convert unix access mask to v4 access mask
*/
static uint32_t
{
if (access_mask & S_IXOTH)
new_mask |= ACE_EXECUTE;
if (access_mask & S_IWOTH)
if (access_mask & S_IROTH)
return (new_mask);
}
static void
{
type != ACE_EVERYONE))
}
/*
* Determine mode of file based on ACL.
*/
{
int entry_type;
continue;
/*
* Skip over owner@, group@ or everyone@ inherit only ACEs
*/
if ((iflags & ACE_INHERIT_ONLY_ACE) &&
entry_type == OWNING_GROUP))
continue;
if ((access_mask & ACE_READ_DATA) &&
}
}
if ((access_mask & ACE_WRITE_DATA) &&
}
}
if ((access_mask & ACE_EXECUTE) &&
}
}
} else if (entry_type == OWNING_GROUP ||
if ((access_mask & ACE_READ_DATA) &&
}
}
if ((access_mask & ACE_WRITE_DATA) &&
}
}
if ((access_mask & ACE_EXECUTE) &&
}
}
} else if (entry_type == ACE_EVERYONE) {
if ((access_mask & ACE_READ_DATA)) {
}
}
}
}
}
}
}
if ((access_mask & ACE_WRITE_DATA)) {
}
}
}
}
}
}
}
if ((access_mask & ACE_EXECUTE)) {
}
}
}
}
}
}
}
} else {
/*
* Only care if this IDENTIFIER_GROUP or
* USER ACE denies execute access to someone,
* mode is not affected
*/
}
}
/*
* Failure to allow is effectively a deny, so execute permission
* is denied if it was never mentioned or if we explicitly
* weren't allowed it.
*/
if (!an_exec_denied &&
if (an_exec_denied)
*pflags &= ~ZFS_NO_EXECS_DENIED;
else
return (mode);
}
/*
* Read an external acl object. If the intent is to modify, always
* create a new acl and leave any cached acl in place.
*/
static int
{
int aclsize;
int acl_count;
int version;
int error;
return (0);
}
/*
* close race where znode could be upgrade while trying to
* read the znode attributes.
*
* But this could only happen if the file isn't already an SA
* znode
*/
}
goto done;
}
if (znode_acl.z_acl_extern_obj) {
} else {
}
} else {
}
if (error != 0) {
/* convert checksum errors into IO errors */
goto done;
}
if (!will_modify)
done:
if (drop_lock)
return (error);
}
/*ARGSUSED*/
void
{
if (start) {
} else {
cb->cb_acl_node);
}
}
static int
{
int count = 0;
int error;
return (error);
}
} else {
}
return (0);
}
int
{
int error;
return (error);
return (error);
}
/*
* common code for setting ACLs.
*
* This function is called from zfs_mode_update, zfs_perm_init, and zfs_setacl.
* zfs_setacl passes a non-NULL inherit pointer (ihp) to indicate that it's
* already checked the acl and knows whether to inherit.
*/
int
{
int error;
zfs_acl_locator_cb_t locate = { 0 };
int count = 0;
return (error);
if (zp->z_acl_cached) {
}
/*
* Upgrade needed?
*/
if (!zfsvfs->z_use_fuids) {
} else {
otype = DMU_OT_ACL;
}
/*
* Arrgh, we have to handle old on disk format
* as well as newer (preferred) SA format.
*/
} else { /* Painful legacy way */
return (error);
/*
* If ACL was previously external and we are now
* converting to new ACL format then release old
* ACL object and create a new one.
*/
if (aoid &&
if (error)
return (error);
aoid = 0;
}
if (aoid == 0) {
otype == DMU_OT_ACL ?
otype == DMU_OT_ACL ?
DN_MAX_BONUSLEN : 0, tx);
} else {
}
if (aclnode->z_ace_count == 0)
continue;
}
} else {
/*
* Migrating back embedded?
*/
if (acl_phys.z_acl_extern_obj) {
if (error)
return (error);
acl_phys.z_acl_extern_obj = 0;
}
if (aclnode->z_ace_count == 0)
continue;
}
}
/*
* layout of znode_acl_phys_t.
*/
} else {
}
}
/*
* Replace ACL wide bits, but first clear them.
*/
}
/*
* Update access mask for prepended ACE
*
* This applies the "groupmask" value for aclmode property.
*/
static void
{
int user_ace;
} else {
}
if (origmask & ACE_READ_DATA) {
acepmask &= ~ACE_READ_DATA;
} else {
}
}
if (origmask & ACE_WRITE_DATA) {
acepmask &= ~ACE_WRITE_DATA;
} else {
}
}
if (origmask & ACE_APPEND_DATA) {
acepmask &= ~ACE_APPEND_DATA;
} else {
}
}
if (origmask & ACE_EXECUTE) {
acepmask &= ~ACE_EXECUTE;
} else {
acepmask |= ACE_EXECUTE;
}
}
}
static void
{
int ace_size;
int entry_type;
void *zacep;
if (allow0) {
new_count++;
} if (deny1) {
new_count++;
}
if (deny2) {
new_count++;
}
(entry_type == OWNING_GROUP)) &&
((inherit_flags & ACE_INHERIT_ONLY_ACE) == 0)) {
continue;
}
if (inherit_flags)
switch (type) {
break;
}
} else {
/*
* Limit permissions to be no greater than
* group permissions
*/
access_mask &= ~ACE_READ_DATA;
access_mask &=
access_mask &= ~ACE_EXECUTE;
access_mask &=
}
}
new_count++;
}
new_count += 3;
}
int
{
return (0);
}
/*
* strip off write_owner and write_acl
*/
static void
{
mask &= ~RESTRICTED_CLEAR;
}
}
/*
* Should ACE be inherited?
*/
static int
{
return (1);
else if (iflags & ACE_FILE_INHERIT_ACE)
return (0);
}
/*
* inherit inheritable ACEs from parent
*/
static zfs_acl_t *
{
void *pacep;
void *acep;
passthrough = passthrough_x ||
noallow =
*need_chmod = B_TRUE;
return (aclp);
/*
* don't inherit bogus ACEs
*/
continue;
continue;
continue;
/*
* If owner@, group@, or everyone@ inheritable
* then zfs_acl_chmod() isn't needed.
*/
if (passthrough &&
((iflags & OWNING_GROUP) ==
*need_chmod = B_FALSE;
}
if (!vdir && passthrough_x &&
access_mask &= ~ACE_EXECUTE;
}
/*
* Copy special opaque data if any
*/
}
aclp->z_acl_count++;
aclnode->z_ace_count++;
if (vdir)
newflags &= ~ALL_INHERIT;
continue;
}
/*
* If only FILE_INHERIT is set then turn on
* inherit_only
*/
if ((iflags & (ACE_FILE_INHERIT_ACE |
} else {
}
}
return (aclp);
}
/*
* Create file system object initial permissions
* including inheritable ACEs.
*/
int
{
int error;
if (vsecp)
return (error);
/*
* Determine uid and gid.
*/
} else {
else
secpolicy_vnode_create_gid(cr) != 0)
}
char *domain;
if (zfsvfs->z_use_fuids &&
&zfsvfs->z_fuid_idx,
}
} else {
}
}
}
/*
* If we're creating a directory, and the parent directory has the
* set-GID bit set, set in on the new directory.
* Otherwise, if the user is neither privileged nor a member of the
* file's new group, clear the file's set-GID bit.
*/
} else {
}
} else {
}
if (need_chmod) {
ZFS_ACL_AUTO_INHERIT : 0;
}
}
}
return (0);
}
/*
* Free ACL and fuid_infop, but not the acl_ids structure
*/
void
{
}
{
}
/*
* Retrieve a files ACL
*/
int
{
int error;
int count = 0;
int largeace = 0;
if (mask == 0)
return (ENOSYS);
return (error);
if (error != 0) {
return (error);
}
/*
* Scan ACL to determine number of ACEs
*/
switch (type) {
largeace++;
continue;
default:
count++;
}
}
} else
if (mask & VSA_ACECNT) {
}
sizeof (ace_object_t) * largeace;
else {
}
aclp->z_acl_bytes);
}
}
if (mask & VSA_ACE_ACLFLAGS) {
vsecp->vsa_aclflags = 0;
}
return (0);
}
int
{
int error;
return (EINVAL);
return (error);
}
} else {
return (error);
}
}
/*
* If flags are being set then add them to z_hints
*/
}
return (0);
}
/*
* Set a files ACL
*/
int
{
int error;
if (mask == 0)
return (ENOSYS);
return (EPERM);
return (error);
&aclp);
if (error)
return (error);
/*
* If ACL wide flags aren't being set then preserve any
* existing flags.
*/
}
top:
if (fuid_dirtied)
/*
* If old version and ACL won't fit in bonus and we aren't
* upgrading then take out necessary DMU holds
*/
aclp->z_acl_bytes);
} else {
}
}
if (error) {
goto top;
}
return (error);
}
if (fuid_dirtied)
if (fuidp)
done:
return (error);
}
/*
* Check accesses of interest (AoI) against attributes of the dataset
* such as read-only. Returns zero if no AoI conflict with dataset
* attributes, otherwise an appropriate errno is returned.
*/
static int
{
if ((v4_mode & WRITE_MASK) &&
return (EROFS);
}
/*
* Only check for READONLY on non-directories.
*/
if ((v4_mode & WRITE_MASK_DATA) &&
return (EPERM);
}
return (EPERM);
}
return (EACCES);
}
return (0);
}
/*
* The primary usage of this function is to loop through all of the
* ACEs in the znode, determining what accesses of interest (AoI) to
* the caller are allowed or denied. The AoI are expressed as bits in
* the working_mode parameter. As each ACE is processed, bits covered
* by that ACE are removed from the working_mode. This removal
* facilitates two things. The first is that when the working mode is
* empty (= 0), we know we've looked at all the AoI. The second is
* that the ACE interpretation rules don't allow a later ACE to undo
* something granted or denied by an earlier ACE. Removing the
* discovered access or denial enforces this rule. At the end of
* processing the ACEs, all AoI that were found to be denied are
* placed into the working_mode, giving the caller a mask of denied
* accesses. Returns:
* 0 if all AoI granted
* EACCESS if the denied mask is non-zero
* other error if abnormal failure (e.g., IO error)
*
* A secondary usage of the function is to determine if any of the
* AoI are granted. If an ACE grants any access in
* the working_mode, we immediately short circuit out of the function.
* This mode is chosen by setting anyaccess to B_TRUE. The
* working_mode is not a denied access mask upon exit if the function
* is used in this manner.
*/
static int
{
int error;
if (error != 0) {
return (error);
}
return (error);
}
continue;
continue;
/* Skip ACE if it does not affect any AoI */
if (!mask_matched)
continue;
switch (entry_type) {
case ACE_OWNER:
break;
case OWNING_GROUP:
/*FALLTHROUGH*/
case ACE_IDENTIFIER_GROUP:
break;
case ACE_EVERYONE:
break;
/* USER Entry */
default:
if (entry_type == 0) {
if (newid != IDMAP_WK_CREATOR_OWNER_UID &&
break;
} else {
return (EIO);
}
}
if (checkit) {
zfs_ace_hdr_t *, acep,
} else {
zfs_ace_hdr_t *, acep,
if (anyaccess) {
return (0);
}
}
*working_mode &= ~mask_matched;
}
/* Are we done? */
if (*working_mode == 0)
break;
}
/* Put the found 'denies' back on the working mode */
if (deny_mask) {
*working_mode |= deny_mask;
return (EACCES);
} else if (*working_mode) {
return (-1);
}
return (0);
}
/*
* Return true if any access whatsoever granted, we don't actually
* care what access is granted.
*/
{
}
return (B_TRUE);
}
static int
{
int err;
*working_mode = v4_mode;
*check_privs = B_TRUE;
/*
* Short circuit empty requests
*/
*working_mode = 0;
return (0);
}
*check_privs = B_FALSE;
return (err);
}
/*
* The caller requested that the ACL check be skipped. This
* would only happen if the caller checked VOP_ACCESS() with a
* 32 bit ACE mask and already had the appropriate permissions.
*/
if (skipaclchk) {
*working_mode = 0;
return (0);
}
}
static int
{
if (*working_mode != ACE_WRITE_DATA)
return (EACCES);
}
int
{
int error;
return (EACCES);
if (is_attr)
goto slow;
return (0);
}
goto slow;
}
return (0);
} else {
goto slow;
}
}
return (0);
} else {
goto slow;
}
}
return (0);
}
}
slow:
return (error);
}
/*
* The least priv subsytem is always consulted as a basic privilege
* can define any form of access.
*/
int
{
int error;
int is_attr;
/*
* If attribute then validate against base file
*/
if (is_attr) {
sizeof (parent))) != 0)
return (error);
return (error);
}
/*
* fixup mode to map to xattr perms
*/
}
}
}
/*
* Map the bits required to the standard vnode flags VREAD|VWRITE|VEXEC
* in needed_bits. Map the bits mapped by working_mode (currently
* missing) in missing_bits.
* Call secpolicy_vnode_access2() with (needed_bits & ~checkmode),
* needed_bits.
*/
needed_bits = 0;
working_mode = mode;
needed_bits |= VREAD;
needed_bits |= VWRITE;
if (working_mode & ACE_EXECUTE)
needed_bits |= VEXEC;
if (is_attr)
}
if (error && !check_privs) {
if (is_attr)
return (error);
}
}
if (error && check_privs) {
/*
* First check for implicit owner permission on
*/
error = 0;
ASSERT(working_mode != 0);
if (working_mode & ACE_EXECUTE)
if (error == 0 && (working_mode &
}
if (error == 0) {
/*
* See if any bits other than those already checked
* for are still present. If so then return EACCES
*/
if (working_mode & ~(ZFS_CHECKED_MASKS)) {
}
}
} else if (error == 0) {
}
if (is_attr)
return (error);
}
/*
* native ACL format and call zfs_zaccess()
*/
int
{
}
/*
* Access function for secpolicy_vnode_setattr
*/
int
{
}
static int
{
int error;
if (error == 0)
return (error);
}
/*
* consulting least priv subsystem.
*
*
* The following chart is the recommended NFSv4 enforcement for
* ability to delete an object.
*
* -------------------------------------------------------
* | Parent Dir | Target Object Permissions |
* | permissions | |
* -------------------------------------------------------
* | | ACL Allows | ACL Denies| Delete |
* | | Delete | Delete | unspecified|
* -------------------------------------------------------
* | ACL Allows | Permit | Permit | Permit |
* | DELETE_CHILD | |
* -------------------------------------------------------
* | ACL Denies | Permit | Deny | Deny |
* | DELETE_CHILD | | | |
* -------------------------------------------------------
* | ACL specifies | | | |
* | only allow | Permit | Permit | Permit |
* | write and | | | |
* | execute | | | |
* -------------------------------------------------------
* | ACL denies | | | |
* | write and | Permit | Deny | Deny |
* | execute | | | |
* -------------------------------------------------------
* ^
* |
* No search privilege, can't even look up file?
*
*/
int
{
uint32_t dzp_working_mode = 0;
uint32_t zp_working_mode = 0;
/*
* We want specific DELETE permissions to
* want an ACL such as this to mess us up.
* user:joe:write_data:deny,user:joe:delete:allow
*
* However, deny permissions may ultimately be overridden
* by secpolicy_vnode_access().
*
* We will ask for all of the necessary permissions and then
* look at the working modes from the directory and target object
* to determine what was found.
*/
return (EPERM);
/*
* First row
* If the directory permissions allow the delete, we are done.
*/
return (0);
/*
* If target object has delete permission then we are done
*/
return (0);
if (!dzpcheck_privs)
return (dzp_error);
if (!zpcheck_privs)
return (zp_error);
/*
* Second row
*
* If directory returns EACCES then delete_child was denied
* due to deny delete_child. In this case send the request through
* secpolicy_vnode_remove(). We don't use zfs_delete_final_check()
*/
return (secpolicy_vnode_remove(cr));
/*
* Third Row
*/
if (dzp_error != 0 && !dzpcheck_privs)
return (dzp_error);
/*
* Fourth row
*/
}
int
{
int add_perm;
int error;
return (EACCES);
/*
* Rename permissions are combination of delete permission +
*/
/*
* first make sure we do the delete portion.
*
* If that succeeds then check for add_file/add_subdir permissions
*/
return (error);
/*
* If we have a tzp, see if we can delete it?
*/
if (tzp) {
return (error);
}
/*
* Now check for add permissions
*/
return (error);
}