Commit 64596af0 authored by Trond Myklebust's avatar Trond Myklebust

RPC: add a field to the xdr_buf that explicitly contains the maximum buffer

     length.
RPC: make the client receive xdr_buf return the actual length of the RPC
     length.
NFSv4/RPC: improved checks to prevent XDR reading beyond the actual end of
     the RPC reply.
parent d9d762e7
......@@ -231,7 +231,7 @@ nfs_xdr_readargs(struct rpc_rqst *req, u32 *p, struct nfs_readargs *args)
static int
nfs_xdr_readres(struct rpc_rqst *req, u32 *p, struct nfs_readres *res)
{
struct iovec *iov = req->rq_rvec;
struct iovec *iov = req->rq_rcv_buf.head;
int status, count, recvd, hdrlen;
if ((status = ntohl(*p++)))
......@@ -250,7 +250,7 @@ nfs_xdr_readres(struct rpc_rqst *req, u32 *p, struct nfs_readres *res)
xdr_shift_buf(&req->rq_rcv_buf, iov->iov_len - hdrlen);
}
recvd = req->rq_received - hdrlen;
recvd = req->rq_rcv_buf.len - hdrlen;
if (count > recvd) {
printk(KERN_WARNING "NFS: server cheating in read reply: "
"count %d > recvd %d\n", count, recvd);
......@@ -396,7 +396,7 @@ nfs_xdr_readdirres(struct rpc_rqst *req, u32 *p, void *dummy)
}
pglen = rcvbuf->page_len;
recvd = req->rq_received - hdrlen;
recvd = rcvbuf->len - hdrlen;
if (pglen > recvd)
pglen = recvd;
page = rcvbuf->pages;
......
......@@ -515,7 +515,7 @@ nfs3_xdr_readdirres(struct rpc_rqst *req, u32 *p, struct nfs3_readdirres *res)
}
pglen = rcvbuf->page_len;
recvd = req->rq_received - hdrlen;
recvd = rcvbuf->len - hdrlen;
if (pglen > recvd)
pglen = recvd;
page = rcvbuf->pages;
......@@ -758,7 +758,7 @@ nfs3_xdr_readlinkres(struct rpc_rqst *req, u32 *p, struct nfs_fattr *fattr)
static int
nfs3_xdr_readres(struct rpc_rqst *req, u32 *p, struct nfs_readres *res)
{
struct iovec *iov = req->rq_rvec;
struct iovec *iov = req->rq_rcv_buf.head;
int status, count, ocount, recvd, hdrlen;
status = ntohl(*p++);
......@@ -789,7 +789,7 @@ nfs3_xdr_readres(struct rpc_rqst *req, u32 *p, struct nfs_readres *res)
xdr_shift_buf(&req->rq_rcv_buf, iov->iov_len - hdrlen);
}
recvd = req->rq_received - hdrlen;
recvd = req->rq_rcv_buf.len - hdrlen;
if (count > recvd) {
printk(KERN_WARNING "NFS: server cheating in read reply: "
"count %d > recvd %d\n", count, recvd);
......
......@@ -2671,7 +2671,7 @@ static int decode_putrootfh(struct xdr_stream *xdr)
static int decode_read(struct xdr_stream *xdr, struct rpc_rqst *req, struct nfs_readres *res)
{
struct iovec *iov = req->rq_rvec;
struct iovec *iov = req->rq_rcv_buf.head;
uint32_t *p;
uint32_t count, eof, recvd, hdrlen;
int status;
......@@ -2683,7 +2683,7 @@ static int decode_read(struct xdr_stream *xdr, struct rpc_rqst *req, struct nfs_
READ32(eof);
READ32(count);
hdrlen = (u8 *) p - (u8 *) iov->iov_base;
recvd = req->rq_received - hdrlen;
recvd = req->rq_rcv_buf.len - hdrlen;
if (count > recvd) {
printk(KERN_WARNING "NFS: server cheating in read reply: "
"count %u > recvd %u\n", count, recvd);
......@@ -2713,7 +2713,7 @@ static int decode_readdir(struct xdr_stream *xdr, struct rpc_rqst *req, struct n
COPYMEM(readdir->verifier.data, 8);
hdrlen = (char *) p - (char *) iov->iov_base;
recvd = req->rq_received - hdrlen;
recvd = rcvbuf->len - hdrlen;
if (pglen > recvd)
pglen = recvd;
xdr_read_pages(xdr, pglen);
......
......@@ -55,7 +55,8 @@ struct xdr_buf {
unsigned int page_base, /* Start of page data */
page_len; /* Length of page data */
unsigned int len; /* Total length of data */
unsigned int buflen, /* Total length of storage buffer */
len; /* Length of XDR encoded message */
};
......@@ -183,86 +184,14 @@ struct xdr_stream {
struct iovec *iov; /* pointer to the current iovec */
};
/*
* Initialize an xdr_stream for encoding data.
*
* Note: at the moment the RPC client only passes the length of our
* scratch buffer in the xdr_buf's header iovec. Previously this
* meant we needed to call xdr_adjust_iovec() after encoding the
* data. With the new scheme, the xdr_stream manages the details
* of the buffer length, and takes care of adjusting the iovec
* length for us.
*/
static inline void
xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, uint32_t *p)
{
struct iovec *iov = buf->head;
xdr->buf = buf;
xdr->iov = iov;
xdr->end = (uint32_t *)((char *)iov->iov_base + iov->iov_len);
buf->len = iov->iov_len = (char *)p - (char *)iov->iov_base;
xdr->p = p;
}
/*
* Check that we have enough buffer space to encode 'nbytes' more
* bytes of data. If so, update the total xdr_buf length, and
* adjust the length of the current iovec.
*/
static inline uint32_t *
xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes)
{
uint32_t *p = xdr->p;
uint32_t *q;
/* align nbytes on the next 32-bit boundary */
nbytes += 3;
nbytes &= ~3;
q = p + (nbytes >> 2);
if (unlikely(q > xdr->end || q < p))
return NULL;
xdr->p = q;
xdr->iov->iov_len += nbytes;
xdr->buf->len += nbytes;
return p;
}
extern void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, uint32_t *p);
extern uint32_t *xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes);
extern void xdr_write_pages(struct xdr_stream *xdr, struct page **pages,
unsigned int base, unsigned int len);
extern void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, uint32_t *p);
extern uint32_t *xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes);
extern void xdr_read_pages(struct xdr_stream *xdr, unsigned int len);
/*
* Initialize an xdr_stream for decoding data.
*/
static inline void
xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, uint32_t *p)
{
struct iovec *iov = buf->head;
xdr->buf = buf;
xdr->iov = iov;
xdr->p = p;
xdr->end = (uint32_t *)((char *)iov->iov_base + iov->iov_len);
}
/*
* Check if the input buffer is long enough to enable us to decode
* 'nbytes' more bytes of data starting at the current position.
* If so return the current pointer, then update the current
* position.
*/
static inline uint32_t *
xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)
{
uint32_t *p = xdr->p;
uint32_t *q = p + XDR_QUADLEN(nbytes);
if (unlikely(q > xdr->end || q < p))
return NULL;
xdr->p = q;
return p;
}
#endif /* __KERNEL__ */
#endif /* _SUNRPC_XDR_H_ */
......@@ -120,8 +120,6 @@ struct rpc_rqst {
};
#define rq_svec rq_snd_buf.head
#define rq_slen rq_snd_buf.len
#define rq_rvec rq_rcv_buf.head
#define rq_rlen rq_rcv_buf.len
#define XPRT_LAST_FRAG (1 << 0)
#define XPRT_COPY_RECM (1 << 1)
......
......@@ -605,11 +605,13 @@ call_encode(struct rpc_task *task)
sndbuf->tail[0].iov_len = 0;
sndbuf->page_len = 0;
sndbuf->len = 0;
sndbuf->buflen = bufsiz;
rcvbuf->head[0].iov_base = (void *)((char *)task->tk_buffer + bufsiz);
rcvbuf->head[0].iov_len = bufsiz;
rcvbuf->tail[0].iov_len = 0;
rcvbuf->page_len = 0;
rcvbuf->len = bufsiz;
rcvbuf->len = 0;
rcvbuf->buflen = bufsiz;
/* Encode header and provided arguments */
encode = task->tk_msg.rpc_proc->p_encode;
......@@ -849,6 +851,8 @@ call_decode(struct rpc_task *task)
return;
}
req->rq_rcv_buf.len = req->rq_private_buf.len;
/* Check that the softirq receive buffer is valid */
WARN_ON(memcmp(&req->rq_rcv_buf, &req->rq_private_buf,
sizeof(req->rq_rcv_buf)) != 0);
......@@ -884,7 +888,7 @@ call_decode(struct rpc_task *task)
task->tk_status);
return;
out_retry:
req->rq_received = 0;
req->rq_received = req->rq_private_buf.len = 0;
task->tk_status = 0;
}
......@@ -956,7 +960,7 @@ call_header(struct rpc_task *task)
static u32 *
call_verify(struct rpc_task *task)
{
u32 *p = task->tk_rqstp->rq_rvec[0].iov_base, n;
u32 *p = task->tk_rqstp->rq_rcv_buf.head[0].iov_base, n;
p += 1; /* skip XID */
......
......@@ -128,8 +128,6 @@ EXPORT_SYMBOL(xdr_encode_netobj);
EXPORT_SYMBOL(xdr_encode_pages);
EXPORT_SYMBOL(xdr_inline_pages);
EXPORT_SYMBOL(xdr_shift_buf);
EXPORT_SYMBOL(xdr_write_pages);
EXPORT_SYMBOL(xdr_read_pages);
EXPORT_SYMBOL(xdr_buf_from_iov);
EXPORT_SYMBOL(xdr_buf_subsegment);
EXPORT_SYMBOL(xdr_buf_read_netobj);
......
......@@ -160,6 +160,7 @@ xdr_encode_pages(struct xdr_buf *xdr, struct page **pages, unsigned int base,
tail->iov_len = pad;
len += pad;
}
xdr->buflen += len;
xdr->len += len;
}
......@@ -181,7 +182,7 @@ xdr_inline_pages(struct xdr_buf *xdr, unsigned int offset,
tail->iov_base = buf + offset;
tail->iov_len = buflen - offset;
xdr->len += len;
xdr->buflen += len;
}
/*
......@@ -675,7 +676,10 @@ xdr_shrink_bufhead(struct xdr_buf *buf, size_t len)
copy);
}
head->iov_len -= len;
buf->len -= len;
buf->buflen -= len;
/* Have we truncated the message? */
if (buf->len > buf->buflen)
buf->len = buf->buflen;
}
/*
......@@ -705,7 +709,7 @@ xdr_shrink_pagelen(struct xdr_buf *buf, size_t len)
copy = tail->iov_len - len;
memmove(p, tail->iov_base, copy);
} else
buf->len -= len;
buf->buflen -= len;
/* Copy from the inlined pages into the tail */
copy = len;
if (copy > tail->iov_len)
......@@ -715,7 +719,10 @@ xdr_shrink_pagelen(struct xdr_buf *buf, size_t len)
copy);
}
buf->page_len -= len;
buf->len -= len;
buf->buflen -= len;
/* Have we truncated the message? */
if (buf->len > buf->buflen)
buf->len = buf->buflen;
}
void
......@@ -724,8 +731,67 @@ xdr_shift_buf(struct xdr_buf *buf, size_t len)
xdr_shrink_bufhead(buf, len);
}
void
xdr_write_pages(struct xdr_stream *xdr, struct page **pages, unsigned int base,
/**
* xdr_init_encode - Initialize a struct xdr_stream for sending data.
* @xdr: pointer to xdr_stream struct
* @buf: pointer to XDR buffer in which to encode data
* @p: current pointer inside XDR buffer
*
* Note: at the moment the RPC client only passes the length of our
* scratch buffer in the xdr_buf's header iovec. Previously this
* meant we needed to call xdr_adjust_iovec() after encoding the
* data. With the new scheme, the xdr_stream manages the details
* of the buffer length, and takes care of adjusting the iovec
* length for us.
*/
void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, uint32_t *p)
{
struct iovec *iov = buf->head;
xdr->buf = buf;
xdr->iov = iov;
xdr->end = (uint32_t *)((char *)iov->iov_base + iov->iov_len);
buf->len = iov->iov_len = (char *)p - (char *)iov->iov_base;
xdr->p = p;
}
EXPORT_SYMBOL(xdr_init_encode);
/**
* xdr_reserve_space - Reserve buffer space for sending
* @xdr: pointer to xdr_stream
* @nbytes: number of bytes to reserve
*
* Checks that we have enough buffer space to encode 'nbytes' more
* bytes of data. If so, update the total xdr_buf length, and
* adjust the length of the current iovec.
*/
uint32_t * xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes)
{
uint32_t *p = xdr->p;
uint32_t *q;
/* align nbytes on the next 32-bit boundary */
nbytes += 3;
nbytes &= ~3;
q = p + (nbytes >> 2);
if (unlikely(q > xdr->end || q < p))
return NULL;
xdr->p = q;
xdr->iov->iov_len += nbytes;
xdr->buf->len += nbytes;
return p;
}
EXPORT_SYMBOL(xdr_reserve_space);
/**
* xdr_write_pages - Insert a list of pages into an XDR buffer for sending
* @xdr: pointer to xdr_stream
* @pages: list of pages
* @base: offset of first byte
* @len: length of data in bytes
*
*/
void xdr_write_pages(struct xdr_stream *xdr, struct page **pages, unsigned int base,
unsigned int len)
{
struct xdr_buf *buf = xdr->buf;
......@@ -747,15 +813,69 @@ xdr_write_pages(struct xdr_stream *xdr, struct page **pages, unsigned int base,
len += pad;
*xdr->p++ = 0;
}
buf->buflen += len;
buf->len += len;
}
EXPORT_SYMBOL(xdr_write_pages);
void
xdr_read_pages(struct xdr_stream *xdr, unsigned int len)
/**
* xdr_init_decode - Initialize an xdr_stream for decoding data.
* @xdr: pointer to xdr_stream struct
* @buf: pointer to XDR buffer from which to decode data
* @p: current pointer inside XDR buffer
*/
void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, uint32_t *p)
{
struct iovec *iov = buf->head;
unsigned int len = iov->iov_len;
if (len > buf->len)
len = buf->len;
xdr->buf = buf;
xdr->iov = iov;
xdr->p = p;
xdr->end = (uint32_t *)((char *)iov->iov_base + len);
}
EXPORT_SYMBOL(xdr_init_decode);
/**
* xdr_inline_decode - Retrieve non-page XDR data to decode
* @xdr: pointer to xdr_stream struct
* @nbytes: number of bytes of data to decode
*
* Check if the input buffer is long enough to enable us to decode
* 'nbytes' more bytes of data starting at the current position.
* If so return the current pointer, then update the current
* pointer position.
*/
uint32_t * xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)
{
uint32_t *p = xdr->p;
uint32_t *q = p + XDR_QUADLEN(nbytes);
if (unlikely(q > xdr->end || q < p))
return NULL;
xdr->p = q;
return p;
}
EXPORT_SYMBOL(xdr_inline_decode);
/**
* xdr_read_pages - Ensure page-based XDR data to decode is aligned at current pointer position
* @xdr: pointer to xdr_stream struct
* @len: number of bytes of page data
*
* Moves data beyond the current pointer position from the XDR head[] buffer
* into the page list. Any data that lies beyond current position + "len"
* bytes is moved into the XDR tail[]. The current pointer is then
* repositioned at the beginning of the XDR tail.
*/
void xdr_read_pages(struct xdr_stream *xdr, unsigned int len)
{
struct xdr_buf *buf = xdr->buf;
struct iovec *iov;
ssize_t shift;
unsigned int end;
int padding;
/* Realign pages to current pointer position */
......@@ -769,9 +889,21 @@ xdr_read_pages(struct xdr_stream *xdr, unsigned int len)
xdr_shrink_pagelen(buf, buf->page_len - len);
padding = (XDR_QUADLEN(len) << 2) - len;
xdr->iov = iov = buf->tail;
/* Compute remaining message length. */
end = iov->iov_len;
shift = buf->buflen - buf->len;
if (shift < end)
end -= shift;
else if (shift > 0)
end = 0;
/*
* Position current pointer at beginning of tail, and
* set remaining message length.
*/
xdr->p = (uint32_t *)((char *)iov->iov_base + padding);
xdr->end = (uint32_t *)((char *)iov->iov_base + iov->iov_len);
xdr->end = (uint32_t *)((char *)iov->iov_base + end);
}
EXPORT_SYMBOL(xdr_read_pages);
static struct iovec empty_iov = {.iov_base = NULL, .iov_len = 0};
......@@ -781,7 +913,7 @@ xdr_buf_from_iov(struct iovec *iov, struct xdr_buf *buf)
buf->head[0] = *iov;
buf->tail[0] = empty_iov;
buf->page_len = 0;
buf->len = iov->iov_len;
buf->buflen = buf->len = iov->iov_len;
}
/* Sets subiov to the intersection of iov with the buffer of length len
......@@ -811,7 +943,7 @@ xdr_buf_subsegment(struct xdr_buf *buf, struct xdr_buf *subbuf,
{
int i;
subbuf->len = len;
subbuf->buflen = subbuf->len = len;
iov_subsegment(buf->head, subbuf->head, &base, &len);
if (base < buf->page_len) {
......
......@@ -647,8 +647,8 @@ xprt_complete_rqst(struct rpc_xprt *xprt, struct rpc_rqst *req, int copied)
#endif
dprintk("RPC: %4d has input (%d bytes)\n", task->tk_pid, copied);
req->rq_received = copied;
list_del_init(&req->rq_list);
req->rq_received = req->rq_private_buf.len = copied;
/* ... and wake up the process. */
rpc_wake_up_task(task);
......@@ -765,7 +765,7 @@ udp_data_ready(struct sock *sk, int len)
dprintk("RPC: %4d received reply\n", task->tk_pid);
if ((copied = rovr->rq_private_buf.len) > repsize)
if ((copied = rovr->rq_private_buf.buflen) > repsize)
copied = repsize;
/* Suck it into the iovec, verify checksum if not done by hw. */
......@@ -908,7 +908,7 @@ tcp_read_request(struct rpc_xprt *xprt, skb_reader_t *desc)
xprt->tcp_copied += len;
xprt->tcp_offset += len;
if (xprt->tcp_copied == req->rq_private_buf.len)
if (xprt->tcp_copied == req->rq_private_buf.buflen)
xprt->tcp_flags &= ~XPRT_COPY_DATA;
else if (xprt->tcp_offset == xprt->tcp_reclen) {
if (xprt->tcp_flags & XPRT_LAST_FRAG)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment