diff --git a/config/kernel-inode-permission.m4 b/config/kernel-inode-permission.m4 new file mode 100644 index 000000000..ba9ff5d43 --- /dev/null +++ b/config/kernel-inode-permission.m4 @@ -0,0 +1,29 @@ +AC_DEFUN([ZFS_AC_KERNEL_SRC_PERMISSION], [ + dnl # + dnl # 5.12 API change that added the struct user_namespace* arg + dnl # to the front of this function type's arg list. + dnl # + ZFS_LINUX_TEST_SRC([permission_userns], [ + #include + #include + + int inode_permission(struct user_namespace *userns, + struct inode *inode, int mask) { return 0; } + + static const struct inode_operations + iops __attribute__ ((unused)) = { + .permission = inode_permission, + }; + ],[]) +]) + +AC_DEFUN([ZFS_AC_KERNEL_PERMISSION], [ + AC_MSG_CHECKING([whether iops->permission() takes struct user_namespace*]) + ZFS_LINUX_TEST_RESULT([permission_userns], [ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_IOPS_PERMISSION_USERNS, 1, + [iops->permission() takes struct user_namespace*]) + ],[ + AC_MSG_RESULT(no) + ]) +]) diff --git a/config/kernel.m4 b/config/kernel.m4 index d1d3dede1..639d18377 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -82,6 +82,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [ ZFS_AC_KERNEL_SRC_MKDIR ZFS_AC_KERNEL_SRC_LOOKUP_FLAGS ZFS_AC_KERNEL_SRC_CREATE + ZFS_AC_KERNEL_SRC_PERMISSION ZFS_AC_KERNEL_SRC_GET_LINK ZFS_AC_KERNEL_SRC_PUT_LINK ZFS_AC_KERNEL_SRC_TMPFILE @@ -193,6 +194,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [ ZFS_AC_KERNEL_MKDIR ZFS_AC_KERNEL_LOOKUP_FLAGS ZFS_AC_KERNEL_CREATE + ZFS_AC_KERNEL_PERMISSION ZFS_AC_KERNEL_GET_LINK ZFS_AC_KERNEL_PUT_LINK ZFS_AC_KERNEL_TMPFILE diff --git a/module/os/linux/zfs/zfs_acl.c b/module/os/linux/zfs/zfs_acl.c index 351e4dad7..b70691ab3 100644 --- a/module/os/linux/zfs/zfs_acl.c +++ b/module/os/linux/zfs/zfs_acl.c @@ -863,6 +863,26 @@ zfs_unix_to_v4(uint32_t access_mask) return (new_mask); } + +static int +zfs_v4_to_unix(uint32_t access_mask, int *unmapped) +{ + int new_mask = 0; + + *unmapped = access_mask & + (ACE_WRITE_OWNER | ACE_WRITE_ACL | ACE_DELETE); + + if (access_mask & WRITE_MASK) + new_mask |= S_IWOTH; + if (access_mask & ACE_READ_DATA) + new_mask |= S_IROTH; + if (access_mask & ACE_EXECUTE) + new_mask |= S_IXOTH; + + return (new_mask); +} + + static void zfs_set_ace(zfs_acl_t *aclp, void *acep, uint32_t access_mask, uint16_t access_type, uint64_t fuid, uint16_t entry_type) @@ -2399,6 +2419,53 @@ zfs_has_access(znode_t *zp, cred_t *cr) return (B_TRUE); } +/* + * Simplified access check for case where ACL is known to not contain + * information beyond what is defined in the mode. In this case, we + * can pass along to the kernel / vfs generic_permission() check, which + * evaluates the mode and POSIX ACL. + * + * NFSv4 ACLs allow granting permissions that are usually relegated only + * to the file owner or superuser. Examples are ACE_WRITE_OWNER (chown), + * ACE_WRITE_ACL(chmod), and ACE_DELETE. ACE_DELETE requests must fail + * because with conventional posix permissions, right to delete file + * is determined by write bit on the parent dir. + * + * If unmappable perms are requested, then we must return EPERM + * and include those bits in the working_mode so that the caller of + * zfs_zaccess_common() can decide whether to perform additional + * policy / capability checks. EACCES is used in zfs_zaccess_aces_check() + * to indicate access check failed due to explicit DENY entry, and so + * we want to avoid that here. + */ +static int +zfs_zaccess_trivial(znode_t *zp, uint32_t *working_mode, cred_t *cr) +{ + int err, mask; + int unmapped = 0; + + ASSERT(zp->z_pflags & ZFS_ACL_TRIVIAL); + + mask = zfs_v4_to_unix(*working_mode, &unmapped); + if (mask == 0 || unmapped) { + *working_mode = unmapped; + return (unmapped ? SET_ERROR(EPERM) : 0); + } + +#if defined(HAVE_IOPS_PERMISSION_USERNS) + err = generic_permission(cr->user_ns, ZTOI(zp), mask); +#else + err = generic_permission(ZTOI(zp), mask); +#endif + if (err != 0) { + return (SET_ERROR(EPERM)); + } + + *working_mode = unmapped; + + return (0); +} + static int zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode, boolean_t *check_privs, boolean_t skipaclchk, cred_t *cr) @@ -2450,6 +2517,9 @@ zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode, return (SET_ERROR(EPERM)); } + if (zp->z_pflags & ZFS_ACL_TRIVIAL) + return (zfs_zaccess_trivial(zp, working_mode, cr)); + return (zfs_zaccess_aces_check(zp, working_mode, B_FALSE, cr)); } diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index ece7c373e..b65728f0d 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -474,7 +474,7 @@ zfs_lookup(znode_t *zdp, char *nm, znode_t **zpp, int flags, cred_t *cr, */ if ((error = zfs_zaccess(*zpp, ACE_EXECUTE, 0, - B_FALSE, cr))) { + B_TRUE, cr))) { zrele(*zpp); *zpp = NULL; }