[PATCH 1/2] NFSv4.1/pnfs: bound notification bitmap length in decode_getdeviceinfo

Michael Bommarito <[email protected]>
Newsgroups gmane.linux.kernel,gmane.linux.nfs
Message-ID <[email protected]>
decode_getdeviceinfo() reads a server-supplied notification bitmap
length into a u32 and scales it by four for xdr_inline_decode(). The
multiply is 32-bit and wraps to 0 for len >= 0x40000000, so the !p
bounds check passes on an empty request and the verify loop reads up to
~2^30 words past the inline XDR buffer. A malicious or compromised pNFS
server, or a man in the middle on an unprotected mount, can drive this
out-of-bounds read while the client decodes a GETDEVICEINFO reply.

RFC 8881 defines only the CHANGE and DELETE deviceid notification bits,
both in word 0, so a conformant reply uses a single word. Bound len to
NFS4_GETDEVICEINFO_NOTIFY_MAXWORDS before scaling, which cannot wrap and
still tolerates trailing zero words the decoder keeps verifying.

Reproduced under KASAN on QEMU via a KUnit case driving the real
decode_getdeviceinfo(); the out-of-bounds read is gone after this change.

Fixes: b1f69b754ee3 ("NFSv4.1: pnfs: add LAYOUTGET and GETDEVICEINFO infrastructure")
Cc: [email protected]
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <[email protected]>
---

Reproduction (v7.1-rc4, x86_64 QEMU/KVM, KASAN):

  BUG: KASAN: slab-out-of-bounds in decode_getdeviceinfo

A KUnit case (patch 2) drives the real decode_getdeviceinfo() with a
0x40000000 notification length placed at a page edge; the wrapped 4*len
multiply over-reads the receive buffer on stock and faults. Patched, the
length is rejected with -EIO. Two benign in-bounds controls pass on both
stock and patched. fs/nfs has no existing kselftest for this decoder.

 fs/nfs/nfs4xdr.c     | 6 +++++-
 include/linux/nfs4.h | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index c23c2eee1b5c4..ca84d0c872a6c 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -6089,7 +6089,11 @@ static int decode_getdeviceinfo(struct xdr_stream *xdr,
 	if (len) {
 		uint32_t i;
 
-		p = xdr_inline_decode(xdr, 4 * len);
+		/* Bound len so len << 2 cannot wrap and defeat the check below. */
+		if (len > NFS4_GETDEVICEINFO_NOTIFY_MAXWORDS)
+			return -EIO;
+
+		p = xdr_inline_decode(xdr, len << 2);
 		if (unlikely(!p))
 			return -EIO;
 
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index d87be1f25273a..d6a84c4545864 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -752,6 +752,9 @@ enum pnfs_notify_deviceid_type4 {
 	NOTIFY_DEVICEID4_DELETE = 1 << 2,
 };
 
+/* All defined deviceid notification bits live in word 0; bound the decoder. */
+#define NFS4_GETDEVICEINFO_NOTIFY_MAXWORDS	4
+
 enum pnfs_block_volume_type {
 	PNFS_BLOCK_VOLUME_SIMPLE	= 0,
 	PNFS_BLOCK_VOLUME_SLICE		= 1,
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.