diff options
-rw-r--r-- | fs/nfsd/nfs3proc.c | 10 | ||||
-rw-r--r-- | fs/nfsd/nfs3xdr.c | 51 | ||||
-rw-r--r-- | fs/nfsd/nfsproc.c | 14 | ||||
-rw-r--r-- | fs/nfsd/nfsxdr.c | 49 | ||||
-rw-r--r-- | fs/nfsd/xdr.h | 1 | ||||
-rw-r--r-- | fs/nfsd/xdr3.h | 1 | ||||
-rw-r--r-- | fs/nfsd/xdr4.h | 2 | ||||
-rw-r--r-- | include/linux/sunrpc/svc.h | 2 | ||||
-rw-r--r-- | net/sunrpc/svc.c | 67 |
9 files changed, 132 insertions, 65 deletions
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c index 2dd95ebf4935..6259a4b8579f 100644 --- a/fs/nfsd/nfs3proc.c +++ b/fs/nfsd/nfs3proc.c | |||
@@ -283,6 +283,16 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp) | |||
283 | struct nfsd3_diropres *resp = rqstp->rq_resp; | 283 | struct nfsd3_diropres *resp = rqstp->rq_resp; |
284 | __be32 nfserr; | 284 | __be32 nfserr; |
285 | 285 | ||
286 | if (argp->tlen == 0) | ||
287 | RETURN_STATUS(nfserr_inval); | ||
288 | if (argp->tlen > NFS3_MAXPATHLEN) | ||
289 | RETURN_STATUS(nfserr_nametoolong); | ||
290 | |||
291 | argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first, | ||
292 | argp->tlen); | ||
293 | if (IS_ERR(argp->tname)) | ||
294 | RETURN_STATUS(nfserrno(PTR_ERR(argp->tname))); | ||
295 | |||
286 | dprintk("nfsd: SYMLINK(3) %s %.*s -> %.*s\n", | 296 | dprintk("nfsd: SYMLINK(3) %s %.*s -> %.*s\n", |
287 | SVCFH_fmt(&argp->ffh), | 297 | SVCFH_fmt(&argp->ffh), |
288 | argp->flen, argp->fname, | 298 | argp->flen, argp->fname, |
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c index e19fc5d8bcb5..3192b544a441 100644 --- a/fs/nfsd/nfs3xdr.c +++ b/fs/nfsd/nfs3xdr.c | |||
@@ -481,51 +481,24 @@ int | |||
481 | nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p) | 481 | nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p) |
482 | { | 482 | { |
483 | struct nfsd3_symlinkargs *args = rqstp->rq_argp; | 483 | struct nfsd3_symlinkargs *args = rqstp->rq_argp; |
484 | unsigned int len, avail; | 484 | char *base = (char *)p; |
485 | char *old, *new; | 485 | size_t dlen; |
486 | struct kvec *vec; | ||
487 | 486 | ||
488 | if (!(p = decode_fh(p, &args->ffh)) || | 487 | if (!(p = decode_fh(p, &args->ffh)) || |
489 | !(p = decode_filename(p, &args->fname, &args->flen)) | 488 | !(p = decode_filename(p, &args->fname, &args->flen))) |
490 | ) | ||
491 | return 0; | 489 | return 0; |
492 | p = decode_sattr3(p, &args->attrs); | 490 | p = decode_sattr3(p, &args->attrs); |
493 | 491 | ||
494 | /* now decode the pathname, which might be larger than the first page. | 492 | args->tlen = ntohl(*p++); |
495 | * As we have to check for nul's anyway, we copy it into a new page | ||
496 | * This page appears in the rq_res.pages list, but as pages_len is always | ||
497 | * 0, it won't get in the way | ||
498 | */ | ||
499 | len = ntohl(*p++); | ||
500 | if (len == 0 || len > NFS3_MAXPATHLEN || len >= PAGE_SIZE) | ||
501 | return 0; | ||
502 | args->tname = new = page_address(*(rqstp->rq_next_page++)); | ||
503 | args->tlen = len; | ||
504 | /* first copy and check from the first page */ | ||
505 | old = (char*)p; | ||
506 | vec = &rqstp->rq_arg.head[0]; | ||
507 | if ((void *)old > vec->iov_base + vec->iov_len) | ||
508 | return 0; | ||
509 | avail = vec->iov_len - (old - (char*)vec->iov_base); | ||
510 | while (len && avail && *old) { | ||
511 | *new++ = *old++; | ||
512 | len--; | ||
513 | avail--; | ||
514 | } | ||
515 | /* now copy next page if there is one */ | ||
516 | if (len && !avail && rqstp->rq_arg.page_len) { | ||
517 | avail = min_t(unsigned int, rqstp->rq_arg.page_len, PAGE_SIZE); | ||
518 | old = page_address(rqstp->rq_arg.pages[0]); | ||
519 | } | ||
520 | while (len && avail && *old) { | ||
521 | *new++ = *old++; | ||
522 | len--; | ||
523 | avail--; | ||
524 | } | ||
525 | *new = '\0'; | ||
526 | if (len) | ||
527 | return 0; | ||
528 | 493 | ||
494 | args->first.iov_base = p; | ||
495 | args->first.iov_len = rqstp->rq_arg.head[0].iov_len; | ||
496 | args->first.iov_len -= (char *)p - base; | ||
497 | |||
498 | dlen = args->first.iov_len + rqstp->rq_arg.page_len + | ||
499 | rqstp->rq_arg.tail[0].iov_len; | ||
500 | if (dlen < XDR_QUADLEN(args->tlen) << 2) | ||
501 | return 0; | ||
529 | return 1; | 502 | return 1; |
530 | } | 503 | } |
531 | 504 | ||
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index 1995ea6bfd2b..f107f9fa8e15 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c | |||
@@ -449,17 +449,19 @@ nfsd_proc_symlink(struct svc_rqst *rqstp) | |||
449 | struct svc_fh newfh; | 449 | struct svc_fh newfh; |
450 | __be32 nfserr; | 450 | __be32 nfserr; |
451 | 451 | ||
452 | if (argp->tlen > NFS_MAXPATHLEN) | ||
453 | return nfserr_nametoolong; | ||
454 | |||
455 | argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first, | ||
456 | argp->tlen); | ||
457 | if (IS_ERR(argp->tname)) | ||
458 | return nfserrno(PTR_ERR(argp->tname)); | ||
459 | |||
452 | dprintk("nfsd: SYMLINK %s %.*s -> %.*s\n", | 460 | dprintk("nfsd: SYMLINK %s %.*s -> %.*s\n", |
453 | SVCFH_fmt(&argp->ffh), argp->flen, argp->fname, | 461 | SVCFH_fmt(&argp->ffh), argp->flen, argp->fname, |
454 | argp->tlen, argp->tname); | 462 | argp->tlen, argp->tname); |
455 | 463 | ||
456 | fh_init(&newfh, NFS_FHSIZE); | 464 | fh_init(&newfh, NFS_FHSIZE); |
457 | /* | ||
458 | * Crazy hack: the request fits in a page, and already-decoded | ||
459 | * attributes follow argp->tname, so it's safe to just write a | ||
460 | * null to ensure it's null-terminated: | ||
461 | */ | ||
462 | argp->tname[argp->tlen] = '\0'; | ||
463 | nfserr = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen, | 465 | nfserr = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen, |
464 | argp->tname, &newfh); | 466 | argp->tname, &newfh); |
465 | 467 | ||
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c index db24ae8b67e0..a43e8260520a 100644 --- a/fs/nfsd/nfsxdr.c +++ b/fs/nfsd/nfsxdr.c | |||
@@ -71,22 +71,6 @@ decode_filename(__be32 *p, char **namp, unsigned int *lenp) | |||
71 | } | 71 | } |
72 | 72 | ||
73 | static __be32 * | 73 | static __be32 * |
74 | decode_pathname(__be32 *p, char **namp, unsigned int *lenp) | ||
75 | { | ||
76 | char *name; | ||
77 | unsigned int i; | ||
78 | |||
79 | if ((p = xdr_decode_string_inplace(p, namp, lenp, NFS_MAXPATHLEN)) != NULL) { | ||
80 | for (i = 0, name = *namp; i < *lenp; i++, name++) { | ||
81 | if (*name == '\0') | ||
82 | return NULL; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | return p; | ||
87 | } | ||
88 | |||
89 | static __be32 * | ||
90 | decode_sattr(__be32 *p, struct iattr *iap) | 74 | decode_sattr(__be32 *p, struct iattr *iap) |
91 | { | 75 | { |
92 | u32 tmp, tmp1; | 76 | u32 tmp, tmp1; |
@@ -384,14 +368,39 @@ int | |||
384 | nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p) | 368 | nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p) |
385 | { | 369 | { |
386 | struct nfsd_symlinkargs *args = rqstp->rq_argp; | 370 | struct nfsd_symlinkargs *args = rqstp->rq_argp; |
371 | char *base = (char *)p; | ||
372 | size_t xdrlen; | ||
387 | 373 | ||
388 | if ( !(p = decode_fh(p, &args->ffh)) | 374 | if ( !(p = decode_fh(p, &args->ffh)) |
389 | || !(p = decode_filename(p, &args->fname, &args->flen)) | 375 | || !(p = decode_filename(p, &args->fname, &args->flen))) |
390 | || !(p = decode_pathname(p, &args->tname, &args->tlen))) | ||
391 | return 0; | 376 | return 0; |
392 | p = decode_sattr(p, &args->attrs); | ||
393 | 377 | ||
394 | return xdr_argsize_check(rqstp, p); | 378 | args->tlen = ntohl(*p++); |
379 | if (args->tlen == 0) | ||
380 | return 0; | ||
381 | |||
382 | args->first.iov_base = p; | ||
383 | args->first.iov_len = rqstp->rq_arg.head[0].iov_len; | ||
384 | args->first.iov_len -= (char *)p - base; | ||
385 | |||
386 | /* This request is never larger than a page. Therefore, | ||
387 | * transport will deliver either: | ||
388 | * 1. pathname in the pagelist -> sattr is in the tail. | ||
389 | * 2. everything in the head buffer -> sattr is in the head. | ||
390 | */ | ||
391 | if (rqstp->rq_arg.page_len) { | ||
392 | if (args->tlen != rqstp->rq_arg.page_len) | ||
393 | return 0; | ||
394 | p = rqstp->rq_arg.tail[0].iov_base; | ||
395 | } else { | ||
396 | xdrlen = XDR_QUADLEN(args->tlen); | ||
397 | if (xdrlen > args->first.iov_len - (8 * sizeof(__be32))) | ||
398 | return 0; | ||
399 | p += xdrlen; | ||
400 | } | ||
401 | decode_sattr(p, &args->attrs); | ||
402 | |||
403 | return 1; | ||
395 | } | 404 | } |
396 | 405 | ||
397 | int | 406 | int |
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h index a765c414015e..ea7cca3a64b7 100644 --- a/fs/nfsd/xdr.h +++ b/fs/nfsd/xdr.h | |||
@@ -72,6 +72,7 @@ struct nfsd_symlinkargs { | |||
72 | char * tname; | 72 | char * tname; |
73 | unsigned int tlen; | 73 | unsigned int tlen; |
74 | struct iattr attrs; | 74 | struct iattr attrs; |
75 | struct kvec first; | ||
75 | }; | 76 | }; |
76 | 77 | ||
77 | struct nfsd_readdirargs { | 78 | struct nfsd_readdirargs { |
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h index deccf7f691e9..2cb29e961a76 100644 --- a/fs/nfsd/xdr3.h +++ b/fs/nfsd/xdr3.h | |||
@@ -90,6 +90,7 @@ struct nfsd3_symlinkargs { | |||
90 | char * tname; | 90 | char * tname; |
91 | unsigned int tlen; | 91 | unsigned int tlen; |
92 | struct iattr attrs; | 92 | struct iattr attrs; |
93 | struct kvec first; | ||
93 | }; | 94 | }; |
94 | 95 | ||
95 | struct nfsd3_readdirargs { | 96 | struct nfsd3_readdirargs { |
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h index 7cbc129092fe..468020fe2a07 100644 --- a/fs/nfsd/xdr4.h +++ b/fs/nfsd/xdr4.h | |||
@@ -110,6 +110,7 @@ struct nfsd4_create { | |||
110 | struct { | 110 | struct { |
111 | u32 datalen; | 111 | u32 datalen; |
112 | char *data; | 112 | char *data; |
113 | struct kvec first; | ||
113 | } link; /* NF4LNK */ | 114 | } link; /* NF4LNK */ |
114 | struct { | 115 | struct { |
115 | u32 specdata1; | 116 | u32 specdata1; |
@@ -124,6 +125,7 @@ struct nfsd4_create { | |||
124 | }; | 125 | }; |
125 | #define cr_datalen u.link.datalen | 126 | #define cr_datalen u.link.datalen |
126 | #define cr_data u.link.data | 127 | #define cr_data u.link.data |
128 | #define cr_first u.link.first | ||
127 | #define cr_specdata1 u.dev.specdata1 | 129 | #define cr_specdata1 u.dev.specdata1 |
128 | #define cr_specdata2 u.dev.specdata2 | 130 | #define cr_specdata2 u.dev.specdata2 |
129 | 131 | ||
diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h index fb3fcacc1e98..574368e8a16f 100644 --- a/include/linux/sunrpc/svc.h +++ b/include/linux/sunrpc/svc.h | |||
@@ -497,6 +497,8 @@ struct svc_pool * svc_pool_for_cpu(struct svc_serv *serv, int cpu); | |||
497 | char * svc_print_addr(struct svc_rqst *, char *, size_t); | 497 | char * svc_print_addr(struct svc_rqst *, char *, size_t); |
498 | unsigned int svc_fill_write_vector(struct svc_rqst *rqstp, | 498 | unsigned int svc_fill_write_vector(struct svc_rqst *rqstp, |
499 | struct kvec *first, size_t total); | 499 | struct kvec *first, size_t total); |
500 | char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, | ||
501 | struct kvec *first, size_t total); | ||
500 | 502 | ||
501 | #define RPC_MAX_ADDRBUFLEN (63U) | 503 | #define RPC_MAX_ADDRBUFLEN (63U) |
502 | 504 | ||
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index a155e2de19aa..30a4226baf03 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c | |||
@@ -1575,3 +1575,70 @@ unsigned int svc_fill_write_vector(struct svc_rqst *rqstp, struct kvec *first, | |||
1575 | return i; | 1575 | return i; |
1576 | } | 1576 | } |
1577 | EXPORT_SYMBOL_GPL(svc_fill_write_vector); | 1577 | EXPORT_SYMBOL_GPL(svc_fill_write_vector); |
1578 | |||
1579 | /** | ||
1580 | * svc_fill_symlink_pathname - Construct pathname argument for VFS symlink call | ||
1581 | * @rqstp: svc_rqst to operate on | ||
1582 | * @first: buffer containing first section of pathname | ||
1583 | * @total: total length of the pathname argument | ||
1584 | * | ||
1585 | * Returns pointer to a NUL-terminated string, or an ERR_PTR. The buffer is | ||
1586 | * released automatically when @rqstp is recycled. | ||
1587 | */ | ||
1588 | char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, struct kvec *first, | ||
1589 | size_t total) | ||
1590 | { | ||
1591 | struct xdr_buf *arg = &rqstp->rq_arg; | ||
1592 | struct page **pages; | ||
1593 | char *result; | ||
1594 | |||
1595 | /* VFS API demands a NUL-terminated pathname. This function | ||
1596 | * uses a page from @rqstp as the pathname buffer, to enable | ||
1597 | * direct placement. Thus the total buffer size is PAGE_SIZE. | ||
1598 | * Space in this buffer for NUL-termination requires that we | ||
1599 | * cap the size of the returned symlink pathname just a | ||
1600 | * little early. | ||
1601 | */ | ||
1602 | if (total > PAGE_SIZE - 1) | ||
1603 | return ERR_PTR(-ENAMETOOLONG); | ||
1604 | |||
1605 | /* Some types of transport can present the pathname entirely | ||
1606 | * in rq_arg.pages. If not, then copy the pathname into one | ||
1607 | * page. | ||
1608 | */ | ||
1609 | pages = arg->pages; | ||
1610 | WARN_ON_ONCE(arg->page_base != 0); | ||
1611 | if (first->iov_base == 0) { | ||
1612 | result = page_address(*pages); | ||
1613 | result[total] = '\0'; | ||
1614 | } else { | ||
1615 | size_t len, remaining; | ||
1616 | char *dst; | ||
1617 | |||
1618 | result = page_address(*(rqstp->rq_next_page++)); | ||
1619 | dst = result; | ||
1620 | remaining = total; | ||
1621 | |||
1622 | len = min_t(size_t, total, first->iov_len); | ||
1623 | memcpy(dst, first->iov_base, len); | ||
1624 | dst += len; | ||
1625 | remaining -= len; | ||
1626 | |||
1627 | /* No more than one page left */ | ||
1628 | if (remaining) { | ||
1629 | len = min_t(size_t, remaining, PAGE_SIZE); | ||
1630 | memcpy(dst, page_address(*pages), len); | ||
1631 | dst += len; | ||
1632 | } | ||
1633 | |||
1634 | *dst = '\0'; | ||
1635 | } | ||
1636 | |||
1637 | /* Sanity check: we don't allow the pathname argument to | ||
1638 | * contain a NUL byte. | ||
1639 | */ | ||
1640 | if (strlen(result) != total) | ||
1641 | return ERR_PTR(-EINVAL); | ||
1642 | return result; | ||
1643 | } | ||
1644 | EXPORT_SYMBOL_GPL(svc_fill_symlink_pathname); | ||