Re: [PATCH 11/14] nfsd: add user xattr RPC XDR encoding/decoding logic

Chuck Lever
2020-03-20T17:35:02Z

> On Mar 20, 2020, at 12:47 PM, Frank van der Linden <fllinden@amazon.com> wrote:
>
> Hi Chuck,
>
> On Thu, Mar 12, 2020 at 03:16:37PM -0400, Chuck Lever wrote:
>>> +static __be32
>>> +nfsd4_decode_setxattr(struct nfsd4_compoundargs *argp,
>>> + struct nfsd4_setxattr *setxattr)
>>> +{
>>> + DECODE_HEAD;
>>> + u32 flags, maxcount, size;
>>> + struct kvec head;
>>> + struct page **pagelist;
>>> +
>>> + READ_BUF(4);
>>> + flags = be32_to_cpup(p++);
>>> +
>>> + if (flags > SETXATTR4_REPLACE)
>>> + return nfserr_inval;
>>> + setxattr->setxa_flags = flags;
>>> +
>>> + status = nfsd4_decode_xattr_name(argp, &setxattr->setxa_name);
>>> + if (status)
>>> + return status;
>>> +
>>> + maxcount = svc_max_payload(argp->rqstp);
>>> + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount);
>>> +
>>> + READ_BUF(4);
>>> + size = be32_to_cpup(p++);
>>> + if (size > maxcount)
>>> + return nfserr_xattr2big;
>>> +
>>> + setxattr->setxa_len = size;
>>> + if (size > 0) {
>>> + status = svcxdr_construct_vector(argp, &head, &pagelist, size);
>>> + if (status)
>>> + return status;
>>> +
>>> + status = nfsd4_vbuf_from_stream(argp, &head, pagelist,
>>> + &setxattr->setxa_buf, size);
>>> + }
>>
>> Now I'm wondering if read_bytes_from_xdr_buf() might be adequate
>> for this purpose, so you can avoid open-coding all of this logic.
>
> This took a little longer, I had to check my notes,

Thanks for checking!

> but basically the reasons for doing it this way are:
>
> * The nfsd decode path uses nfsd4_compoundargs, which doesn't have an
> xdr_stream (it has a page array). So read_bytes_from_xdr_buf isn't
> a natural fit.
> * READ_BUF/read_buf don't deal with > PAGE_SIZE chunks, but xattrs may
> be larger than that.
>
> The other code that deals with > PAGE_SIZE chunks is the write code. So,
> I factored out some code from the write code, used that, and added a function
> to process the resulting kvec / pagelist (nfs4_vbuf_from_stream).

Yes, I only recently discovered that the xdr_stream helpers do not
work for decoding data items that are page size or larger.

For the record, my IMA prototype takes this approach for decode_fattr:

+ if (bmval[2] & FATTR4_WORD2_IMA) {
+ READ_BUF(4);
+ len += 4;
+ dummy32 = be32_to_cpup(p++);
+ READ_BUF(dummy32);
+ if (dummy32 > NFS4_MAXIMALEN)
+ return nfserr_badlabel;
+ *ima = svcxdr_tmpalloc(argp, sizeof(**ima));
+ if (*ima == NULL)
+ return nfserr_jukebox;
+
+ len += (XDR_QUADLEN(dummy32) << 2);
+ READMEM(buf, dummy32);
+ (*ima)->len = dummy32;
+ (*ima)->data = svcxdr_dupstr(argp, buf, dummy32);
+ if ((*ima)->data == NULL)
+ return nfserr_jukebox;
+ } else
+ ima = NULL;

Although, an IMA blob is never larger than a page, IIRC.

> There definitely seem to be several copy functions in both the client
> and server code that basically do the same, but in slightly different ways,
> depending on whether they use an XDR buf or not, whether the pages are
> mapped or not, etc. Seems like a good candidate for a cleanup, but I
> considered it to be out of scope for these patches.

"out of scope" - probably so. Depending on Bruce's thinking, we can
add this to the list of janitorial tasks.

For my peace of mind, "from_stream" implies there _is_ an xdr_stream
in use, even though the function does not have a struct xdr_stream
parameter. Perhaps a different naming scheme would be wise.

--
Chuck Lever