diff options
author | Andy Adamson <andros@netapp.com> | 2011-12-07 11:55:27 -0500 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2012-01-05 10:42:42 -0500 |
commit | bf118a342f10dafe44b14451a1392c3254629a1f (patch) | |
tree | 50c2800c7c92f8e47e4a0a440e2a97847f678e19 | |
parent | 3476f114addb7b96912840a234702f660a1f460b (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.c | 96 | ||||
-rw-r--r-- | fs/nfs/nfs4xdr.c | 31 | ||||
-rw-r--r-- | include/linux/nfs_xdr.h | 5 | ||||
-rw-r--r-- | include/linux/sunrpc/xdr.h | 2 | ||||
-rw-r--r-- | net/sunrpc/xdr.c | 3 |
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 | ||
3429 | static 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 | |||
3442 | static int buf_to_pages_noslab(const void *buf, size_t buflen, | 3429 | static 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 | */ | ||
3538 | static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen) | 3535 | static 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; |
3587 | out_free: | 3600 | out_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 | ||
4959 | static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req, | 4961 | static 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 | ||
6033 | out: | 6048 | out: |
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 */ | ||
608 | struct nfs_getaclres { | 611 | struct 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); |
192 | extern int xdr_encode_array2(struct xdr_buf *buf, unsigned int base, | 192 | extern int xdr_encode_array2(struct xdr_buf *buf, unsigned int base, |
193 | struct xdr_array2_desc *desc); | 193 | struct xdr_array2_desc *desc); |
194 | extern 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 | */ |
299 | static void | 299 | void |
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 | } |
327 | EXPORT_SYMBOL_GPL(_copy_from_pages); | ||
327 | 328 | ||
328 | /* | 329 | /* |
329 | * xdr_shrink_bufhead | 330 | * xdr_shrink_bufhead |