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

Chuck Lever
2020-03-12T16:24:33Z

> On Mar 11, 2020, at 3:59 PM, Frank van der Linden <fllinden@amazon.com> wrote:
>
> Add functions to calculate the reply size for the user extended attribute
> operations, and implement the XDR encode / decode logic for these
> operations.
>
> Signed-off-by: Frank van der Linden <fllinden@amazon.com>
> ---
> fs/nfsd/nfs4proc.c | 36 +++++
> fs/nfsd/nfs4xdr.c | 448 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 484 insertions(+)
>
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index a76b9025a357..44d488bdebd9 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -2778,6 +2778,42 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
> return (op_encode_hdr_size + 3) * sizeof(__be32);
> }
>
> +static inline u32 nfsd4_getxattr_rsize(struct svc_rqst *rqstp,
> + struct nfsd4_op *op)
> +{
> + u32 maxcount, rlen;
> +
> + maxcount = svc_max_payload(rqstp);
> + rlen = min_t(u32, XATTR_SIZE_MAX, maxcount);
> +
> + return (op_encode_hdr_size + 1 + XDR_QUADLEN(rlen)) * sizeof(__be32);

These should be added in the same patch that adds OP_GETXATTR and friends.

Also, Trond recently added xdr_align_size which I prefer over the
use of XDR_QUADLEN in new code.

> +}
> +
> +static inline u32 nfsd4_setxattr_rsize(struct svc_rqst *rqstp,
> + struct nfsd4_op *op)
> +{
> + return (op_encode_hdr_size + op_encode_change_info_maxsz)
> + * sizeof(__be32);
> +}
> +static inline u32 nfsd4_listxattrs_rsize(struct svc_rqst *rqstp,
> + struct nfsd4_op *op)
> +{
> + u32 maxcount, rlen;
> +
> + maxcount = svc_max_payload(rqstp);
> + rlen = min(op->u.listxattrs.lsxa_maxcount, maxcount);
> +
> + return (op_encode_hdr_size + 4 + XDR_QUADLEN(rlen)) * sizeof(__be32);
> +}
> +
> +static inline u32 nfsd4_removexattr_rsize(struct svc_rqst *rqstp,
> + struct nfsd4_op *op)
> +{
> + return (op_encode_hdr_size + op_encode_change_info_maxsz)
> + * sizeof(__be32);
> +}
> +
> +
> static const struct nfsd4_operation nfsd4_ops[LAST_NFS4_OP + 1] = {
> [OP_ACCESS] = {
> .op_func = nfsd4_access,
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index b12d7ac6f52c..41c8b95ca1c5 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -1879,6 +1879,224 @@ nfsd4_decode_seek(struct nfsd4_compoundargs *argp, struct nfsd4_seek *seek)
> DECODE_TAIL;
> }
>
> +/*
> + * XDR data that is more than PAGE_SIZE in size is normally part of a
> + * read or write. However, the size of extended attributes is limited
> + * by the maximum request size, and then further limited by the underlying
> + * filesystem limits. This can exceed PAGE_SIZE (currently, XATTR_SIZE_MAX
> + * is 64k). Since there is no kvec- or page-based interface to xattrs,
> + * and we're not dealing with contiguous pages, we need to do some copying.
> + */
> +
> +/*
> + * Decode int to buffer. Uses head and pages constructed by
> + * svcxdr_construct_vector.
> + */
> +static int
> +nfsd4_vbuf_from_stream(struct nfsd4_compoundargs *argp, struct kvec *head,
> + struct page **pages, char **bufp, u32 buflen)
> +{
> + char *tmp, *dp;
> + u32 len;
> +
> + if (buflen <= head->iov_len) {
> + /*
> + * We're in luck, the head has enough space. Just return
> + * the head, no need for copying.
> + */
> + *bufp = head->iov_base;
> + return 0;
> + }
> +
> + tmp = svcxdr_tmpalloc(argp, buflen);
> + if (tmp == NULL)
> + return nfserr_jukebox;
> +
> + dp = tmp;
> + memcpy(dp, head->iov_base, head->iov_len);
> + buflen -= head->iov_len;
> + dp += head->iov_len;
> +
> + while (buflen > 0) {
> + len = min_t(u32, buflen, PAGE_SIZE);
> + memcpy(dp, page_address(*pages), len);
> +
> + buflen -= len;
> + dp += len;
> + pages++;
> + }
> +
> + *bufp = tmp;
> + return 0;
> +}
> +
> +/*
> + * Get a user extended attribute name from the XDR buffer.
> + * It will not have the "user." prefix, so prepend it.
> + * Lastly, check for nul characters in the name.
> + */
> +static int
> +nfsd4_decode_xattr_name(struct nfsd4_compoundargs *argp, char **namep)
> +{
> + DECODE_HEAD;
> + char *name, *sp, *dp;
> + u32 namelen, cnt;
> +
> + READ_BUF(4);
> + namelen = be32_to_cpup(p++);
> +
> + if (namelen > (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN))
> + return nfserr_nametoolong;
> +
> + if (namelen == 0)
> + goto xdr_error;
> +
> + READ_BUF(namelen);
> +
> + name = svcxdr_tmpalloc(argp, namelen + XATTR_USER_PREFIX_LEN + 1);
> + if (!name)
> + return nfserr_jukebox;
> +
> + memcpy(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
> +
> + /*
> + * Copy the extended attribute name over while checking for 0
> + * characters.
> + */
> + sp = (char *)p;
> + dp = name + XATTR_USER_PREFIX_LEN;
> + cnt = namelen;
> +
> + while (cnt-- > 0) {
> + if (*sp == '\0')
> + goto xdr_error;
> + *dp++ = *sp++;
> + }
> + *dp = '\0';
> +
> + *namep = name;
> +
> + DECODE_TAIL;
> +}
> +
> +/*
> + * A GETXATTR op request comes without a length specifier. We just set the
> + * maximum length for the reply based on XATTR_SIZE_MAX and the maximum
> + * channel reply size, allocate a buffer of that length and pass it to
> + * vfs_getxattr.
> + */
> +static __be32
> +nfsd4_decode_getxattr(struct nfsd4_compoundargs *argp,
> + struct nfsd4_getxattr *getxattr)
> +{
> + int status;
> + u32 maxcount;
> +
> + status = nfsd4_decode_xattr_name(argp, &getxattr->getxa_name);
> + if (status)
> + return status;
> +
> + maxcount = svc_max_payload(argp->rqstp);
> + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount);
> +
> + getxattr->getxa_buf = svcxdr_tmpalloc(argp, maxcount);
> + if (!getxattr->getxa_buf)
> + status = nfserr_jukebox;
> + getxattr->getxa_len = maxcount;
> +
> + return status;
> +}
> +
> +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);
> + }
> +
> + DECODE_TAIL;
> +}
> +
> +static __be32
> +nfsd4_decode_listxattrs(struct nfsd4_compoundargs *argp,
> + struct nfsd4_listxattrs *listxattrs)
> +{
> + DECODE_HEAD;
> + u32 maxcount;
> +
> + READ_BUF(12);
> + p = xdr_decode_hyper(p, &listxattrs->lsxa_cookie);
> +
> + /*
> + * If the cookie is too large to have even one user.x attribute
> + * plus trailing '\0' left in a maximum size buffer, it's invalid.
> + */
> + if (listxattrs->lsxa_cookie >=
> + (XATTR_LIST_MAX / (XATTR_USER_PREFIX_LEN + 2)))
> + return nfserr_badcookie;
> +
> + maxcount = be32_to_cpup(p++);
> + if (maxcount < 8)
> + /* Always need at least 2 words (length and one character) */
> + return nfserr_inval;
> +
> + maxcount = min(maxcount, svc_max_payload(argp->rqstp));
> + listxattrs->lsxa_maxcount = maxcount;
> +
> + /*
> + * Unfortunately, there is no interface to only list xattrs for
> + * one prefix. So there is no good way to convert maxcount to
> + * a maximum value to pass to vfs_listxattr, as we don't know
> + * how many of the returned attributes will be user attributes.
> + *
> + * So, always ask vfs_listxattr for the maximum size, and encode
> + * as many as possible.
> + */

Well, this approach worries me a little bit. Wouldn't it be better if the
VFS provided the APIs? Review by linux-fsdevel might help here.

> + listxattrs->lsxa_buf = svcxdr_tmpalloc(argp, XATTR_LIST_MAX);
> + if (!listxattrs->lsxa_buf)
> + status = nfserr_jukebox;
> +
> + DECODE_TAIL;
> +}
> +
> +static __be32
> +nfsd4_decode_removexattr(struct nfsd4_compoundargs *argp,
> + struct nfsd4_removexattr *removexattr)
> +{
> + return nfsd4_decode_xattr_name(argp, &removexattr->rmxa_name);
> +}
> +
> static __be32
> nfsd4_decode_noop(struct nfsd4_compoundargs *argp, void *p)
> {
> @@ -1975,6 +2193,11 @@ static const nfsd4_dec nfsd4_dec_ops[] = {
> [OP_SEEK] = (nfsd4_dec)nfsd4_decode_seek,
> [OP_WRITE_SAME] = (nfsd4_dec)nfsd4_decode_notsupp,
> [OP_CLONE] = (nfsd4_dec)nfsd4_decode_clone,
> + /* RFC 8276 extended atributes operations */
> + [OP_GETXATTR] = (nfsd4_dec)nfsd4_decode_getxattr,
> + [OP_SETXATTR] = (nfsd4_dec)nfsd4_decode_setxattr,
> + [OP_LISTXATTRS] = (nfsd4_dec)nfsd4_decode_listxattrs,
> + [OP_REMOVEXATTR] = (nfsd4_dec)nfsd4_decode_removexattr,
> };
>
> static inline bool
> @@ -4458,6 +4681,225 @@ nfsd4_encode_noop(struct nfsd4_compoundres *resp, __be32 nfserr, void *p)
> return nfserr;
> }
>
> +/*
> + * Encode kmalloc-ed buffer in to XDR stream.
> + */
> +static int
> +nfsd4_vbuf_to_stream(struct xdr_stream *xdr, char *buf, u32 buflen)
> +{
> + u32 cplen;
> + __be32 *p;
> +
> + cplen = min_t(unsigned long, buflen,
> + ((void *)xdr->end - (void *)xdr->p));
> + p = xdr_reserve_space(xdr, cplen);
> + if (!p)
> + return nfserr_resource;
> +
> + memcpy(p, buf, cplen);
> + buf += cplen;
> + buflen -= cplen;
> +
> + while (buflen) {
> + cplen = min_t(u32, buflen, PAGE_SIZE);
> + p = xdr_reserve_space(xdr, cplen);
> + if (!p)
> + return nfserr_resource;
> +
> + memcpy(p, buf, cplen);
> +
> + if (cplen < PAGE_SIZE) {
> + /*
> + * We're done, with a length that wasn't page
> + * aligned, so possibly not word aligned. Pad
> + * any trailing bytes with 0.
> + */
> + xdr_encode_opaque_fixed(p, NULL, cplen);
> + break;
> + }
> +
> + buflen -= PAGE_SIZE;
> + buf += PAGE_SIZE;
> + }
> +
> + return 0;
> +}
> +
> +static __be32
> +nfsd4_encode_getxattr(struct nfsd4_compoundres *resp, __be32 nfserr,
> + struct nfsd4_getxattr *getxattr)
> +{
> + struct xdr_stream *xdr = &resp->xdr;
> + __be32 *p;
> +
> + p = xdr_reserve_space(xdr, 4);
> + if (!p)
> + return nfserr_resource;
> +
> + *p = cpu_to_be32(getxattr->getxa_len);
> +
> + if (getxattr->getxa_len == 0)
> + return 0;
> +
> + return nfsd4_vbuf_to_stream(xdr, getxattr->getxa_buf,
> + getxattr->getxa_len);
> +}
> +
> +static __be32
> +nfsd4_encode_setxattr(struct nfsd4_compoundres *resp, __be32 nfserr,
> + struct nfsd4_setxattr *setxattr)
> +{
> + struct xdr_stream *xdr = &resp->xdr;
> + __be32 *p;
> +
> + p = xdr_reserve_space(xdr, 20);
> + if (!p)
> + return nfserr_resource;
> +
> + encode_cinfo(p, &setxattr->setxa_cinfo);
> +
> + return 0;
> +}
> +
> +/*
> + * See if there are cookie values that can be rejected outright.
> + */
> +static int
> +nfsd4_listxattr_validate_cookie(struct nfsd4_listxattrs *listxattrs,
> + u32 *offsetp)
> +{
> + u64 cookie = listxattrs->lsxa_cookie;
> +
> + /*
> + * If the cookie is larger than the maximum number we can fit
> + * in either the buffer we just got back from vfs_listxattr, or,
> + * XDR-encoded, in the return buffer, it's invalid.
> + */
> + if (cookie > (listxattrs->lsxa_len) / (XATTR_USER_PREFIX_LEN + 2))
> + return nfserr_badcookie;
> +
> + if (cookie > (listxattrs->lsxa_maxcount /
> + (XDR_QUADLEN(XATTR_USER_PREFIX_LEN + 2) + 4)))
> + return nfserr_badcookie;
> +
> + *offsetp = (u32)cookie;
> + return 0;
> +}
> +
> +static __be32
> +nfsd4_encode_listxattrs(struct nfsd4_compoundres *resp, __be32 nfserr,
> + struct nfsd4_listxattrs *listxattrs)
> +{
> + struct xdr_stream *xdr = &resp->xdr;
> + u32 cookie_offset, count_offset, eof;
> + u32 left, xdrleft, slen, count;
> + u32 xdrlen, offset;
> + u64 cookie;
> + char *sp;
> + int status;
> + __be32 *p;
> + u32 nuser;
> +
> + eof = 1;
> +
> + status = nfsd4_listxattr_validate_cookie(listxattrs, &offset);
> + if (status)
> + return status;
> +
> + /*
> + * Reserve space for the cookie and the name array count. Record
> + * the offsets to save them later.
> + */
> + cookie_offset = xdr->buf->len;
> + count_offset = cookie_offset + 8;
> + p = xdr_reserve_space(xdr, 12);
> + if (!p)
> + return nfserr_resource;
> +
> + count = 0;
> + left = listxattrs->lsxa_len;
> + sp = listxattrs->lsxa_buf;
> + nuser = 0;
> +
> + xdrleft = listxattrs->lsxa_maxcount;
> +
> + while (left > 0 && xdrleft > 0) {
> + slen = strlen(sp);
> +
> + /*
> + * Check if this a user. attribute, skip it if not.
> + */
> + if (strncmp(sp, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
> + goto contloop;
> +
> + slen -= XATTR_USER_PREFIX_LEN;
> + xdrlen = 4 + ((slen + 3) & ~3);
> + if (xdrlen > xdrleft) {
> + if (count == 0) {
> + /*
> + * Can't even fit the first attribute name.
> + */
> + return nfserr_toosmall;
> + }
> + eof = 0;
> + goto wreof;
> + }
> +
> + left -= XATTR_USER_PREFIX_LEN;
> + sp += XATTR_USER_PREFIX_LEN;
> + if (nuser++ < offset)
> + goto contloop;
> +
> +
> + p = xdr_reserve_space(xdr, xdrlen);
> + if (!p)
> + return nfserr_resource;
> +
> + p = xdr_encode_opaque(p, sp, slen);
> +
> + xdrleft -= xdrlen;
> + count++;
> +contloop:
> + sp += slen + 1;
> + left -= slen + 1;
> + }
> +
> + /*
> + * If there were user attributes to copy, but we didn't copy
> + * any, the offset was too large (e.g. the cookie was invalid).
> + */
> + if (nuser > 0 && count == 0)
> + return nfserr_badcookie;
> +
> +wreof:
> + p = xdr_reserve_space(xdr, 4);
> + if (!p)
> + return nfserr_resource;
> + *p = cpu_to_be32(eof);
> +
> + cookie = offset + count;
> +
> + write_bytes_to_xdr_buf(xdr->buf, cookie_offset, &cookie, 8);
> + count = htonl(count);
> + write_bytes_to_xdr_buf(xdr->buf, count_offset, &count, 4);
> + return 0;
> +}
> +
> +static __be32
> +nfsd4_encode_removexattr(struct nfsd4_compoundres *resp, __be32 nfserr,
> + struct nfsd4_removexattr *removexattr)
> +{
> + struct xdr_stream *xdr = &resp->xdr;
> + __be32 *p;
> +
> + p = xdr_reserve_space(xdr, 20);
> + if (!p)
> + return nfserr_resource;
> +
> + p = encode_cinfo(p, &removexattr->rmxa_cinfo);
> + return 0;
> +}
> +
> typedef __be32(* nfsd4_enc)(struct nfsd4_compoundres *, __be32, void *);
>
> /*
> @@ -4547,6 +4989,12 @@ static const nfsd4_enc nfsd4_enc_ops[] = {
> [OP_SEEK] = (nfsd4_enc)nfsd4_encode_seek,
> [OP_WRITE_SAME] = (nfsd4_enc)nfsd4_encode_noop,
> [OP_CLONE] = (nfsd4_enc)nfsd4_encode_noop,
> +
> + /* RFC 8276 extended atributes operations */
> + [OP_GETXATTR] = (nfsd4_enc)nfsd4_encode_getxattr,
> + [OP_SETXATTR] = (nfsd4_enc)nfsd4_encode_setxattr,
> + [OP_LISTXATTRS] = (nfsd4_enc)nfsd4_encode_listxattrs,
> + [OP_REMOVEXATTR] = (nfsd4_enc)nfsd4_encode_removexattr,
> };
>
> /*
> --
> 2.16.6
>

--
Chuck Lever