nfsd: implement the xattr functions and en/decode logic

Implement the main entry points for the *XATTR operations.

Add functions to calculate the reply size for the user extended attribute
operations, and implement the XDR encode / decode logic for these
operations.

Add the user extended attributes operations to nfsd4_ops.

Signed-off-by: Frank van der Linden <fllinden@amazon.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Frank van der Linden
2020-06-23 22:39:26 +00:00
committed by Chuck Lever
parent 6178713bd4
commit 23e50fe3a5
3 changed files with 571 additions and 1 deletions

View File

@@ -41,6 +41,8 @@
#include <linux/pagemap.h>
#include <linux/sunrpc/svcauth_gss.h>
#include <linux/sunrpc/addr.h>
#include <linux/xattr.h>
#include <uapi/linux/xattr.h>
#include "idmap.h"
#include "acl.h"
@@ -1877,6 +1879,208 @@ 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 data into buffer. Uses head and pages constructed by
* svcxdr_construct_vector.
*/
static __be32
nfsd4_vbuf_from_vector(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 __be32
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. nfsd_getxattr will probe the length of the xattr,
* check it against getxa_len, and allocate + return the value.
*/
static __be32
nfsd4_decode_getxattr(struct nfsd4_compoundargs *argp,
struct nfsd4_getxattr *getxattr)
{
__be32 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_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_vector(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;
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)
{
@@ -1973,6 +2177,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 +4667,241 @@ 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, err;
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;
err = nfsd4_vbuf_to_stream(xdr, getxattr->getxa_buf,
getxattr->getxa_len);
kvfree(getxattr->getxa_buf);
return err;
}
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 __be32
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;
__be32 status;
__be32 *p;
u32 nuser;
eof = 1;
status = nfsd4_listxattr_validate_cookie(listxattrs, &offset);
if (status)
goto out;
/*
* 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) {
status = nfserr_resource;
goto out;
}
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.
*/
status = nfserr_toosmall;
goto out;
}
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) {
status = nfserr_resource;
goto out;
}
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) {
status = nfserr_badcookie;
goto out;
}
wreof:
p = xdr_reserve_space(xdr, 4);
if (!p) {
status = nfserr_resource;
goto out;
}
*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);
out:
if (listxattrs->lsxa_len)
kvfree(listxattrs->lsxa_buf);
return status;
}
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 +4991,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,
};
/*