Implement NFSv41 ACLs through xattr

This implements NFSv41 (RFC 5661) ACLs in a manner
compatible with vfs_nfs4acl_xattr in Samba and
nfs4xdr-acl-tools.

There are three key areas of change in this commit:
1) NFSv4 ACL management through system.nfs4_acl_xdr xattr.
  Install an xattr handler for "system.nfs4_acl_xdr" that
  presents an xattr containing full NFSv41 ACL structures
  generated through rpcgen using specification from the Samba
  project. This xattr is used by userspace programs to read and
  set permissions.

2) add an i_op->permissions endpoint: zpl_permissions(). This
  is used by the VFS in Linux to determine whether to allow /
  deny an operation. Wherever possible, we try to avoid having
  to call zfs_access(). If kernel has NFSv4 patch for VFS, then
  perform more complete check of avaiable access mask.

3) add capability-based overrides to secpolicy_vnode_access2()
  there are various situations in which ACL may need to be
  overridden based on capabilities. This logic is almost directly
  copied from Linux VFS. For instance, root needs to be able to
  always read / write ACLs (otherwise admin can get locked out
  from files).

This is commit was initially inspired by work from Paul B. Henson
to implement NFSv4.0 (RFC3530) ACLs in ZFS on Linux. Key areas of
divergence are as follows:
- ACL specification, xattr format, xattr name
- Addition of handling for NFSv4 masks from Linux VFS
- Addition of ACL overrides based on capabilities

Signed-off-by: Andrew Walker <awalker@ixsystems.com>
This commit is contained in:
Andrew 2021-05-24 10:49:43 -04:00 committed by Ameer Hamza
parent 6343ecfcfe
commit b5748dea71
12 changed files with 624 additions and 4 deletions

2
.gitignore vendored
View File

@ -84,6 +84,8 @@ modules.order
Makefile
Makefile.in
changelog
nfs41acl.h
nfs41acl_xdr.c
*.patch
*.orig
*.tmp

View File

@ -122,6 +122,7 @@ cstyle:
$(AM_V_at)find $(top_srcdir) -name build -prune \
-o -type f -name '*.[hc]' \
! -name 'zfs_config.*' ! -name '*.mod.c' \
! -name 'nfs41acl_xdr.c' ! -name 'nfs41acl.h' \
! -name 'opt_global.h' ! -name '*_if*.h' \
! -name 'zstd_compat_wrapper.h' \
! -path './module/zstd/lib/*' \

View File

@ -22,6 +22,7 @@
#define _SPL_RPC_XDR_H
#include <sys/types.h>
#include <sys/sysmacros.h>
typedef int bool_t;

View File

@ -106,6 +106,8 @@ zpl_chmod_acl(struct inode *ip)
}
#endif /* CONFIG_FS_POSIX_ACL */
extern int zpl_permission(struct inode *ip, int mask);
extern xattr_handler_t *zpl_xattr_handlers[];
/* zpl_ctldir.c */

View File

@ -439,6 +439,7 @@ ZFS_OBJS_OS := \
abd_os.o \
arc_os.o \
mmp_os.o \
nfs41acl_xdr.o \
policy.o \
qat.o \
qat_compress.o \

View File

