From 88be621fc687d85891636ed4695ab993e1f2cb3b Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Fri, 23 Jun 2023 11:43:14 -0400 Subject: [PATCH] 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. Reported-by: Krzysztof Kozlowski Signed-off-by: Anna Schumaker --- include/linux/sunrpc/xdr.h | 2 ++ net/sunrpc/clnt.c | 1 + net/sunrpc/xdr.c | 20 ++++++++++++++------ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h index d917618a3058..f562aab468f5 100644 --- a/include/linux/sunrpc/xdr.h +++ b/include/linux/sunrpc/xdr.h @@ -228,6 +228,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 */ @@ -254,6 +255,7 @@ extern void xdr_truncate_decode(struct xdr_stream *xdr, size_t len); extern int xdr_restrict_buflen(struct xdr_stream *xdr, int newbuflen); extern void xdr_write_pages(struct xdr_stream *xdr, struct page **pages, unsigned int base, unsigned int len); +extern void xdr_stream_unmap_current_page(struct xdr_stream *xdr); extern unsigned int xdr_stream_pos(const struct xdr_stream *xdr); extern unsigned int xdr_page_pos(const struct xdr_stream *xdr); extern void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c index d2ee56634308..3b7e676d8935 100644 --- a/net/sunrpc/clnt.c +++ b/net/sunrpc/clnt.c @@ -2590,6 +2590,7 @@ call_decode(struct rpc_task *task) case 0: task->tk_action = rpc_exit_task; task->tk_status = rpcauth_unwrap_resp(task, &xdr); + xdr_stream_unmap_current_page(&xdr); return; case -EAGAIN: task->tk_status = 0; diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index 391b336d97de..b073f51e6018 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -1308,6 +1308,14 @@ static unsigned int xdr_set_tail_base(struct xdr_stream *xdr, return xdr_set_iov(xdr, buf->tail, base, len); } +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) { @@ -1315,7 +1323,6 @@ static unsigned int xdr_set_page_base(struct xdr_stream *xdr, unsigned int maxlen; unsigned int pgoff; unsigned int pgend; - void *kaddr; maxlen = xdr->buf->page_len; if (base >= maxlen) @@ -1330,15 +1337,15 @@ static unsigned int xdr_set_page_base(struct xdr_stream *xdr, pgnr = base >> PAGE_SHIFT; xdr->page_ptr = &xdr->buf->pages[pgnr]; - kaddr = page_address(*xdr->page_ptr); + xdr->page_kaddr = kmap_local_page(*xdr->page_ptr); pgoff = base & ~PAGE_MASK; - xdr->p = (__be32*)(kaddr + pgoff); + xdr->p = (__be32*)(xdr->page_kaddr + pgoff); pgend = pgoff + len; if (pgend > PAGE_SIZE) pgend = PAGE_SIZE; - xdr->end = (__be32*)(kaddr + pgend); + xdr->end = (__be32*)(xdr->page_kaddr + pgend); xdr->iov = NULL; return len; } @@ -1366,9 +1373,10 @@ static void xdr_set_next_page(struct xdr_stream *xdr) static bool xdr_set_next_buffer(struct xdr_stream *xdr) { - if (xdr->page_ptr != NULL) + if (xdr->page_ptr != NULL) { + xdr_stream_unmap_current_page(xdr); xdr_set_next_page(xdr); - else if (xdr->iov == xdr->buf->head) + } else if (xdr->iov == xdr->buf->head) xdr_set_page(xdr, 0, xdr_stream_remaining(xdr)); return xdr->p != xdr->end; } -- 2.41.0