]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
NFSD: Add support for XDR decoding POSIX draft ACLs
authorRick Macklem <rmacklem@uoguelph.ca>
Fri, 9 Jan 2026 16:21:39 +0000 (11:21 -0500)
committerChuck Lever <chuck.lever@oracle.com>
Thu, 29 Jan 2026 14:48:33 +0000 (09:48 -0500)
The POSIX ACL extension to NFSv4 defines FATTR4_POSIX_ACCESS_ACL
and FATTR4_POSIX_DEFAULT_ACL for setting access and default ACLs
via CREATE, OPEN, and SETATTR operations. This patch adds the XDR
decoders for those attributes.

The nfsd4_decode_fattr4() function gains two additional parameters
for receiving decoded POSIX ACLs. CREATE, OPEN, and SETATTR
decoders pass pointers to these new parameters, enabling clients
to set POSIX ACLs during object creation or modification.

Signed-off-by: Rick Macklem <rmacklem@uoguelph.ca>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
fs/nfsd/acl.h
fs/nfsd/nfs4acl.c
fs/nfsd/nfs4xdr.c
fs/nfsd/xdr4.h

index 4b7324458a94e13de4785ac7866932967ffb1b51..2003523d0e65c76ee0f7502496ed919b63bca78f 100644 (file)
@@ -49,5 +49,6 @@ int nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
                struct nfs4_acl **acl);
 __be32 nfsd4_acl_to_attr(enum nfs_ftype4 type, struct nfs4_acl *acl,
                         struct nfsd_attrs *attr);
+void sort_pacl_range(struct posix_acl *pacl, int start, int end);
 
 #endif /* LINUX_NFS4_ACL_H */
index 936ea1ad9586770201b7b0e8729913fe462108ad..2c2f2fd89e87959100e317919c8273be82ec43fd 100644 (file)
@@ -369,12 +369,21 @@ pace_gt(struct posix_acl_entry *pace1, struct posix_acl_entry *pace2)
        return false;
 }
 