@ -53,6 +53,16 @@ FMAKE = env -u MAKEFLAGS make $(FMAKEFLAGS)
modules-Linux:
mkdir -p $(sort $(dir $(spl-objs) $(spl-)))
mkdir -p $(sort $(dir $(zfs-objs) $(zfs-)))
@# Generate the nfs41acl XDR header.
rpcgen -h "@abs_srcdir@/os/linux/zfs/nfs41acl.x" > "@abs_srcdir@/os/linux/zfs/nfs41acl.h"
sed -i "/\<rpc\/rpc.h\>/c\\#include \<rpc\/xdr.h\>" "@abs_srcdir@/os/linux/zfs/nfs41acl.h"
sed -i "9i #include <sys/sysmacros.h>" "@abs_srcdir@/os/linux/zfs/nfs41acl.h"
@# Generate the nfs41acl XDR code.
rpcgen -c "@abs_srcdir@/os/linux/zfs/nfs41acl.x" > "@abs_srcdir@/os/linux/zfs/nfs41acl_xdr.c"
sed -i "5i #pragma GCC diagnostic push" "@abs_srcdir@/os/linux/zfs/nfs41acl_xdr.c"
sed -i "6i #pragma GCC diagnostic ignored \"-Wunused-variable\"" "@abs_srcdir@/os/linux/zfs/nfs41acl_xdr.c"
echo "#pragma GCC diagnostic pop" >> "@abs_srcdir@/os/linux/zfs/nfs41acl_xdr.c"
@# Build the kernel modules.
$(MAKE) -C @LINUX_OBJ@ $(if @KERNEL_CC@,CC=@KERNEL_CC@) \
$(if @KERNEL_LD@,LD=@KERNEL_LD@) $(if @KERNEL_LLVM@,LLVM=@KERNEL_LLVM@) \
M="$$PWD" @KERNEL_MAKE@ CONFIG_ZFS=m modules
@ -161,7 +171,7 @@ cppcheck-FreeBSD:
cppcheck: cppcheck-@ac_system@
distdir:
cd @srcdir@ && find . -name '*.[chS]' -exec sh -c 'for f; do mkdir -p $$distdir/$${f%/*}; cp @srcdir@/$$f $$distdir/$$f; done' _ {} +
cd @srcdir@ && find . -name '*.[chxS]' -exec sh -c 'for f; do mkdir -p $$distdir/$${f%/*}; cp @srcdir@/$$f $$distdir/$$f; done' _ {} +
cp @srcdir@/Makefile.bsd $$distdir/Makefile.bsd
gen-zstd-symbols:

View File

@ -0,0 +1,74 @@
const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000;
const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001;
const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002;
const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003;
typedef u_int acetype4;
const ACE4_FILE_INHERIT_ACE = 0x00000001;
const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002;
const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004;
const ACE4_INHERIT_ONLY_ACE = 0x00000008;
const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010;
const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020;
const ACE4_IDENTIFIER_GROUP = 0x00000040;
const ACE4_INHERITED_ACE = 0x00000080;
typedef u_int aceflag4;
const ACEI4_SPECIAL_WHO = 0x00000001;
typedef u_int aceiflag4;
const ACE4_SPECIAL_OWNER = 1;
const ACE4_SPECIAL_GROUP = 2;
const ACE4_SPECIAL_EVERYONE = 3;
const ACE4_SPECIAL_INTERACTIVE = 4;
const ACE4_SPECIAL_NETWORK = 5;
const ACE4_SPECIAL_DIALUP = 6;
const ACE4_SPECIAL_BATCH = 7;
const ACE4_SPECIAL_ANONYMOUS = 8;
const ACE4_SPECIAL_AUTHENTICATED = 9;
const ACE4_SPECIAL_SERVICE = 10;
const ACE4_READ_DATA = 0x00000001;
const ACE4_LIST_DIRECTORY = 0x00000001;
const ACE4_WRITE_DATA = 0x00000002;
const ACE4_ADD_FILE = 0x00000002;
const ACE4_APPEND_DATA = 0x00000004;
const ACE4_ADD_SUBDIRECTORY = 0x00000004;
const ACE4_READ_NAMED_ATTRS = 0x00000008;
const ACE4_WRITE_NAMED_ATTRS = 0x00000010;
const ACE4_EXECUTE = 0x00000020;
const ACE4_DELETE_CHILD = 0x00000040;
const ACE4_READ_ATTRIBUTES = 0x00000080;
const ACE4_WRITE_ATTRIBUTES = 0x00000100;
const ACE4_WRITE_RETENTION = 0x00000200;
const ACE4_WRITE_RETENTION_HOLD = 0x00000400;
const ACE4_DELETE = 0x00010000;
const ACE4_READ_ACL = 0x00020000;
const ACE4_WRITE_ACL = 0x00040000;
const ACE4_WRITE_OWNER = 0x00080000;
const ACE4_SYNCHRONIZE = 0x00100000;
typedef u_int acemask4;
struct nfsace4i {
acetype4 type;
aceflag4 flag;
aceiflag4 iflag;
acemask4 access_mask;
u_int who;
};
const ACL4_AUTO_INHERIT = 0x00000001;
const ACL4_PROTECTED = 0x00000002;
const ACL4_DEFAULTED = 0x00000004;
typedef u_int aclflag4;
struct nfsacl41i {
aclflag4 na41_flag;
nfsace4i na41_aces<>;
};

View File

