NFSD: Add support for POSIX draft ACLs for file creation

NFSv4.2 clients can specify POSIX draft ACLs when creating file
objects via OPEN(CREATE) and CREATE operations. The previous patch
added POSIX ACL support to the NFSv4 SETATTR operation for modifying
existing objects, but file creation follows different code paths
that also require POSIX ACL handling.

This patch integrates POSIX ACL support into nfsd4_create() and
nfsd4_create_file(). Ownership of the decoded ACL pointers
(op_dpacl, op_pacl, cr_dpacl, cr_pacl) transfers to the nfsd_attrs
structure immediately, with the original fields cleared to NULL.
This transfer ensures nfsd_attrs_free() releases the ACLs upon
completion while preventing double-free on error paths.

Mutual exclusion between NFSv4 ACLs and POSIX ACLs is enforced:
setting both op_acl and op_dpacl/op_pacl simultaneously returns
nfserr_inval. Errors during ACL application clear the corresponding
bits in the result bitmask (fattr->bmval), signaling partial
completion to the client.

Signed-off-by: Rick Macklem <rmacklem@uoguelph.ca>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Rick Macklem 2026-01-09 11:21:40 -05:00 committed by Chuck Lever
parent 5fc51dfc2e
commit d2ca50606f

View File

@ -91,6 +91,10 @@ check_attr_support(struct nfsd4_compound_state *cstate, u32 *bmval,
return nfserr_attrnotsupp; return nfserr_attrnotsupp;
if ((bmval[0] & FATTR4_WORD0_ACL) && !IS_POSIXACL(d_inode(dentry))) if ((bmval[0] & FATTR4_WORD0_ACL) && !IS_POSIXACL(d_inode(dentry)))
return nfserr_attrnotsupp; return nfserr_attrnotsupp;
if ((bmval[2] & (FATTR4_WORD2_POSIX_DEFAULT_ACL |
FATTR4_WORD2_POSIX_ACCESS_ACL)) &&
!IS_POSIXACL(d_inode(dentry)))
return nfserr_attrnotsupp;
if ((bmval[2] & FATTR4_WORD2_SECURITY_LABEL) && if ((bmval[2] & FATTR4_WORD2_SECURITY_LABEL) &&
!(exp->ex_flags & NFSEXP_SECURITY_LABEL)) !(exp->ex_flags & NFSEXP_SECURITY_LABEL))
return nfserr_attrnotsupp; return nfserr_attrnotsupp;
@ -265,8 +269,20 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (host_err) if (host_err)
return nfserrno(host_err); return nfserrno(host_err);
if (open->op_acl) {
if (open->op_dpacl || open->op_pacl) {
status = nfserr_inval;
goto out_write;
}
if (is_create_with_attrs(open)) if (is_create_with_attrs(open))
nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs); nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs);
} else if (is_create_with_attrs(open)) {
/* The dpacl and pacl will get released by nfsd_attrs_free(). */
attrs.na_dpacl = open->op_dpacl;
attrs.na_pacl = open->op_pacl;
open->op_dpacl = NULL;
open->op_pacl = NULL;
}
child = start_creating(&nop_mnt_idmap, parent, child = start_creating(&nop_mnt_idmap, parent,
&QSTR_LEN(open->op_fname, open->op_fnamelen)); &QSTR_LEN(open->op_fname, open->op_fnamelen));
@ -379,6 +395,10 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
open->op_bmval[2] &= ~FATTR4_WORD2_SECURITY_LABEL; open->op_bmval[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
if (attrs.na_paclerr || attrs.na_dpaclerr) if (attrs.na_paclerr || attrs.na_dpaclerr)
open->op_bmval[0] &= ~FATTR4_WORD0_ACL; open->op_bmval[0] &= ~FATTR4_WORD0_ACL;
if (attrs.na_dpaclerr)
open->op_bmval[2] &= ~FATTR4_WORD2_POSIX_DEFAULT_ACL;
if (attrs.na_paclerr)
open->op_bmval[2] &= ~FATTR4_WORD2_POSIX_ACCESS_ACL;
out: out:
end_creating(child); end_creating(child);
nfsd_attrs_free(&attrs); nfsd_attrs_free(&attrs);
@ -546,8 +566,10 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
open->op_rqstp = rqstp; open->op_rqstp = rqstp;
/* This check required by spec. */ /* This check required by spec. */
if (open->op_create && open->op_claim_type != NFS4_OPEN_CLAIM_NULL) if (open->op_create && open->op_claim_type != NFS4_OPEN_CLAIM_NULL) {
return nfserr_inval; status = nfserr_inval;
goto out_err;
}
open->op_created = false; open->op_created = false;
/* /*
@ -556,8 +578,10 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
*/ */
if (nfsd4_has_session(cstate) && if (nfsd4_has_session(cstate) &&
!test_bit(NFSD4_CLIENT_RECLAIM_COMPLETE, &cstate->clp->cl_flags) && !test_bit(NFSD4_CLIENT_RECLAIM_COMPLETE, &cstate->clp->cl_flags) &&
open->op_claim_type != NFS4_OPEN_CLAIM_PREVIOUS) open->op_claim_type != NFS4_OPEN_CLAIM_PREVIOUS) {
return nfserr_grace; status = nfserr_grace;
goto out_err;
}
if (nfsd4_has_session(cstate)) if (nfsd4_has_session(cstate))
copy_clientid(&open->op_clientid, cstate->session); copy_clientid(&open->op_clientid, cstate->session);
@ -644,6 +668,9 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
} }
nfsd4_cleanup_open_state(cstate, open); nfsd4_cleanup_open_state(cstate, open);
nfsd4_bump_seqid(cstate, status); nfsd4_bump_seqid(cstate, status);
out_err:
posix_acl_release(open->op_dpacl);
posix_acl_release(open->op_pacl);
return status; return status;
} }
@ -784,22 +811,34 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd_attrs attrs = { struct nfsd_attrs attrs = {
.na_iattr = &create->cr_iattr, .na_iattr = &create->cr_iattr,
.na_seclabel = &create->cr_label, .na_seclabel = &create->cr_label,
.na_dpacl = create->cr_dpacl,
.na_pacl = create->cr_pacl,
}; };
struct svc_fh resfh; struct svc_fh resfh;
__be32 status; __be32 status;
dev_t rdev; dev_t rdev;
create->cr_dpacl = NULL;
create->cr_pacl = NULL;
fh_init(&resfh, NFS4_FHSIZE); fh_init(&resfh, NFS4_FHSIZE);
status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR, NFSD_MAY_NOP); status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR, NFSD_MAY_NOP);
if (status) if (status)
return status; goto out_aftermask;
status = check_attr_support(cstate, create->cr_bmval, nfsd_attrmask); status = check_attr_support(cstate, create->cr_bmval, nfsd_attrmask);
if (status) if (status)
return status; goto out_aftermask;
status = nfsd4_acl_to_attr(create->cr_type, create->cr_acl, &attrs); if (create->cr_acl) {
if (create->cr_dpacl || create->cr_pacl) {
status = nfserr_inval;
goto out_aftermask;
}
status = nfsd4_acl_to_attr(create->cr_type, create->cr_acl,
&attrs);
}
current->fs->umask = create->cr_umask; current->fs->umask = create->cr_umask;
switch (create->cr_type) { switch (create->cr_type) {
case NF4LNK: case NF4LNK:
@ -860,12 +899,17 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
create->cr_bmval[2] &= ~FATTR4_WORD2_SECURITY_LABEL; create->cr_bmval[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
if (attrs.na_paclerr || attrs.na_dpaclerr) if (attrs.na_paclerr || attrs.na_dpaclerr)
create->cr_bmval[0] &= ~FATTR4_WORD0_ACL; create->cr_bmval[0] &= ~FATTR4_WORD0_ACL;
if (attrs.na_dpaclerr)
create->cr_bmval[2] &= ~FATTR4_WORD2_POSIX_DEFAULT_ACL;
if (attrs.na_paclerr)
create->cr_bmval[2] &= ~FATTR4_WORD2_POSIX_ACCESS_ACL;
set_change_info(&create->cr_cinfo, &cstate->current_fh); set_change_info(&create->cr_cinfo, &cstate->current_fh);
fh_dup2(&cstate->current_fh, &resfh); fh_dup2(&cstate->current_fh, &resfh);
out: out:
fh_put(&resfh); fh_put(&resfh);
out_umask: out_umask:
current->fs->umask = 0; current->fs->umask = 0;
out_aftermask:
nfsd_attrs_free(&attrs); nfsd_attrs_free(&attrs);
return status; return status;
} }