aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndy Adamson <andros@netapp.com>2011-12-07 11:55:27 -0500
committerTrond Myklebust <Trond.Myklebust@netapp.com>2012-01-05 10:42:42 -0500
commitbf118a342f10dafe44b14451a1392c3254629a1f (patch)
tree50c2800c7c92f8e47e4a0a440e2a97847f678e19
parent3476f114addb7b96912840a234702f660a1f460b (diff)
NFSv4: include bitmap in nfsv4 get acl data
The NFSv4 bitmap size is unbounded: a server can return an arbitrary sized bitmap in an FATTR4_WORD0_ACL request. Replace using the nfs4_fattr_bitmap_maxsz as a guess to the maximum bitmask returned by a server with the inclusion of the bitmap (xdr length plus bitmasks) and the acl data xdr length to the (cached) acl page data. This is a general solution to commit e5012d1f "NFSv4.1: update nfs4_fattr_bitmap_maxsz" and fixes hitting a BUG_ON in xdr_shrink_bufhead when getting ACLs. Fix a bug in decode_getacl that returned -EINVAL on ACLs > page when getxattr was called with a NULL buffer, preventing ACL > PAGE_SIZE from being retrieved. Cc: stable@kernel.org Signed-off-by: Andy Adamson <andros@netapp.com> Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
-rw-r--r--fs/nfs/nfs4proc.c96
-rw-r--r--fs/nfs/nfs4xdr.c31
-rw-r--r--include/linux/nfs_xdr.h5
-rw-r--r--include/linux/sunrpc/xdr.h2
-rw-r--r--net/sunrpc/xdr.c3
5 files changed, 89 insertions, 48 deletions
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index fcc2408d7ab0..3b1080118452 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -3426,19 +3426,6 @@ static inline int nfs4_server_supports_acls(struct nfs_server *server)
3426 */ 3426 */
3427#define NFS4ACL_MAXPAGES (XATTR_SIZE_MAX >> PAGE_CACHE_SHIFT) 3427#define NFS4ACL_MAXPAGES (XATTR_SIZE_MAX >> PAGE_CACHE_SHIFT)
3428 3428
3429static void buf_to_pages(const void *buf, size_t buflen,
3430 struct page **pages, unsigned int *pgbase)
3431{
3432 const void *p = buf;
3433
3434 *pgbase = offset_in_page(buf);
3435 p -= *pgbase;
3436 while (p < buf + buflen) {
3437 *(pages++) = virt_to_page(p);
3438 p += PAGE_CACHE_SIZE;
3439 }
3440}
3441
3442static int buf_to_pages_noslab(const void *buf, size_t buflen, 3429static int buf_to_pages_noslab(const void *buf, size_t buflen,
3443 struct page **pages, unsigned int *pgbase) 3430 struct page **pages, unsigned int *pgbase)
3444{ 3431{
@@ -3535,9 +3522,19 @@ out:
3535 nfs4_set_cached_acl(inode, acl); 3522 nfs4_set_cached_acl(inode, acl);
3536} 3523}
3537 3524
3525/*
3526 * The getxattr API returns the required buffer length when called with a
3527 * NULL buf. The NFSv4 acl tool then calls getxattr again after allocating
3528 * the required buf. On a NULL buf, we send a page of data to the server
3529 * guessing that the ACL request can be serviced by a page. If so, we cache
3530 * up to the page of ACL data, and the 2nd call to getxattr is serviced by
3531 * the cache. If not so, we throw away the page, and cache the required
3532 * length. The next getxattr call will then produce another round trip to
3533 * the server, this time with the input buf of the required size.
3534 */
3538static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen) 3535static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
3539{ 3536{
3540 struct page *pages[NFS4ACL_MAXPAGES]; 3537 struct page *pages[NFS4ACL_MAXPAGES] = {NULL, };
3541 struct nfs_getaclargs args = { 3538 struct nfs_getaclargs args = {
3542 .fh = NFS_FH(inode), 3539 .fh = NFS_FH(inode),
3543 .acl_pages = pages, 3540 .acl_pages = pages,
@@ -3552,41 +3549,60 @@ static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t bu
3552 .rpc_argp = &args, 3549 .rpc_argp = &args,
3553 .rpc_resp = &res, 3550 .rpc_resp = &res,
3554 }; 3551 };
3555 struct page *localpage = NULL; 3552 int ret = -ENOMEM, npages, i, acl_len = 0;
3556 int ret;
3557 3553
3558 if (buflen < PAGE_SIZE) { 3554 npages = (buflen + PAGE_SIZE - 1) >> PAGE_SHIFT;
3559 /* As long as we're doing a round trip to the server anyway, 3555 /* As long as we're doing a round trip to the server anyway,
3560 * let's be prepared for a page of acl data. */ 3556 * let's be prepared for a page of acl data. */
3561 localpage = alloc_page(GFP_KERNEL); 3557 if (npages == 0)
3562 resp_buf = page_address(localpage); 3558 npages = 1;
3563 if (localpage == NULL) 3559
3564 return -ENOMEM; 3560 for (i = 0; i < npages; i++) {
3565 args.acl_pages[0] = localpage; 3561 pages[i] = alloc_page(GFP_KERNEL);
3566 args.acl_pgbase = 0; 3562 if (!pages[i])
3567 args.acl_len = PAGE_SIZE; 3563 goto out_free;
3568 } else { 3564 }
3569 resp_buf = buf; 3565 if (npages > 1) {
3570 buf_to_pages(buf, buflen, args.acl_pages, &args.acl_pgbase); 3566 /* for decoding across pages */
3567 args.acl_scratch = alloc_page(GFP_KERNEL);
3568 if (!args.acl_scratch)
3569 goto out_free;
3571 } 3570 }
3572 ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode), &msg, &args.seq_args, &res.seq_res, 0); 3571 args.acl_len = npages * PAGE_SIZE;
3572 args.acl_pgbase = 0;
3573 /* Let decode_getfacl know not to fail if the ACL data is larger than
3574 * the page we send as a guess */
3575 if (buf == NULL)
3576 res.acl_flags |= NFS4_ACL_LEN_REQUEST;
3577 resp_buf = page_address(pages[0]);
3578
3579 dprintk("%s buf %p buflen %ld npages %d args.acl_len %ld\n",
3580 __func__, buf, buflen, npages, args.acl_len);
3581 ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
3582 &msg, &args.seq_args, &res.seq_res, 0);
3573 if (ret) 3583 if (ret)
3574 goto out_free; 3584 goto out_free;
3575 if (res.acl_len > args.acl_len) 3585
3576 nfs4_write_cached_acl(inode, NULL, res.acl_len); 3586 acl_len = res.acl_len - res.acl_data_offset;
3587 if (acl_len > args.acl_len)
3588 nfs4_write_cached_acl(inode, NULL, acl_len);
3577 else 3589 else
3578 nfs4_write_cached_acl(inode, resp_buf, res.acl_len); 3590 nfs4_write_cached_acl(inode, resp_buf + res.acl_data_offset,
3591 acl_len);
3579 if (buf) { 3592 if (buf) {
3580 ret = -ERANGE; 3593 ret = -ERANGE;
3581 if (res.acl_len > buflen) 3594 if (acl_len > buflen)
3582 goto out_free; 3595 goto out_free;
3583 if (localpage) 3596 _copy_from_pages(buf, pages, res.acl_data_offset,
3584 memcpy(buf, resp_buf, res.acl_len); 3597 res.acl_len);
3585 } 3598 }
3586 ret = res.acl_len; 3599 ret = acl_len;
3587out_free: 3600out_free:
3588 if (localpage) 3601 for (i = 0; i < npages; i++)
3589 __free_page(localpage); 3602 if (pages[i])
3603 __free_page(pages[i]);
3604 if (args.acl_scratch)
3605 __free_page(args.acl_scratch);
3590 return ret; 3606 return ret;
3591} 3607}
3592 3608
@@ -3617,6 +3633,8 @@ static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
3617 nfs_zap_acl_cache(inode); 3633 nfs_zap_acl_cache(inode);
3618 ret = nfs4_read_cached_acl(inode, buf, buflen); 3634 ret = nfs4_read_cached_acl(inode, buf, buflen);
3619 if (ret != -ENOENT) 3635 if (ret != -ENOENT)
3636 /* -ENOENT is returned if there is no ACL or if there is an ACL
3637 * but no cached acl data, just the acl length */
3620 return ret; 3638 return ret;
3621 return nfs4_get_acl_uncached(inode, buf, buflen); 3639 return nfs4_get_acl_uncached(inode, buf, buflen);
3622} 3640}
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index e6161b213ed1..dcaf69309d8e 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -2517,11 +2517,13 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
2517 encode_compound_hdr(xdr, req, &hdr); 2517 encode_compound_hdr(xdr, req, &hdr);
2518 encode_sequence(xdr, &args->seq_args, &hdr); 2518 encode_sequence(xdr, &args->seq_args, &hdr);
2519 encode_putfh(xdr, args->fh, &hdr); 2519 encode_putfh(xdr, args->fh, &hdr);
2520 replen = hdr.replen + op_decode_hdr_maxsz + nfs4_fattr_bitmap_maxsz + 1; 2520 replen = hdr.replen + op_decode_hdr_maxsz + 1;
2521 encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr); 2521 encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
2522 2522
2523 xdr_inline_pages(&req->rq_rcv_buf, replen << 2, 2523 xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
2524 args->acl_pages, args->acl_pgbase, args->acl_len); 2524 args->acl_pages, args->acl_pgbase, args->acl_len);
2525 xdr_set_scratch_buffer(xdr, page_address(args->acl_scratch), PAGE_SIZE);
2526
2525 encode_nops(&hdr); 2527 encode_nops(&hdr);
2526} 2528}
2527 2529
@@ -4957,17 +4959,18 @@ decode_restorefh(struct xdr_stream *xdr)
4957} 4959}
4958 4960
4959static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req, 4961static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
4960 size_t *acl_len) 4962 struct nfs_getaclres *res)
4961{ 4963{
4962 __be32 *savep; 4964 __be32 *savep, *bm_p;
4963 uint32_t attrlen, 4965 uint32_t attrlen,
4964 bitmap[3] = {0}; 4966 bitmap[3] = {0};
4965 struct kvec *iov = req->rq_rcv_buf.head; 4967 struct kvec *iov = req->rq_rcv_buf.head;
4966 int status; 4968 int status;
4967 4969
4968 *acl_len = 0; 4970 res->acl_len = 0;
4969 if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0) 4971 if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
4970 goto out; 4972 goto out;
4973 bm_p = xdr->p;
4971 if ((status = decode_attr_bitmap(xdr, bitmap)) != 0) 4974 if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
4972 goto out; 4975 goto out;
4973 if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0) 4976 if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
@@ -4979,18 +4982,30 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
4979 size_t hdrlen; 4982 size_t hdrlen;
4980 u32 recvd; 4983 u32 recvd;
4981 4984
4985 /* The bitmap (xdr len + bitmaps) and the attr xdr len words
4986 * are stored with the acl data to handle the problem of
4987 * variable length bitmaps.*/
4988 xdr->p = bm_p;
4989 res->acl_data_offset = be32_to_cpup(bm_p) + 2;
4990 res->acl_data_offset <<= 2;
4991
4982 /* We ignore &savep and don't do consistency checks on 4992 /* We ignore &savep and don't do consistency checks on
4983 * the attr length. Let userspace figure it out.... */ 4993 * the attr length. Let userspace figure it out.... */
4984 hdrlen = (u8 *)xdr->p - (u8 *)iov->iov_base; 4994 hdrlen = (u8 *)xdr->p - (u8 *)iov->iov_base;
4995 attrlen += res->acl_data_offset;
4985 recvd = req->rq_rcv_buf.len - hdrlen; 4996 recvd = req->rq_rcv_buf.len - hdrlen;
4986 if (attrlen > recvd) { 4997 if (attrlen > recvd) {
4987 dprintk("NFS: server cheating in getattr" 4998 if (res->acl_flags & NFS4_ACL_LEN_REQUEST) {
4988 " acl reply: attrlen %u > recvd %u\n", 4999 /* getxattr interface called with a NULL buf */
5000 res->acl_len = attrlen;
5001 goto out;
5002 }
5003 dprintk("NFS: acl reply: attrlen %u > recvd %u\n",
4989 attrlen, recvd); 5004 attrlen, recvd);
4990 return -EINVAL; 5005 return -EINVAL;
4991 } 5006 }
4992 xdr_read_pages(xdr, attrlen); 5007 xdr_read_pages(xdr, attrlen);
4993 *acl_len = attrlen; 5008 res->acl_len = attrlen;
4994 } else 5009 } else
4995 status = -EOPNOTSUPP; 5010 status = -EOPNOTSUPP;
4996 5011
@@ -6028,7 +6043,7 @@ nfs4_xdr_dec_getacl(struct rpc_rqst *rqstp, struct xdr_stream *xdr,
6028 status = decode_putfh(xdr); 6043 status = decode_putfh(xdr);
6029 if (status) 6044 if (status)
6030 goto out; 6045 goto out;
6031 status = decode_getacl(xdr, rqstp, &res->acl_len); 6046 status = decode_getacl(xdr, rqstp, res);
6032 6047
6033out: 6048out:
6034 return status; 6049 return status;
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index 2a7c533be5dd..6c898afe6095 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -602,11 +602,16 @@ struct nfs_getaclargs {
602 size_t acl_len; 602 size_t acl_len;
603 unsigned int acl_pgbase; 603 unsigned int acl_pgbase;
604 struct page ** acl_pages; 604 struct page ** acl_pages;
605 struct page * acl_scratch;
605 struct nfs4_sequence_args seq_args; 606 struct nfs4_sequence_args seq_args;
606}; 607};
607 608
609/* getxattr ACL interface flags */
610#define NFS4_ACL_LEN_REQUEST 0x0001 /* zero length getxattr buffer */
608struct nfs_getaclres { 611struct nfs_getaclres {
609 size_t acl_len; 612 size_t acl_len;
613 size_t acl_data_offset;
614 int acl_flags;
610 struct nfs4_sequence_res seq_res; 615 struct nfs4_sequence_res seq_res;
611}; 616};
612 617
diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h
index a20970ef9e4e..af70af333546 100644
--- a/include/linux/sunrpc/xdr.h
+++ b/include/linux/sunrpc/xdr.h
@@ -191,6 +191,8 @@ extern int xdr_decode_array2(struct xdr_buf *buf, unsigned int base,
191 struct xdr_array2_desc *desc); 191 struct xdr_array2_desc *desc);
192extern int xdr_encode_array2(struct xdr_buf *buf, unsigned int base, 192extern int xdr_encode_array2(struct xdr_buf *buf, unsigned int base,
193 struct xdr_array2_desc *desc); 193 struct xdr_array2_desc *desc);
194extern void _copy_from_pages(char *p, struct page **pages, size_t pgbase,
195 size_t len);
194 196
195/* 197/*
196 * Provide some simple tools for XDR buffer overflow-checking etc. 198 * Provide some simple tools for XDR buffer overflow-checking etc.
diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
index 277ebd4bf095..593f4c605305 100644
--- a/net/sunrpc/xdr.c
+++ b/net/sunrpc/xdr.c
@@ -296,7 +296,7 @@ _copy_to_pages(struct page **pages, size_t pgbase, const char *p, size_t len)
296 * Copies data into an arbitrary memory location from an array of pages 296 * Copies data into an arbitrary memory location from an array of pages
297 * The copy is assumed to be non-overlapping. 297 * The copy is assumed to be non-overlapping.
298 */ 298 */
299static void 299void
300_copy_from_pages(char *p, struct page **pages, size_t pgbase, size_t len) 300_copy_from_pages(char *p, struct page **pages, size_t pgbase, size_t len)
301{ 301{
302 struct page **pgfrom; 302 struct page **pgfrom;
@@ -324,6 +324,7 @@ _copy_from_pages(char *p, struct page **pages, size_t pgbase, size_t len)
324 324
325 } while ((len -= copy) != 0); 325 } while ((len -= copy) != 0);
326} 326}
327EXPORT_SYMBOL_GPL(_copy_from_pages);
327 328
328/* 329/*
329 * xdr_shrink_bufhead 330 * xdr_shrink_bufhead