Commit 15261b91 authored by Chuck Lever's avatar Chuck Lever Committed by Trond Myklebust
Browse files

xprtrdma: Fix XDRBUF_SPARSE_PAGES support



Olga K. observed that rpcrdma_marsh_req() allocates sparse pages
only when it has determined that a Reply chunk is necessary. There
are plenty of cases where no Reply chunk is needed, but the
XDRBUF_SPARSE_PAGES flag is set. The result would be a crash in
rpcrdma_inline_fixup() when it tries to copy parts of the received
Reply into a missing page.

To avoid crashing, handle sparse page allocation up front.

Until XATTR support was added, this issue did not appear often
because the only SPARSE_PAGES consumer always expected a reply large
enough to always require a Reply chunk.

Reported-by: default avatarOlga Kornievskaia <kolga@netapp.com>
Signed-off-by: default avatarChuck Lever <chuck.lever@oracle.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarTrond Myklebust <trond.myklebust@hammerspace.com>
parent a1f26739
Loading
Loading
Loading
Loading
+31 −9
Original line number Diff line number Diff line
@@ -179,6 +179,31 @@ rpcrdma_nonpayload_inline(const struct rpcrdma_xprt *r_xprt,
		r_xprt->rx_ep->re_max_inline_recv;
}

/* ACL likes to be lazy in allocating pages. For TCP, these
 * pages can be allocated during receive processing. Not true
 * for RDMA, which must always provision receive buffers
 * up front.
 */
static noinline int
rpcrdma_alloc_sparse_pages(struct xdr_buf *buf)
{
	struct page **ppages;
	int len;

	len = buf->page_len;
	ppages = buf->pages + (buf->page_base >> PAGE_SHIFT);
	while (len > 0) {
		if (!*ppages)
			*ppages = alloc_page(GFP_NOWAIT | __GFP_NOWARN);
		if (!*ppages)
			return -ENOBUFS;
		ppages++;
		len -= PAGE_SIZE;
	}

	return 0;
}

/* Split @vec on page boundaries into SGEs. FMR registers pages, not
 * a byte range. Other modes coalesce these SGEs into a single MR
 * when they can.
@@ -233,15 +258,6 @@ rpcrdma_convert_iovs(struct rpcrdma_xprt *r_xprt, struct xdr_buf *xdrbuf,
	ppages = xdrbuf->pages + (xdrbuf->page_base >> PAGE_SHIFT);
	page_base = offset_in_page(xdrbuf->page_base);
	while (len) {
		/* ACL likes to be lazy in allocating pages - ACLs
		 * are small by default but can get huge.
		 */
		if (unlikely(xdrbuf->flags & XDRBUF_SPARSE_PAGES)) {
			if (!*ppages)
				*ppages = alloc_page(GFP_NOWAIT | __GFP_NOWARN);
			if (!*ppages)
				return -ENOBUFS;
		}
		seg->mr_page = *ppages;
		seg->mr_offset = (char *)page_base;
		seg->mr_len = min_t(u32, PAGE_SIZE - page_base, len);
@@ -867,6 +883,12 @@ rpcrdma_marshal_req(struct rpcrdma_xprt *r_xprt, struct rpc_rqst *rqst)
	__be32 *p;
	int ret;

	if (unlikely(rqst->rq_rcv_buf.flags & XDRBUF_SPARSE_PAGES)) {
		ret = rpcrdma_alloc_sparse_pages(&rqst->rq_rcv_buf);
		if (ret)
			return ret;
	}

	rpcrdma_set_xdrlen(&req->rl_hdrbuf, 0);
	xdr_init_encode(xdr, &req->rl_hdrbuf, rdmab_data(req->rl_rdmabuf),
			rqst);