zfs_acl.c revision 1ab996781aab376b5ee79af025ab24ff42a0a3f0
/*
* 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
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#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
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 = {
};
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
{
}
static zfs_acl_t *
zfs_acl_alloc(int vers)
{
if (vers == ZFS_ACL_VERSION_FUID)
else
return (aclp);
}
static zfs_acl_node_t *
{
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
{
/*
* first check type of entry
*/
switch (iflags & ACE_TYPE_FLAGS) {
case ACE_OWNER:
case OWNING_GROUP:
case ACE_IDENTIFIER_GROUP:
case ACE_EVERYONE:
case 0: /* User entry */
break;
default:
return (B_FALSE);
}
/*
* next check inheritance level flags
*/
return (B_FALSE);
}
switch (type) {
return (B_FALSE);
}
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 {
}
}
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) {
if (!aclp->z_has_fuids)
}
/*
* 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));
&newaclnode->z_size) == 0);
/*
* 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.
*/
static uint64_t
{
int entry_type;
/*
* Skip over owner@, group@ or everyone@ inherit only ACEs
*/
if ((iflags & ACE_INHERIT_ONLY_ACE) &&
entry_type == OWNING_GROUP))
continue;
if (entry_type == ACE_OWNER) {
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)) {
}
}
}
}
}
}
}
}
/*
*/
}
}
return (mode);
}
static zfs_acl_t *
{
/*
* Version 0 didn't have a size field, only a count.
*/
} else {
}
if (will_modify) {
aclp->z_acl_bytes);
} else {
}
return (aclp);
}
/*
* Read an external acl object.
*/
static int
{
int error;
return (0);
}
} else {
if (aclsize == 0)
}
if (error != 0) {
/* convert checksum errors into IO errors */
return (error);
}
return (0);
}
/*
* 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;
/*
* Decide which opbject type to use. If we are forced to
* use old ACL format than transform ACL into zfs_oldace_t
* layout.
*/
if (!zfsvfs->z_use_fuids) {
} else {
otype = DMU_OT_ACL;
}
/*
* 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 (error)
return (error);
aoid = 0;
}
if (aoid == 0) {
} else {
}
if (aclnode->z_ace_count == 0)
continue;
}
} else {
/*
* Migrating back embedded?
*/
if (error)
return (error);
}
if (aclnode->z_ace_count == 0)
continue;
}
}
/*
* layout of znode_acl_phys_t.
*/
} else {
}
/*
* Replace ACL wide bits, but first clear them.
*/
return (0);
}
/*
* 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;
}
}
}
/*
* Apply mode to canonical six ACEs.
*/
static void
{
void *acep;
/*
* Fixup final ACEs to match the mode
*/
}
static int
int entry_type, int accessmask)
{
}
/*
* Can prepended ACE be reused?
*/
static int
{
int okay_masks;
return (B_FALSE);
return (B_FALSE);
return (B_FALSE);
if (prevmask & ~okay_masks)
return (B_FALSE);
return (B_TRUE);
}
/*
* Insert new ACL node into chain of zfs_acl_node_t's
*
* This will result in two possible results.
* 1. If the ACL is currently just a single zfs_acl_node and
* we are prepending the entry then current acl node will have
* a new node inserted above it.
*
* 2. If we are inserting in the middle of current acl node then
* the current node will be split in two and new node will be inserted
* in between the two split nodes.
*/
static zfs_acl_node_t *
{
int trailer_count;
if (curr_idx != 1) {
}
if (curr_idx == 1)
else
if (trailernode) {
}
return (newnode);
}
/*
* Prepend deny ACE
*/
static void *
{
void *newacep;
return (newacep);
}
/*
* Split an inherited ACE into inherit_only ACE
* and original ACE with inheritance flags stripped off.
*/
static void
{
void *newacep;
flags &= ~ALL_INHERIT;
}
/*
* Are ACES started at index i, the canonical six ACES?
*/
static int
{
void *acep;
int i = 0;
return (0);
(abstract_size * i++),
ALLOW, OWNING_GROUP, 0) &&
return (1);
} else {
return (0);
}
}
/*
* Apply step 1g, to group entries
*
* Need to deal with corner case where group may have
* greater permissions than owner. If so then limit
* group permissions, based on what extra permissions
* group has.
*/
static void
{
if (prevflags & ACE_IDENTIFIER_GROUP) {
if (extramode) {
prevmask &= ~ACE_READ_DATA;
mask &= ~ACE_READ_DATA;
}
}
prevmask &= ~ACE_EXECUTE;
mask &= ~ACE_EXECUTE;
}
}
}
}
/*
* Apply the chmod algorithm as described
* in PSARC/2002/240
*/
static void
{
int i;
int entry_type;
int reuse_deny;
int need_canonical_six = 1;
/*
* If discard then just discard all ACL nodes which
* represent the ACEs.
*
* New owner@/group@/everone@ ACEs will be added
* later.
*/
(iflags & ACE_INHERIT_ONLY_ACE)) {
if (iflags)
switch (type) {
break;
}
goto nextace;
}
/*
* Need to split ace into two?
*/
if ((iflags & (ACE_FILE_INHERIT_ACE|
(!(iflags & ACE_INHERIT_ONLY_ACE))) {
goto nextace;
}
(entry_type == OWNING_GROUP)) {
access_mask &= ~OGE_CLEAR;
goto nextace;
} else {
reuse_deny = B_TRUE;
/*
* Check preceding ACE if any, to see
* if we need to prepend a DENY ACE.
* This is only applicable when the acl_mode
* property == groupmask.
*/
prevacep);
if (!reuse_deny) {
prevacep =
} else {
}
}
}
}
}
/*
* Check out last six aces, if we have six.
*/
if (zfs_have_canonical_six(aclp)) {
need_canonical_six = 0;
}
}
if (need_canonical_six) {
void *zacep;
i = 0;
}
}
int
{
int error;
if (error == 0)
return (error);
}
/*
* 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;
*need_chmod = B_TRUE;
continue;
continue;
/*
* If owner@, group@, or everyone@ inheritable
* then zfs_acl_chmod() isn't needed.
*/
if (zfsvfs->z_acl_inherit ==
((iflags & OWNING_GROUP) ==
(iflags & ACE_DIRECTORY_INHERIT_ACE))))
*need_chmod = B_FALSE;
/*
* Copy special opaque data if any
*/
&data1)) != 0) {
}
aclp->z_acl_count++;
aclnode->z_ace_count++;
if ((iflags & ACE_NO_PROPAGATE_INHERIT_ACE) ||
newflags &= ~ALL_INHERIT;
continue;
}
if ((iflags & (ACE_FILE_INHERIT_ACE |
newflags &= ~ALL_INHERIT;
/*
* Copy special opaque data if any
*/
&data1)) != 0) {
}
aclp->z_acl_count++;
aclnode2->z_ace_count++;
} else {
}
}
}
return (aclp);
}
/*
* Create file system object initial permissions
* including inheritable ACEs.
*/
void
{
int error;
if (setaclp)
/*
* Determine uid and gid.
*/
} else {
fgid = 0;
secpolicy_vnode_create_gid(cr) != 0)
fgid = 0;
}
if (fgid == 0) {
} 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)
} else {
}
/* Force auto_inherit on all new directory objects */
/* Set optional attributes if any */
}
/*
* Retrieve a files ACL
*/
int
{
int error;
int count = 0;
int largeace = 0;
return (error);
if (mask == 0)
return (ENOSYS);
if (error != 0) {
return (error);
}
/*
* Scan ACL to determine number of ACEs
*/
!(mask & VSA_ACE_ALLTYPES)) {
switch (type) {
largeace++;
continue;
default:
count++;
}
}
} else
if (mask & VSA_ACECNT) {
}
sizeof (ace_object_t) * largeace;
else {
}
}
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);
if (error)
return (error);
/*
* If ACL wide flags aren't being set then preserve any
* existing flags.
*/
}
top:
return (error);
}
/* Are we upgrading ACL? */
0, DMU_OBJECT_END);
0, aclp->z_acl_bytes);
} else {
0, aclp->z_acl_bytes);
}
}
if (aclp->z_has_fuids) {
if (zfsvfs->z_fuid_obj == 0) {
} else {
}
}
if (error) {
goto top;
}
return (error);
}
if (fuidp)
done:
return (error);
}
/*
* working_mode returns the permissions that were not granted
*/
static int
{
int error;
/*
* Short circuit empty requests
*/
if (v4_mode == 0)
return (0);
*check_privs = B_TRUE;
*working_mode = 0;
return (0);
}
*working_mode = v4_mode;
if ((v4_mode & WRITE_MASK) &&
*check_privs = B_FALSE;
return (EROFS);
}
/*
* Only check for READONLY on non-directories.
*/
if ((v4_mode & WRITE_MASK_DATA) &&
*check_privs = B_FALSE;
return (EPERM);
}
*check_privs = B_FALSE;
return (EPERM);
}
*check_privs = B_FALSE;
return (EACCES);
}
/*
* 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);
}
if (error != 0) {
return (error);
}
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) {
if (mask_matched) {
*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);
}
static int
{
if (*working_mode != ACE_WRITE_DATA)
return (EACCES);
}
/*
* priv subsytem when a deny is determined.
*/
int
{
int error;
int is_attr;
/*
* If attribute then validate against base file
*/
if (is_attr) {
return (error);
}
/*
* fixup mode to map to xattr perms
*/
}
}
}
if (is_attr)
return (0);
}
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 (checkmode)
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)) {
}
}
}
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 (!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);
}