@ -33,6 +33,7 @@
#include <sys/policy.h>
#include <linux/security.h>
#include <linux/vfs_compat.h>
#include <sys/zfs_znode.h>
/*
* The passed credentials cannot be directly verified because Linux only
@ -103,13 +104,52 @@ secpolicy_sys_config(const cred_t *cr, boolean_t checkonly)
* Like secpolicy_vnode_access() but we get the actual wanted mode and the
* current mode of the file, not the missing bits.
*
* Enforced in the Linux VFS.
* If filesystem is using NFSv4 ACLs, validate the current mode
* and the wanted mode are the same, otherwise access fails.
*
* If using POSIX ACLs or no ACLs, enforced in the Linux VFS.
*/
int
secpolicy_vnode_access2(const cred_t *cr, struct inode *ip, uid_t owner,
mode_t curmode, mode_t wantmode)
{
return (0);
mode_t remainder = ~curmode & wantmode;
if ((ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) ||
(remainder == 0)) {
return (0);
}
/*
* short-circuit if root
*/
if (capable(CAP_SYS_ADMIN)) {
return (0);
}
/*
* There are some situations in which capabilities
* may allow overriding the DACL.
*/
if (S_ISDIR(ip->i_mode)) {
if (!(wantmode & S_IWUSR) &&
capable(CAP_DAC_READ_SEARCH)) {
return (0);
}
if (capable(CAP_DAC_OVERRIDE)) {
return (0);
}
return (EACCES);
}
if ((wantmode == S_IRUSR) && capable(CAP_DAC_READ_SEARCH)) {
return (0);
}
if (!(remainder & S_IXUSR) && capable(CAP_DAC_OVERRIDE)) {
return (0);
}
return (EACCES);
}
/*

View File

@ -359,12 +359,17 @@ acltype_changed_cb(void *arg, uint64_t newval)
zfsvfs_t *zfsvfs = arg;
switch (newval) {
case ZFS_ACLTYPE_NFSV4:
case ZFS_ACLTYPE_OFF:
zfsvfs->z_acl_type = ZFS_ACLTYPE_OFF;
zfsvfs->z_sb->s_flags &= ~SB_POSIXACL;
#ifdef SB_NFSV4ACL
zfsvfs->z_sb->s_flags &= ~SB_NFSV4ACL;
#endif
break;
case ZFS_ACLTYPE_POSIX:
#ifdef SB_NFSV4ACL
zfsvfs->z_sb->s_flags &= ~SB_NFSV4ACL;
#endif
#ifdef CONFIG_FS_POSIX_ACL
zfsvfs->z_acl_type = ZFS_ACLTYPE_POSIX;
zfsvfs->z_sb->s_flags |= SB_POSIXACL;
@ -373,6 +378,13 @@ acltype_changed_cb(void *arg, uint64_t newval)
zfsvfs->z_sb->s_flags &= ~SB_POSIXACL;
#endif /* CONFIG_FS_POSIX_ACL */
break;
case ZFS_ACLTYPE_NFSV4:
zfsvfs->z_acl_type = ZFS_ACLTYPE_NFSV4;
zfsvfs->z_sb->s_flags &= ~SB_POSIXACL;
#ifdef SB_NFSV4ACL
zfsvfs->z_sb->s_flags |= SB_NFSV4ACL;
#endif
break;
default:
break;
}

View File

@ -816,6 +816,7 @@ const struct inode_operations zpl_inode_operations = {
.get_acl = zpl_get_acl,
#endif /* HAVE_GET_INODE_ACL */
#endif /* CONFIG_FS_POSIX_ACL */
.permission = zpl_permission,
};
#ifdef HAVE_RENAME2_OPERATIONS_WRAPPER
@ -862,6 +863,7 @@ const struct inode_operations zpl_dir_inode_operations = {
.get_acl = zpl_get_acl,
#endif /* HAVE_GET_INODE_ACL */
#endif /* CONFIG_FS_POSIX_ACL */
.permission = zpl_permission,
#ifdef HAVE_RENAME2_OPERATIONS_WRAPPER
},
.rename2 = zpl_rename2,
@ -909,4 +911,5 @@ const struct inode_operations zpl_special_inode_operations = {
.get_acl = zpl_get_acl,
#endif /* HAVE_GET_INODE_ACL */
#endif /* CONFIG_FS_POSIX_ACL */
.permission = zpl_permission,
};

