diff --git a/.gitignore b/.gitignore index a2cb92dd54..bd3d4befd2 100644 --- a/.gitignore +++ b/.gitignore @@ -84,6 +84,8 @@ modules.order Makefile Makefile.in changelog +nfs41acl.h +nfs41acl_xdr.c *.patch *.orig *.tmp diff --git a/Makefile.am b/Makefile.am index 11e45dae82..76ec01f2aa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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/*' \ diff --git a/include/os/linux/spl/rpc/xdr.h b/include/os/linux/spl/rpc/xdr.h index b00f3542fc..05aed7cb81 100644 --- a/include/os/linux/spl/rpc/xdr.h +++ b/include/os/linux/spl/rpc/xdr.h @@ -22,6 +22,7 @@ #define _SPL_RPC_XDR_H #include +#include typedef int bool_t; diff --git a/include/os/linux/zfs/sys/zpl.h b/include/os/linux/zfs/sys/zpl.h index 91a4751fff..afea2336ba 100644 --- a/include/os/linux/zfs/sys/zpl.h +++ b/include/os/linux/zfs/sys/zpl.h @@ -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 */ diff --git a/module/Kbuild.in b/module/Kbuild.in index 7e08374fa2..94d6987b0a 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -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 \ diff --git a/module/Makefile.in b/module/Makefile.in index 9b34b3dfae..757dfc088d 100644 --- a/module/Makefile.in +++ b/module/Makefile.in @@ -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 "/\/c\\#include \" "@abs_srcdir@/os/linux/zfs/nfs41acl.h" + sed -i "9i #include " "@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: diff --git a/module/os/linux/zfs/nfs41acl.x b/module/os/linux/zfs/nfs41acl.x new file mode 100644 index 0000000000..1df04e8b8a --- /dev/null +++ b/module/os/linux/zfs/nfs41acl.x @@ -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<>; +}; diff --git a/module/os/linux/zfs/policy.c b/module/os/linux/zfs/policy.c index 5d1b438341..f8a8086046 100644 --- a/module/os/linux/zfs/policy.c +++ b/module/os/linux/zfs/policy.c @@ -33,6 +33,7 @@ #include #include #include +#include /* * 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); } /* diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index 8800a6c220..dcc586362c 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -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; } diff --git a/module/os/linux/zfs/zpl_inode.c b/module/os/linux/zfs/zpl_inode.c index ad1753f7a0..b25f3e9224 100644 --- a/module/os/linux/zfs/zpl_inode.c +++ b/module/os/linux/zfs/zpl_inode.c @@ -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, }; diff --git a/module/os/linux/zfs/zpl_super.c b/module/os/linux/zfs/zpl_super.c index d98d32c1f9..670058bd4e 100644 --- a/module/os/linux/zfs/zpl_super.c +++ b/module/os/linux/zfs/zpl_super.c @@ -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; diff --git a/module/os/linux/zfs/zpl_xattr.c b/module/os/linux/zfs/zpl_xattr.c index 4e4f5210f8..4005258728 100644 --- a/module/os/linux/zfs/zpl_xattr.c +++ b/module/os/linux/zfs/zpl_xattr.c @@ -80,10 +80,34 @@ #include #include #include +#include #include #include #include #include +#include +#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); }