Commit 61182c79 authored by Anna Schumaker's avatar Anna Schumaker
Browse files

SUNRPC: kmap() the xdr pages during decode



If the pages are in HIGHMEM then we need to make sure they're mapped
before trying to read data off of them, otherwise we could end up with a
NULL pointer dereference.

The downside to this is that we need an extra cleanup step at the end of
decode to kunmap() the last page. I introduced an xdr_finish_decode()
function to do this. Right now this function only calls the
unmap_current_page() function, but other generic cleanup steps could be
added in the future if we come across anything else.

Reported-by: default avatarKrzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: default avatarAnna Schumaker <Anna.Schumaker@Netapp.com>
parent 303a7805
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -224,6 +224,7 @@ struct xdr_stream {
	struct kvec *iov;	/* pointer to the current kvec */
	struct kvec scratch;	/* Scratch buffer */
	struct page **page_ptr;	/* pointer to the current page */
	void *page_kaddr;	/* kmapped address of the current page */
	unsigned int nwords;	/* Remaining decode buffer length */

	struct rpc_rqst *rqst;	/* For debugging */
@@ -255,6 +256,7 @@ extern void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf,
			    __be32 *p, struct rpc_rqst *rqst);
extern void xdr_init_decode_pages(struct xdr_stream *xdr, struct xdr_buf *buf,
		struct page **pages, unsigned int len);
extern void xdr_finish_decode(struct xdr_stream *xdr);
extern __be32 *xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes);
extern unsigned int xdr_read_pages(struct xdr_stream *xdr, unsigned int len);
extern void xdr_enter_page(struct xdr_stream *xdr, unsigned int len);
+1 −0
Original line number Diff line number Diff line
@@ -2602,6 +2602,7 @@ call_decode(struct rpc_task *task)
	case 0:
		task->tk_action = rpc_exit_task;
		task->tk_status = rpcauth_unwrap_resp(task, &xdr);
		xdr_finish_decode(&xdr);
		return;
	case -EAGAIN:
		task->tk_status = 0;
+2 −0
Original line number Diff line number Diff line
@@ -1370,6 +1370,8 @@ svc_process_common(struct svc_rqst *rqstp)
	rc = process.dispatch(rqstp);
	if (procp->pc_release)
		procp->pc_release(rqstp);
	xdr_finish_decode(xdr);

	if (!rc)
		goto dropit;
	if (rqstp->rq_auth_stat != rpc_auth_ok)
+26 −1
Original line number Diff line number Diff line
@@ -1288,6 +1288,14 @@ static unsigned int xdr_set_tail_base(struct xdr_stream *xdr,
	return xdr_set_iov(xdr, buf->tail, base, len);
}

static void xdr_stream_unmap_current_page(struct xdr_stream *xdr)
{
	if (xdr->page_kaddr) {
		kunmap_local(xdr->page_kaddr);
		xdr->page_kaddr = NULL;
	}
}

static unsigned int xdr_set_page_base(struct xdr_stream *xdr,
				      unsigned int base, unsigned int len)
{
@@ -1305,11 +1313,17 @@ static unsigned int xdr_set_page_base(struct xdr_stream *xdr,
	if (len > maxlen)
		len = maxlen;

	xdr_stream_unmap_current_page(xdr);
	xdr_stream_page_set_pos(xdr, base);
	base += xdr->buf->page_base;

	pgnr = base >> PAGE_SHIFT;
	xdr->page_ptr = &xdr->buf->pages[pgnr];

	if (PageHighMem(*xdr->page_ptr)) {
		xdr->page_kaddr = kmap_local_page(*xdr->page_ptr);
		kaddr = xdr->page_kaddr;
	} else
		kaddr = page_address(*xdr->page_ptr);

	pgoff = base & ~PAGE_MASK;
@@ -1364,6 +1378,7 @@ void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p,
		     struct rpc_rqst *rqst)
{
	xdr->buf = buf;
	xdr->page_kaddr = NULL;
	xdr_reset_scratch_buffer(xdr);
	xdr->nwords = XDR_QUADLEN(buf->len);
	if (xdr_set_iov(xdr, buf->head, 0, buf->len) == 0 &&
@@ -1396,6 +1411,16 @@ void xdr_init_decode_pages(struct xdr_stream *xdr, struct xdr_buf *buf,
}
EXPORT_SYMBOL_GPL(xdr_init_decode_pages);

/**
 * xdr_finish_decode - Clean up the xdr_stream after decoding data.
 * @xdr: pointer to xdr_stream struct
 */
void xdr_finish_decode(struct xdr_stream *xdr)
{
	xdr_stream_unmap_current_page(xdr);
}
EXPORT_SYMBOL(xdr_finish_decode);

static __be32 * __xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)
{
	unsigned int nwords = XDR_QUADLEN(nbytes);