View File

@ -230,6 +230,9 @@ __zpl_show_options(struct seq_file *seq, zfsvfs_t *zfsvfs)
case ZFS_ACLTYPE_POSIX:
seq_puts(seq, ",posixacl");
break;
case ZFS_ACLTYPE_NFSV4:
seq_puts(seq, ",nfs4acl");
break;
default:
seq_puts(seq, ",noacl");
break;

View File

@ -80,10 +80,34 @@
#include <sys/zfs_znode.h>
#include <sys/zfs_vfsops.h>
#include <sys/zfs_vnops.h>
#include <sys/xvattr.h>
#include <sys/zap.h>
#include <sys/vfs.h>
#include <sys/zpl.h>
#include <linux/vfs_compat.h>
#include <rpc/xdr.h>
#include "nfs41acl.h"
#define NFS41ACL_XATTR "system.nfs4_acl_xdr"
static const struct {
int kmask;
int zfsperm;
} mask2zfs[] = {
{ MAY_READ, ACE_READ_DATA },
{ MAY_WRITE, ACE_WRITE_DATA },
{ MAY_EXEC, ACE_EXECUTE },
#ifdef SB_NFSV4ACL
{ MAY_DELETE, ACE_DELETE },
{ MAY_DELETE_CHILD, ACE_DELETE_CHILD },
{ MAY_WRITE_ATTRS, ACE_WRITE_ATTRIBUTES },
{ MAY_WRITE_NAMED_ATTRS, ACE_WRITE_NAMED_ATTRS },
{ MAY_WRITE_ACL, ACE_WRITE_ACL },
{ MAY_WRITE_OWNER, ACE_WRITE_OWNER },
#endif
};
#define GENERIC_MASK(mask) ((mask & ~(MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
enum xattr_permission {
XAPERM_DENY,
@ -1457,6 +1481,448 @@ static xattr_handler_t zpl_xattr_acl_default_handler = {
#endif /* CONFIG_FS_POSIX_ACL */
int
zpl_permission(struct inode *ip, int mask)
{
int to_check = 0, i, ret;
cred_t *cr = NULL;
/*
* If NFSv4 ACLs are not being used, go back to
* generic_permission(). If ACL is trivial and the
* mask is representable by POSIX permissions, then
* also go back to generic_permission().
*/
if ((ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) ||
((ITOZ(ip)->z_pflags & ZFS_ACL_TRIVIAL && GENERIC_MASK(mask)))) {
return (generic_permission(ip, mask));
}
for (i = 0; i < ARRAY_SIZE(mask2zfs); i++) {
if (mask & mask2zfs[i].kmask) {
to_check |= mask2zfs[i].zfsperm;
}
}
/*
* We're being asked to check something that doesn't contain an
* NFSv4 ACE. Pass back to default kernel permissions check.
*/
if (to_check == 0) {
return (generic_permission(ip, mask));
}
/*
* Avoid potentially blocking in RCU walk.
*/
if (mask & MAY_NOT_BLOCK) {
return (-ECHILD);
}
cr = CRED();
crhold(cr);
ret = -zfs_access(ITOZ(ip), to_check, V_ACE_MASK, cr);
if (ret != -EPERM && ret != -EACCES) {
crfree(cr);
return (ret);
}
/*
* There are some situations in which capabilities
* may allow overriding the DACL.
*/
if (S_ISDIR(ip->i_mode)) {
#ifdef SB_NFSV4ACL
if (!(mask & (MAY_WRITE | NFS41ACL_WRITE_ALL))) {
#else
if (!(mask & MAY_WRITE)) {
#endif
if (capable(CAP_DAC_READ_SEARCH)) {
crfree(cr);
return (0);
}
}
if (capable(CAP_DAC_OVERRIDE)) {
crfree(cr);
return (0);
}
crfree(cr);
return (ret);
}
if (to_check == ACE_READ_DATA) {
if (capable(CAP_DAC_READ_SEARCH)) {
crfree(cr);
return (0);
}
}
if (!(mask & MAY_EXEC) ||
(zfs_fastaccesschk_execute(ITOZ(ip), cr) == 0)) {
if (capable(CAP_DAC_OVERRIDE)) {
crfree(cr);
return (0);
}
}
crfree(cr);
return (ret);
}
#define NFS41ACL_MAX_ACES 128
#define NFS41_FLAGS (ACE_DIRECTORY_INHERIT_ACE| \
ACE_FILE_INHERIT_ACE| \
ACE_NO_PROPAGATE_INHERIT_ACE| \
ACE_INHERIT_ONLY_ACE| \
ACE_INHERITED_ACE| \
ACE_IDENTIFIER_GROUP)
/*
* Macros for sanity checks related to XDR and ACL buffer sizes
*/
#define ACE4SIZE (sizeof (nfsace4i))
#define ACLBASE (sizeof (nfsacl41i))
#define XDRBASE (2 * sizeof (uint_t))
#define ACES_TO_SIZE(x, y) (x + (y * ACE4SIZE))
#define SIZE_TO_ACES(x, y) ((y - x) / ACE4SIZE)
#define SIZE_IS_VALID(x, y) ((x >= ACES_TO_SIZE(y, 0)) && \
(((x - y) % ACE4SIZE) == 0))
#define ACES_TO_ACLSIZE(x) (ACES_TO_SIZE(ACLBASE, x))
#define ACES_TO_XDRSIZE(x) (ACES_TO_SIZE(XDRBASE, x))
#define ACLSIZE_TO_ACES(x) (SIZE_TO_ACES(ACLBASE, x))
#define XDRSIZE_TO_ACES(x) (SIZE_TO_ACES(XDRBASE, x))
#define ACLSIZE_IS_VALID(x) (SIZE_IS_VALID(x, ACLBASE))
#define XDRSIZE_IS_VALID(x) (SIZE_IS_VALID(x, XDRBASE))
static int
__zpl_xattr_nfs41acl_list(struct inode *ip, char *list, size_t list_size,
const char *name, size_t name_len)
{
char *xattr_name = NFS41ACL_XATTR;
size_t xattr_size = sizeof (NFS41ACL_XATTR);
if (ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4)
return (0);
if (list && xattr_size <= list_size)
memcpy(list, xattr_name, xattr_size);
return (xattr_size);
}
ZPL_XATTR_LIST_WRAPPER(zpl_xattr_nfs41acl_list);
static int
acep_to_nfsace4i(const ace_t *acep, nfsace4i *nacep)
{
nacep->type = acep->a_type;
nacep->flag = acep->a_flags & NFS41_FLAGS;
nacep->access_mask = acep->a_access_mask;
switch (acep->a_flags & ACE_TYPE_FLAGS) {
case ACE_OWNER:
nacep->iflag |= ACEI4_SPECIAL_WHO;
nacep->who = ACE4_SPECIAL_OWNER;
break;
case ACE_GROUP|ACE_IDENTIFIER_GROUP:
nacep->iflag |= ACEI4_SPECIAL_WHO;
nacep->who = ACE4_SPECIAL_GROUP;
break;
case ACE_EVERYONE:
nacep->iflag |= ACEI4_SPECIAL_WHO;
nacep->who = ACE4_SPECIAL_EVERYONE;
break;
case ACE_IDENTIFIER_GROUP:
case 0:
nacep->who = acep->a_who;
break;
default:
dprintf("Unknown ACE_TYPE_FLAG 0x%08x\n",
acep->a_flags & ACE_TYPE_FLAGS);
return (-EINVAL);
}
return (0);
}
static int
zfsacl_to_nfsacl41i(const vsecattr_t vsecp, nfsacl41i **_nacl, size_t *_size)
{
nfsacl41i *nacl = NULL;
nfsace4i *nacep = NULL;
ace_t *acep = NULL;
int i, error;
size_t acl_size;
acl_size = ACES_TO_ACLSIZE(vsecp.vsa_aclcnt);
nacl = kmem_alloc(acl_size, KM_SLEEP);
nacl->na41_aces.na41_aces_len = vsecp.vsa_aclcnt;
nacl->na41_flag = vsecp.vsa_aclflags;
nacep = (nfsace4i *)((char *)nacl + sizeof (nfsacl41i));
nacl->na41_aces.na41_aces_val = nacep;
for (i = 0; i < nacl->na41_aces.na41_aces_len; i++) {
nacep = &nacl->na41_aces.na41_aces_val[i];
acep = vsecp.vsa_aclentp + (i * sizeof (ace_t));
error = acep_to_nfsace4i(acep, nacep);
if (error) {
kmem_free(nacl, acl_size);
return (error);
}
}
*_size = acl_size;
*_nacl = nacl;
return (0);
}
static int
nfsace4i_to_acep(const nfsace4i *nacep, ace_t *acep)
{
acep->a_type = nacep->type;
acep->a_flags = nacep->flag & NFS41_FLAGS;
acep->a_access_mask = nacep->access_mask;
if (nacep->iflag & ACEI4_SPECIAL_WHO) {
switch (nacep->who) {
case ACE4_SPECIAL_OWNER:
acep->a_flags |= ACE_OWNER;
acep->a_who = -1;
break;
case ACE4_SPECIAL_GROUP:
acep->a_flags |= (ACE_GROUP | ACE_IDENTIFIER_GROUP);
acep->a_who = -1;
break;
case ACE4_SPECIAL_EVERYONE:
acep->a_flags |= ACE_EVERYONE;
acep->a_who = -1;
break;
default:
dprintf("Unknown id 0x%08x\n", nacep->who);
return (-EINVAL);
}
} else {
acep->a_who = nacep->who;
}
return (0);
}
static int
nfsacl41i_to_zfsacl(const nfsacl41i *nacl, vsecattr_t *_vsecp)
{
int i, error = 0;
vsecattr_t vsecp;
vsecp.vsa_aclcnt = nacl->na41_aces.na41_aces_len;
vsecp.vsa_aclflags = nacl->na41_flag;
vsecp.vsa_aclentsz = vsecp.vsa_aclcnt * sizeof (ace_t);
vsecp.vsa_mask = (VSA_ACE | VSA_ACE_ACLFLAGS);
vsecp.vsa_aclentp = kmem_alloc(vsecp.vsa_aclentsz, KM_SLEEP);
for (i = 0; i < vsecp.vsa_aclcnt; i++) {
ace_t *acep = vsecp.vsa_aclentp + (i * sizeof (ace_t));
nfsace4i *nacep = &nacl->na41_aces.na41_aces_val[i];
error = nfsace4i_to_acep(nacep, acep);
if (error) {
return (error);
}
}
*_vsecp = vsecp;
return (error);
}
static int
__zpl_xattr_nfs41acl_get(struct inode *ip, const char *name,
void *buffer, size_t size)
{
vsecattr_t vsecp;
cred_t *cr = CRED();
int ret, fl;
size_t acl_size = 0, xdr_size = 0;
XDR xdr = {0};
boolean_t ok;
nfsacl41i *nacl = NULL;
/* xattr_resolve_name will do this for us if this is defined */
#ifndef HAVE_XATTR_HANDLER_NAME
if (strcmp(name, "") != 0)
return (-EINVAL);
#endif
if (ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4)
return (-EOPNOTSUPP);
if (size == 0) {
/*
* API user may send 0 size so that we
* return size of buffer needed for ACL.
*/
crhold(cr);
vsecp.vsa_mask = VSA_ACECNT;
ret = -zfs_getsecattr(ITOZ(ip), &vsecp, ATTR_NOACLCHECK, cr);
if (ret) {
return (ret);
}
crfree(cr);
ret = ACES_TO_XDRSIZE(vsecp.vsa_aclcnt);
return (ret);
}
if (size < ACES_TO_XDRSIZE(1)) {
return (-EINVAL);
}
vsecp.vsa_mask = VSA_ACE_ALLTYPES | VSA_ACECNT | VSA_ACE |
VSA_ACE_ACLFLAGS;
crhold(cr);
fl = capable(CAP_DAC_OVERRIDE) ? ATTR_NOACLCHECK : 0;
ret = -zfs_getsecattr(ITOZ(ip), &vsecp, fl, cr);
crfree(cr);
if (ret) {
return (ret);
}
if (vsecp.vsa_aclcnt == 0) {
ret = -ENODATA;
goto nfs4acl_get_out;
}
xdr_size = ACES_TO_XDRSIZE(vsecp.vsa_aclcnt);
if (xdr_size > size) {
ret = -ERANGE;
goto nfs4acl_get_out;
}
ret = zfsacl_to_nfsacl41i(vsecp, &nacl, &acl_size);
if (ret) {
ret = -ENOMEM;
goto nfs4acl_get_out;
}
xdrmem_create(&xdr, (char *)buffer, xdr_size, XDR_ENCODE);
ok = xdr_nfsacl41i(&xdr, nacl);
if (!ok) {
ret = -ENOMEM;
kmem_free(nacl, acl_size);
goto nfs4acl_get_out;
}
kmem_free(nacl, acl_size);
ret = xdr_size;
nfs4acl_get_out:
kmem_free(vsecp.vsa_aclentp, vsecp.vsa_aclentsz);
return (ret);
}
ZPL_XATTR_GET_WRAPPER(zpl_xattr_nfs41acl_get);
static int
__zpl_xattr_nfs41acl_set(struct inode *ip, const char *name,
const void *value, size_t size, int flags)
{
cred_t *cr = CRED();
vsecattr_t vsecp;
char *bufp = NULL;
nfsacl41i *nacl = NULL;
boolean_t ok;
XDR xdr = {0};
size_t acl_size = 0;
int error, fl, naces;
if (ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4)
return (-EOPNOTSUPP);
/*
* TODO: we may receive NULL value and size 0
* when rmxattr() on our special xattr is called.
* A function to "strip" the ACL needs to be added
* to avoid POLA violation.
*/
/* xdr data is 4-byte aligned */
if (((ulong_t)value % 4) != 0) {
return (-EINVAL);
}
naces = XDRSIZE_TO_ACES(size);
if (naces > NFS41ACL_MAX_ACES) {
return (-E2BIG);
}
if (!XDRSIZE_IS_VALID(size)) {
return (-EINVAL);
}
bufp = (char *)value;
acl_size = ACES_TO_ACLSIZE(naces);
nacl = kmem_alloc(sizeof (nfsacl41i), KM_SLEEP);
/*
* NULL may still be returned with KM_SLEEP set.
* In principal, checks for SIZE of xattr are
* sufficient to protect, but check for NULL anyway.
*/
if (nacl == NULL) {
return (-ENOMEM);
}
xdrmem_create(&xdr, bufp, acl_size, XDR_DECODE);
ok = xdr_nfsacl41i(&xdr, nacl);
if (!ok) {
kmem_free(nacl, sizeof (nfsacl41i));
return (-ENOMEM);
}
error = nfsacl41i_to_zfsacl(nacl, &vsecp);
if (error) {
kmem_free(nacl, sizeof (nfsacl41i));
return (error);
}
/* XDR_DECODE allocates memory for the array of aces */
kmem_free(nacl->na41_aces.na41_aces_val,
(nacl->na41_aces.na41_aces_len * ACE4SIZE));
kmem_free(nacl, sizeof (nfsacl41i));
crhold(cr);
fl = capable(CAP_DAC_OVERRIDE) ? ATTR_NOACLCHECK : 0;
error = -zfs_setsecattr(ITOZ(ip), &vsecp, fl, cr);
crfree(cr);
kmem_free(vsecp.vsa_aclentp, vsecp.vsa_aclentsz);
return (error);
}
ZPL_XATTR_SET_WRAPPER(zpl_xattr_nfs41acl_set);
/*
* ACL access xattr namespace handlers.
*
* Use .name instead of .prefix when available. xattr_resolve_name will match
* whole name and reject anything that has .name only as prefix.
*/
xattr_handler_t zpl_xattr_nfs41acl_handler =
{
#ifdef HAVE_XATTR_HANDLER_NAME
.name = NFS41ACL_XATTR,
#else
.prefix = NFS41ACL_XATTR,
#endif
.list = zpl_xattr_nfs41acl_list,
.get = zpl_xattr_nfs41acl_get,
.set = zpl_xattr_nfs41acl_set,
};
xattr_handler_t *zpl_xattr_handlers[] = {
&zpl_xattr_security_handler,
&zpl_xattr_trusted_handler,
@ -1465,6 +1931,7 @@ xattr_handler_t *zpl_xattr_handlers[] = {
&zpl_xattr_acl_access_handler,
&zpl_xattr_acl_default_handler,
#endif /* CONFIG_FS_POSIX_ACL */
&zpl_xattr_nfs41acl_handler,
NULL
};
@ -1493,6 +1960,10 @@ zpl_xattr_handler(const char *name)
return (&zpl_xattr_acl_default_handler);
#endif /* CONFIG_FS_POSIX_ACL */
if (strncmp(name, NFS41ACL_XATTR,
sizeof (NFS41ACL_XATTR)) == 0)
return (&zpl_xattr_nfs41acl_handler);
return (NULL);
}