SUNRPC: kmap() the xdr pages during decode
authorAnna Schumaker <Anna.Schumaker@Netapp.com>
Fri, 23 Jun 2023 15:43:14 +0000 (11:43 -0400)
committerAnna Schumaker <Anna.Schumaker@Netapp.com>
Wed, 23 Aug 2023 19:58:47 +0000 (15:58 -0400)
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: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
include/linux/sunrpc/xdr.h
net/sunrpc/clnt.c
net/sunrpc/svc.c
net/sunrpc/xdr.c

index f89ec4b5ea1693b0a814e420752d49740ec07e38..adc844db1ea52d01c5337eec66a1325169cb48f8 100644 (file)
@@ -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);
index d7c697af3762f69b8406f3d60a277f3f3f72b53a..ca2c6efe19c956344b31250a355115c67d673979 100644 (file)
@@ -2602,6 +2602,7 @@ out:
        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;
index 587811a002c98bf90bbc57c3109a33143ba9f2c8..a864414ce811951103ce66de0f841b8762dbfb58 100644 (file)
@@ -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)
index 2a22e78af116e76e8c9f1e366fa33fc6d5811504..f5011344dfe78264a6c20ce729fcf12fe369e272 100644 (file)
@@ -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,12 +1313,18 @@ 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];
-       kaddr = page_address(*xdr->page_ptr);
+
+       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;
        xdr->p = (__be32*)(kaddr + pgoff);
@@ -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);