-static void
-sort_pacl_range(struct posix_acl *pacl, int start, int end) {
+/**
+ * sort_pacl_range - sort a range of POSIX ACL entries by tag and id
+ * @pacl: POSIX ACL containing entries to sort
+ * @start: starting index of range to sort
+ * @end: ending index of range to sort (inclusive)
+ *
+ * Sorts ACL entries in place so that USER entries are ordered by UID
+ * and GROUP entries are ordered by GID. Required before calling
+ * posix_acl_valid().
+ */
+void sort_pacl_range(struct posix_acl *pacl, int start, int end)
+{
        int sorted = 0, i;
 
-       /* We just do a bubble sort; easy to do in place, and we're not
-        * expecting acl's to be long enough to justify anything more. */
+       /* Bubble sort: acceptable here because ACLs are typically short. */
        while (!sorted) {
                sorted = 1;
                for (i = start; i < end; i++) {
index 358fa014be15ddb2b9cc1dcdbbc540c6d4a73b22..5172dbd0cb05956f8d5465e08fec93b5133ec55f 100644 (file)
@@ -378,10 +378,111 @@ nfsd4_decode_security_label(struct nfsd4_compoundargs *argp,
        return nfs_ok;
 }
 
+#ifdef CONFIG_NFSD_V4_POSIX_ACLS
+
+static short nfsd4_posixacetag4_to_tag(posixacetag4 tag)
+{
+       switch (tag) {
+       case POSIXACE4_TAG_USER_OBJ:    return ACL_USER_OBJ;
+       case POSIXACE4_TAG_GROUP_OBJ:   return ACL_GROUP_OBJ;
+       case POSIXACE4_TAG_USER:        return ACL_USER;
+       case POSIXACE4_TAG_GROUP:       return ACL_GROUP;
+       case POSIXACE4_TAG_MASK:        return ACL_MASK;
+       case POSIXACE4_TAG_OTHER:       return ACL_OTHER;
+       }
+       return ACL_OTHER;
+}
+
+static __be32
+nfsd4_decode_posixace4(struct nfsd4_compoundargs *argp,
+                      struct posix_acl_entry *ace)
+{
+       posixaceperm4 perm;
+       __be32 *p, status;
+       posixacetag4 tag;
+       u32 len;
+
+       if (!xdrgen_decode_posixacetag4(argp->xdr, &tag))
+               return nfserr_bad_xdr;
+       ace->e_tag = nfsd4_posixacetag4_to_tag(tag);
+
+       if (!xdrgen_decode_posixaceperm4(argp->xdr, &perm))
+               return nfserr_bad_xdr;
+       if (perm & ~S_IRWXO)
+               return nfserr_bad_xdr;
+       ace->e_perm = perm;
+
+       if (xdr_stream_decode_u32(argp->xdr, &len) < 0)
+               return nfserr_bad_xdr;
+       p = xdr_inline_decode(argp->xdr, len);
+       if (!p)
+               return nfserr_bad_xdr;
+       switch (tag) {
+       case POSIXACE4_TAG_USER:
+               if (len > 0)
+                       status = nfsd_map_name_to_uid(argp->rqstp,
+                                       (char *)p, len, &ace->e_uid);
+               else
+                       status = nfserr_bad_xdr;
+               break;
+       case POSIXACE4_TAG_GROUP:
+               if (len > 0)
+                       status = nfsd_map_name_to_gid(argp->rqstp,
+                                       (char *)p, len, &ace->e_gid);
+               else
+                       status = nfserr_bad_xdr;
+               break;
+       default:
+               status = nfs_ok;
+       }
+
+       return status;
+}
+
+static noinline __be32
+nfsd4_decode_posixacl(struct nfsd4_compoundargs *argp, struct posix_acl **acl)
+{
+       struct posix_acl_entry *ace;
+       __be32 status;
+       u32 count;
+
+       if (xdr_stream_decode_u32(argp->xdr, &count) < 0)
+               return nfserr_bad_xdr;
+
+       *acl = posix_acl_alloc(count, GFP_KERNEL);
+       if (*acl == NULL)
+               return nfserr_resource;
+
+       (*acl)->a_count = count;
+       for (ace = (*acl)->a_entries; ace < (*acl)->a_entries + count; ace++) {
+               status = nfsd4_decode_posixace4(argp, ace);
+               if (status) {
+                       posix_acl_release(*acl);
+                       *acl = NULL;
+                       return status;
+               }
+       }
+
+       /*
+        * posix_acl_valid() requires the ACEs to be sorted.
+        * If they are already sorted, sort_pacl_range() will return
+        * after one pass through the ACEs, since it implements bubble sort.
+        * Note that a count == 0 is used to delete a POSIX ACL and a count
+        * of 1 or 2 will always be found invalid by posix_acl_valid().
+        */
+       if (count >= 3)
+               sort_pacl_range(*acl, 0, count - 1);
+
+       return nfs_ok;
+}
+
+#endif /* CONFIG_NFSD_V4_POSIX_ACLS */
+
 static __be32
 nfsd4_decode_fattr4(struct nfsd4_compoundargs *argp, u32 *bmval, u32 bmlen,
                    struct iattr *iattr, struct nfs4_acl **acl,
-                   struct xdr_netobj *label, int *umask)
+                   struct xdr_netobj *label, int *umask,
+                   struct posix_acl **dpaclp, struct posix_acl **paclp)
 {
        unsigned int starting_pos;
        u32 attrlist4_count;
@@ -544,9 +645,40 @@ nfsd4_decode_fattr4(struct nfsd4_compoundargs *argp, u32 *bmval, u32 bmlen,
                                   ATTR_MTIME | ATTR_MTIME_SET | ATTR_DELEG;
        }
 
+       *dpaclp = NULL;
+       *paclp = NULL;
+#ifdef CONFIG_NFSD_V4_POSIX_ACLS
+       if (bmval[2] & FATTR4_WORD2_POSIX_DEFAULT_ACL) {
+               struct posix_acl *dpacl;
+
+               status = nfsd4_decode_posixacl(argp, &dpacl);
+               if (status)
+                       return status;
+               *dpaclp = dpacl;
+       }
+       if (bmval[2] & FATTR4_WORD2_POSIX_ACCESS_ACL) {
+               struct posix_acl *pacl;
+
+               status = nfsd4_decode_posixacl(argp, &pacl);
+               if (status) {
+                       posix_acl_release(*dpaclp);
+                       *dpaclp = NULL;
+                       return status;
+               }
+               *paclp = pacl;
+       }
+#endif /* CONFIG_NFSD_V4_POSIX_ACLS */
+
        /* request sanity: did attrlist4 contain the expected number of words? */
-       if (attrlist4_count != xdr_stream_pos(argp->xdr) - starting_pos)
+       if (attrlist4_count != xdr_stream_pos(argp->xdr) - starting_pos) {
+#ifdef CONFIG_NFSD_V4_POSIX_ACLS
+               posix_acl_release(*dpaclp);
+               posix_acl_release(*paclp);
+               *dpaclp = NULL;
+               *paclp = NULL;
+#endif
                return nfserr_bad_xdr;
+       }
 
        return nfs_ok;
 }
@@ -850,7 +982,8 @@ nfsd4_decode_create(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u)
        status = nfsd4_decode_fattr4(argp, create->cr_bmval,
                                    ARRAY_SIZE(create->cr_bmval),
                                    &create->cr_iattr, &create->cr_acl,
-                                   &create->cr_label, &create->cr_umask);
+                                   &create->cr_label, &create->cr_umask,
+                                   &create->cr_dpacl, &create->cr_pacl);
        if (status)
                return status;
 
@@ -1001,7 +1134,8 @@ nfsd4_decode_createhow4(struct nfsd4_compoundargs *argp, struct nfsd4_open *open
                status = nfsd4_decode_fattr4(argp, open->op_bmval,
                                             ARRAY_SIZE(open->op_bmval),
                                             &open->op_iattr, &open->op_acl,
-                                            &open->op_label, &open->op_umask);
+                                            &open->op_label, &open->op_umask,
+                                            &open->op_dpacl, &open->op_pacl);
                if (status)
                        return status;
                break;
@@ -1019,7 +1153,8 @@ nfsd4_decode_createhow4(struct nfsd4_compoundargs *argp, struct nfsd4_open *open
                status = nfsd4_decode_fattr4(argp, open->op_bmval,
                                             ARRAY_SIZE(open->op_bmval),
                                             &open->op_iattr, &open->op_acl,
-                                            &open->op_label, &open->op_umask);
+                                            &open->op_label, &open->op_umask,
+                                            &open->op_dpacl, &open->op_pacl);
                if (status)
                        return status;
                break;
@@ -1346,7 +1481,8 @@ nfsd4_decode_setattr(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u)
        return nfsd4_decode_fattr4(argp, setattr->sa_bmval,
                                   ARRAY_SIZE(setattr->sa_bmval),
                                   &setattr->sa_iattr, &setattr->sa_acl,
-                                  &setattr->sa_label, NULL);
+                                  &setattr->sa_label, NULL, &setattr->sa_dpacl,
+                                  &setattr->sa_pacl);
 }
 
 static __be32
