[PATCH] NFSv4.1: fix out-of-bounds read decoding GETDEVICEINFO notification bitmap
Chuyf26 <[email protected]>
| Newsgroups | org.kernel.vger.linux-nfs |
|---|---|
| Message-ID | <20260818162429.hRkBfHzaSOJtsemwF3XqknX_wBRwf6WfB8WnVDvY5Rg@z> |
decode_getdeviceinfo() passes 4 * len to a single xdr_inline_decode()
call, where len is the word count of the notification bitmap supplied
by the server. The multiplication can wrap around to a small value,
so the bounds check only covers a few bytes while the following loop
reads len words from the resulting pointer. A malicious NFS server
can trigger a large out-of-bounds read in the client by replying to
GETDEVICEINFO with a huge bitmap word count.
The path is: a pNFS client issues GETDEVICEINFO while discovering a
layout, and the reply is decoded by nfs4_xdr_dec_getdeviceinfo() ->
decode_getdeviceinfo(). With a wrapped 4 * len the pointer returned
by xdr_inline_decode() sits at the very end of the receive buffer, and
the loop then reads up to nearly a gigabyte of words past it. The XDR
receive buffer is built from pages, not slab objects, so KASAN does
not instrument these reads; depending on what follows in memory the
loop either consumes garbage notification bits or walks into unmapped
memory.
Decode the bitmap one word at a time so every read is bounds checked.
Fixes: 4e59080397fa ("NFSv4.1: Allow getdeviceinfo to return notification info back to caller")
Reported-by: Abaci <[email protected]>
Assisted-by: abaci:qwen3.8-max
Signed-off-by: Chuyf26 <[email protected]>
---
fs/nfs/nfs4xdr.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index c8e073ef1d37..582f571689dc 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -5958,13 +5958,13 @@ static int decode_getdeviceinfo(struct xdr_stream *xdr,
if (len) {
uint32_t i;
- p = xdr_inline_decode(xdr, 4 * len);
- if (unlikely(!p))
- return -EIO;
-
- res->notification = be32_to_cpup(p++);
- for (i = 1; i < len; i++) {
- if (be32_to_cpup(p++)) {
+ for (i = 0; i < len; i++) {
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(!p))
+ return -EIO;
+ if (i == 0)
+ res->notification = be32_to_cpup(p);
+ else if (be32_to_cpup(p)) {
dprintk("%s: unsupported notification\n",
__func__);
return -EIO;
--
2.43.5