Add ability for xattr handler to "strip" NFSv4 ACL (#54)

On Linux POSIX ACLs can be removed via rmxattr() for the
relevant system xattrs. On FreeBSD a non-trivial ACL
can be converted to one that is described by the mode with
no loss of info via combination of acl_get_file(), acl_strip_np(),
and acl_set_file(). Since there's no libc equivalent of these
ops in Linux for NFSv4 ACLs, this commit makes this less error
prone by handling entirely in ZFS. When user performs
rmxattr() vfs_setxattr() is called with value of NULL and length
of 0. Add special handling for this situation in the xattr
handler for the NFSv4 ACL so that we generate a new ACL and
zfs_acl_chmod() with the existing mode of file, then set the ACL.

Signed-off-by: Andrew Walker <awalker@ixsystems.com>
This commit is contained in:
Andrew 2022-06-21 13:48:30 -05:00 committed by Ameer Hamza
parent 29d6360344
commit d1df3f6cab
4 changed files with 135 additions and 42 deletions

View File

@ -211,6 +211,8 @@ void zfs_acl_ids_free(zfs_acl_ids_t *);
boolean_t zfs_acl_ids_overquota(struct zfsvfs *, zfs_acl_ids_t *, uint64_t);
int zfs_getacl(struct znode *, vsecattr_t *, boolean_t, cred_t *);
int zfs_setacl(struct znode *, vsecattr_t *, boolean_t, cred_t *);
int zfs_stripacl(struct znode *, cred_t *);
void zfs_acl_rele(void *);
void zfs_oldace_byteswap(ace_t *, int);
void zfs_ace_byteswap(void *, size_t, boolean_t);

View File

@ -2029,6 +2029,12 @@ top:
return (error);
}
int
zfs_stripacl(znode_t *zp, cred_t *cr)
{
return (SET_ERROR(EOPNOTSUPP));
}
/*
* Check accesses of interest (AoI) against attributes of the dataset
* such as read-only. Returns zero if no AoI conflict with dataset

View File

@ -46,6 +46,7 @@
#include <sys/zfs_quota.h>
#include <sys/zfs_vfsops.h>
#include <sys/dmu.h>
#include <sys/dmu_objset.h>
#include <sys/dnode.h>
#include <sys/zap.h>
#include <sys/sa.h>
@ -1963,8 +1964,8 @@ zfs_acl_ids_overquota(zfsvfs_t *zv, zfs_acl_ids_t *acl_ids, uint64_t projid)
/*
* Retrieve a file's ACL
*/
int
zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
static int
zfs_getacl_impl(znode_t *zp, vsecattr_t *vsecp, boolean_t stripped, cred_t *cr)
{
zfs_acl_t *aclp;
ulong_t mask;
@ -1975,21 +1976,21 @@ zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT |
VSA_ACE_ACLFLAGS | VSA_ACE_ALLTYPES);
if (mask == 0)
return (SET_ERROR(ENOSYS));
if ((error = zfs_zaccess(zp, ACE_READ_ACL, 0, skipaclchk, cr,
zfs_init_idmap)))
return (error);
mutex_enter(&zp->z_acl_lock);
error = zfs_acl_node_read(zp, B_FALSE, &aclp, B_FALSE);
if (error != 0) {
mutex_exit(&zp->z_acl_lock);
return (error);
if (stripped) {
mode_t mode = ZTOI(zp)->i_mode;
aclp = zfs_acl_alloc(zfs_acl_version_zp(zp));
(aclp)->z_hints = zp->z_pflags & V4_ACL_WIDE_FLAGS;
zfs_acl_chmod(S_ISDIR(mode), mode, B_TRUE,
(ZTOZSB(zp)->z_acl_mode == ZFS_ACL_GROUPMASK), aclp);
} else {
error = zfs_acl_node_read(zp, B_FALSE, &aclp, B_FALSE);
if (error != 0)
return (error);
}
mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT |
VSA_ACE_ACLFLAGS | VSA_ACE_ALLTYPES);
/*
* Scan ACL to determine number of ACEs
*/
@ -2038,7 +2039,7 @@ zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
for (aclnode = list_head(&aclp->z_acl); aclnode;
aclnode = list_next(&aclp->z_acl, aclnode)) {
memcpy(start, aclnode->z_acldata,
bcopy(aclnode->z_acldata, start,
aclnode->z_size);
start = (caddr_t)start + aclnode->z_size;
}
@ -2060,9 +2061,29 @@ zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
vsecp->vsa_aclflags |= ACL_IS_DIR;
}
return (0);
}
int
zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
{
int error;
ulong_t mask;
mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT |
VSA_ACE_ACLFLAGS | VSA_ACE_ALLTYPES);
if (mask == 0)
return (SET_ERROR(ENOSYS));
if ((error = zfs_zaccess(zp, ACE_READ_ACL, 0, skipaclchk, cr)))
return (error);
mutex_enter(&zp->z_acl_lock);
error = zfs_getacl_impl(zp, vsecp, B_FALSE, cr);
mutex_exit(&zp->z_acl_lock);
return (0);
return (error);
}
int
@ -2123,12 +2144,11 @@ zfs_vsec_2_aclp(zfsvfs_t *zfsvfs, umode_t obj_mode,
/*
* Set a file's ACL
*/
int
zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
static int
zfs_setacl_impl(znode_t *zp, vsecattr_t *vsecp, cred_t *cr)
{
zfsvfs_t *zfsvfs = ZTOZSB(zp);
zilog_t *zilog = zfsvfs->z_log;
ulong_t mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT);
dmu_tx_t *tx;
int error;
zfs_acl_t *aclp;
@ -2136,16 +2156,6 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
boolean_t fuid_dirtied;
uint64_t acl_obj;
if (mask == 0)
return (SET_ERROR(ENOSYS));
if (zp->z_pflags & ZFS_IMMUTABLE)
return (SET_ERROR(EPERM));
if ((error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, skipaclchk, cr,
zfs_init_idmap)))
return (error);
error = zfs_vsec_2_aclp(zfsvfs, ZTOI(zp)->i_mode, vsecp, cr, &fuidp,
&aclp);
if (error)
@ -2160,9 +2170,6 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
(zp->z_pflags & V4_ACL_WIDE_FLAGS);
}
top:
mutex_enter(&zp->z_acl_lock);
mutex_enter(&zp->z_lock);
tx = dmu_tx_create(zfsvfs->z_os);
dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE);
@ -2193,12 +2200,15 @@ top:
zfs_sa_upgrade_txholds(tx, zp);
error = dmu_tx_assign(tx, TXG_NOWAIT);
if (error) {
mutex_exit(&zp->z_acl_lock);
mutex_exit(&zp->z_lock);
if (error == ERESTART) {
mutex_exit(&zp->z_acl_lock);
mutex_exit(&zp->z_lock);
dmu_tx_wait(tx);
dmu_tx_abort(tx);
mutex_enter(&zp->z_acl_lock);
mutex_enter(&zp->z_lock);
goto top;
}
dmu_tx_abort(tx);
@ -2220,9 +2230,84 @@ top:
zfs_fuid_info_free(fuidp);
dmu_tx_commit(tx);
return (error);
}
int
zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr)
{
ulong_t mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT);
int error;
if (mask == 0)
return (SET_ERROR(ENOSYS));
if (zp->z_pflags & ZFS_IMMUTABLE)
return (SET_ERROR(EPERM));
if ((error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, skipaclchk, cr)))
return (error);
mutex_enter(&zp->z_acl_lock);
mutex_enter(&zp->z_lock);
error = zfs_setacl_impl(zp, vsecp, cr);
mutex_exit(&zp->z_lock);
mutex_exit(&zp->z_acl_lock);
return (error);
}
int
zfs_stripacl(znode_t *zp, cred_t *cr)
{
int error;
zfsvfs_t *zfsvfs = ZTOZSB(zp);
vsecattr_t vsec = {
.vsa_mask = VSA_ACE_ALLTYPES | VSA_ACECNT | VSA_ACE |
VSA_ACE_ACLFLAGS
};
ZFS_ENTER(zfsvfs);
ZFS_VERIFY_ZP(zp);
if (zp->z_pflags & ZFS_IMMUTABLE) {
error = SET_ERROR(EPERM);
goto done;
}
if ((error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, B_FALSE, cr)))
goto done;
if (zp->z_pflags & ZFS_ACL_TRIVIAL) {
// ACL is already stripped. Nothing to do.
error = 0;
goto done;
}
mutex_enter(&zp->z_acl_lock);
error = zfs_getacl_impl(zp, &vsec, B_TRUE, cr);
if (error) {
mutex_exit(&zp->z_acl_lock);
goto done;
}
mutex_enter(&zp->z_lock);
error = zfs_setacl_impl(zp, &vsec, cr);
mutex_exit(&zp->z_lock);
mutex_exit(&zp->z_acl_lock);
kmem_free(vsec.vsa_aclentp, vsec.vsa_aclentsz);
if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS)
zil_commit(zfsvfs->z_log, 0);
done:
ZFS_EXIT(zfsvfs);
return (error);
}

View File

@ -1867,12 +1867,12 @@ __zpl_xattr_nfs41acl_set(struct inode *ip, const char *name,
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.
*/
if (value == NULL && size == 0) {
crhold(cr);
error = zfs_stripacl(ITOZ(ip), cr);
crfree(cr);
return (error);
}
/* xdr data is 4-byte aligned */
if (((ulong_t)value % 4) != 0) {