index 1be2814b528866cc34c268816ddabec8a4257d24..417e9ad9fbb3977afbe118379282e29d7baf1cbc 100644 (file)
@@ -245,6 +245,8 @@ struct nfsd4_create {
        int             cr_umask;           /* request */
        struct nfsd4_change_info  cr_cinfo; /* response */
        struct nfs4_acl *cr_acl;
+       struct posix_acl *cr_dpacl;
+       struct posix_acl *cr_pacl;
        struct xdr_netobj cr_label;
 };
 #define cr_datalen     u.link.datalen
@@ -397,6 +399,8 @@ struct nfsd4_open {
        struct nfs4_ol_stateid *op_stp;     /* used during processing */
        struct nfs4_clnt_odstate *op_odstate; /* used during processing */
        struct nfs4_acl *op_acl;
+       struct posix_acl *op_dpacl;
+       struct posix_acl *op_pacl;
        struct xdr_netobj op_label;
        struct svc_rqst *op_rqstp;
 };
@@ -483,6 +487,8 @@ struct nfsd4_setattr {
        struct iattr    sa_iattr;           /* request */
        struct nfs4_acl *sa_acl;
        struct xdr_netobj sa_label;
+       struct posix_acl *sa_dpacl;
+       struct posix_acl *sa_pacl;
 };
 
 struct nfsd4_setclientid {