Linux NFS development
 help / color / mirror / Atom feed
* [PATCH 0/4] NFS: harden pNFS device address decoding
@ 2026-08-23 11:42 Prabhakar Pujeri
  2026-08-23 11:42 ` [PATCH 1/4] NFS: validate pNFS data server port octets Prabhakar Pujeri
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-23 11:42 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs, Prabhakar Pujeri

pNFS GETDEVICEINFO replies contain server-provided universal addresses
and multipath counts. The current decoders accept malformed port octets
and can spend billions of iterations on a count that is not bounded by
the reply length.

Patch 1 validates both decimal port octets. Patches 2 and 3 bound the
file-layout and flexfiles multipath loops by the minimum XDR space needed
for each remaining netaddr4. Patch 4 adds focused KUnit coverage.

This series is based on the NFS client linux-next integration commit
10f307e525a1, as published in next-20260821.

Tested on a Dell PowerEdge R660: the pNFS decoder KUnit suite passed
16/16 and the flexfiles suite passed 3/3, with an empty kmemleak scan.
No pNFS-capable server was available, so the affected decoder paths were
exercised directly by KUnit.

NFSv3, v4.1, and v4.2 data, locking, and parallel workloads passed,
together with NFSv3 ACL and NFSv4.2 xattr checks. The series builds
cleanly with GCC and Clang W=1 and passes Sparse C=2. Strict checkpatch
is clean


Prabhakar Pujeri (4):
  NFS: validate pNFS data server port octets
  NFS: bound multipath address count in file-layout GETDEVICEINFO
  NFS: bound multipath address count in flexfiles GETDEVICEINFO
  NFS: add KUnit coverage for pNFS address decoding

 fs/nfs/Kconfig                               |  34 +++
 fs/nfs/Makefile                              |   3 +
 fs/nfs/filelayout/filelayoutdev.c            |   5 +
 fs/nfs/flexfilelayout/Makefile               |   3 +
 fs/nfs/flexfilelayout/flexfilelayoutdev.c    |   9 +
 fs/nfs/flexfilelayout/tests/deviceid_kunit.c | 188 ++++++++++++++++
 fs/nfs/pnfs.h                                |   8 +
 fs/nfs/pnfs_nfs.c                            |  47 ++--
 fs/nfs/tests/pnfs_decode_kunit.c             | 222 +++++++++++++++++++
 9 files changed, 500 insertions(+), 19 deletions(-)
 create mode 100644 fs/nfs/flexfilelayout/tests/deviceid_kunit.c
 create mode 100644 fs/nfs/tests/pnfs_decode_kunit.c


base-commit: 10f307e525a1783570a39eb9ac146d45f4f16b3e
-- 
2.54.0

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] NFS: validate pNFS data server port octets
  2026-08-23 11:42 [PATCH 0/4] NFS: harden pNFS device address decoding Prabhakar Pujeri
@ 2026-08-23 11:42 ` Prabhakar Pujeri
  2026-08-23 11:42 ` [PATCH 2/4] NFS: bound multipath address count in file-layout GETDEVICEINFO Prabhakar Pujeri
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-23 11:42 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs, Prabhakar Pujeri, stable

nfs4_decode_mp_ds_addr() parses the two decimal port octets in a
server-provided universal address with an unchecked sscanf(). A missing
or non-numeric field leaves part of the temporary array uninitialized,
while values outside the octet range are silently folded into the
resulting port.

Split the two port fields at their delimiters and parse each with
kstrtou8(). This rejects missing fields, trailing garbage, integer
overflow, and values outside 0 through 255 before constructing the data
server socket address.

Fixes: 6b7f3cf96364 ("nfs41: pull decode_ds_addr from file layout to generic pnfs")
Cc: stable@vger.kernel.org
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
 fs/nfs/pnfs_nfs.c | 47 ++++++++++++++++++++++++++++-------------------
 1 file changed, 28 insertions(+), 19 deletions(-)

diff --git a/fs/nfs/pnfs_nfs.c b/fs/nfs/pnfs_nfs.c
index b539e1a44d26..0c8c50eab4ad 100644
--- a/fs/nfs/pnfs_nfs.c
+++ b/fs/nfs/pnfs_nfs.c
@@ -8,6 +8,7 @@
  * Tom Haynes <loghyr@primarydata.com>
  */
 
+#include <linux/ctype.h>
 #include <linux/nfs_fs.h>
 #include <linux/nfs_page.h>
 #include <linux/sunrpc/addr.h>
@@ -1059,6 +1060,19 @@ int nfs4_pnfs_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds,
 }
 EXPORT_SYMBOL_GPL(nfs4_pnfs_ds_connect);
 
+static int
+nfs4_parse_port_octet(const char *str, u8 *port)
+{
+	const char *p;
+
+	if (!*str)
+		return -EINVAL;
+	for (p = str; *p; p++)
+		if (!isdigit(*p))
+			return -EINVAL;
+	return kstrtou8(str, 10, port);
+}
+
 /*
  * Currently only supports ipv4, ipv6 and one multi-path address.
  */
@@ -1066,10 +1080,10 @@ struct nfs4_pnfs_ds_addr *
 nfs4_decode_mp_ds_addr(struct net *net, struct xdr_stream *xdr, gfp_t gfp_flags)
 {
 	struct nfs4_pnfs_ds_addr *da = NULL;
-	char *buf, *portstr;
+	char *buf, *portsep;
 	__be16 port;
 	ssize_t nlen, rlen;
-	int tmp[2];
+	u8 porthi, portlo;
 	char *netid;
 	size_t len;
 	char *startsep = "";
@@ -1089,37 +1103,33 @@ nfs4_decode_mp_ds_addr(struct net *net, struct xdr_stream *xdr, gfp_t gfp_flags)
 	if (unlikely(rlen <= 0))
 		goto out_free_netid;
 
-	/* replace port '.' with '-' */
-	portstr = strrchr(buf, '.');
-	if (!portstr) {
-		dprintk("%s: Failed finding expected dot in port\n",
-			__func__);
+	/* Parse the low port octet and remove it from the address string. */
+	portsep = strrchr(buf, '.');
+	if (!portsep || nfs4_parse_port_octet(portsep + 1, &portlo)) {
+		dprintk("%s: Failed parsing low port octet\n", __func__);
 		goto out_free_buf;
 	}
-	*portstr = '-';
+	*portsep = '\0';
 
-	/* find '.' between address and port */
-	portstr = strrchr(buf, '.');
-	if (!portstr) {
-		dprintk("%s: Failed finding expected dot between address and "
-			"port\n", __func__);
+	/* Parse the high port octet and terminate the address string. */
+	portsep = strrchr(buf, '.');
+	if (!portsep || nfs4_parse_port_octet(portsep + 1, &porthi)) {
+		dprintk("%s: Failed parsing high port octet\n", __func__);
 		goto out_free_buf;
 	}
-	*portstr = '\0';
+	*portsep = '\0';
 
 	da = nfs4_pnfs_ds_addr_alloc(gfp_flags);
 	if (unlikely(!da))
 		goto out_free_buf;
 
-	if (!rpc_pton(net, buf, portstr-buf, (struct sockaddr *)&da->da_addr,
+	if (!rpc_pton(net, buf, portsep - buf, (struct sockaddr *)&da->da_addr,
 		      sizeof(da->da_addr))) {
 		dprintk("%s: error parsing address %s\n", __func__, buf);
 		goto out_free_da;
 	}
 
-	portstr++;
-	sscanf(portstr, "%d-%d", &tmp[0], &tmp[1]);
-	port = htons((tmp[0] << 8) | (tmp[1]));
+	port = htons((porthi << 8) | portlo);
 
 	switch (da->da_addr.ss_family) {
 	case AF_INET:
@@ -1226,4 +1236,3 @@ pnfs_nfs_generic_sync(struct inode *inode, bool datasync)
 	return pnfs_layoutcommit_inode(inode, true);
 }
 EXPORT_SYMBOL_GPL(pnfs_nfs_generic_sync);
-
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/4] NFS: bound multipath address count in file-layout GETDEVICEINFO
  2026-08-23 11:42 [PATCH 0/4] NFS: harden pNFS device address decoding Prabhakar Pujeri
  2026-08-23 11:42 ` [PATCH 1/4] NFS: validate pNFS data server port octets Prabhakar Pujeri
@ 2026-08-23 11:42 ` Prabhakar Pujeri
  2026-08-23 11:42 ` [PATCH 3/4] NFS: bound multipath address count in flexfiles GETDEVICEINFO Prabhakar Pujeri
  2026-08-23 11:42 ` [PATCH 4/4] NFS: add KUnit coverage for pNFS address decoding Prabhakar Pujeri
  3 siblings, 0 replies; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-23 11:42 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs, Prabhakar Pujeri, stable

nfs4_fl_alloc_deviceid_node() bounds the number of data-server multipath
lists, but not the number of addresses in each list. The latter count is
supplied by the server and can be as large as U32_MAX.

Once the XDR stream is exhausted, nfs4_decode_mp_ds_addr() returns NULL
without consuming input. The enclosing loop can therefore spin for
billions of iterations in kernel context when decoding a malicious
GETDEVICEINFO response.

A netaddr4 contains at least the two XDR length words for its netid and
universal-address strings. Reject a count when that minimum representation
cannot fit in the remaining XDR stream. This makes the loop bound
proportional to the reply length without imposing an arbitrary protocol
limit.

Keep the existing file-layout multipath-list limit separate because that
limit is tied to the u8 stripe-index representation.

Fixes: 14f9a6076f53 ("NFS: Parse and store all multipath DS addresses")
Cc: stable@vger.kernel.org
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
 fs/nfs/filelayout/filelayoutdev.c | 5 +++++
 fs/nfs/pnfs.h                     | 8 ++++++++
 2 files changed, 13 insertions(+)

diff --git a/fs/nfs/filelayout/filelayoutdev.c b/fs/nfs/filelayout/filelayoutdev.c
index d06d303fdcc3..d44baac25d43 100644
--- a/fs/nfs/filelayout/filelayoutdev.c
+++ b/fs/nfs/filelayout/filelayoutdev.c
@@ -159,6 +159,11 @@ nfs4_fl_alloc_deviceid_node(struct nfs_server *server, struct pnfs_device *pdev,
 			goto out_err_free_deviceid;
 
 		mp_count = be32_to_cpup(p); /* multipath count */
+		if (!nfs4_pnfs_ds_addr_count_valid(&stream, mp_count)) {
+			pr_warn_ratelimited("NFS: %s: multipath address count %u exceeds XDR capacity\n",
+					    __func__, mp_count);
+			goto out_err_drain_dsaddrs;
+		}
 		for (j = 0; j < mp_count; j++) {
 			da = nfs4_decode_mp_ds_addr(net, &stream, gfp_flags);
 			if (da)
diff --git a/fs/nfs/pnfs.h b/fs/nfs/pnfs.h
index bab81f769636..38df700e6a0c 100644
--- a/fs/nfs/pnfs.h
+++ b/fs/nfs/pnfs.h
@@ -33,11 +33,19 @@
 #include <linux/refcount.h>
 #include <linux/nfs_fs.h>
 #include <linux/nfs_page.h>
+#include <linux/sunrpc/xdr.h>
 #include <linux/workqueue.h>
 
 struct nfs4_exception;
 struct nfs4_opendata;
 
+/* A netaddr4 contains at least the length words of its two XDR strings. */
+static inline bool
+nfs4_pnfs_ds_addr_count_valid(const struct xdr_stream *xdr, u32 count)
+{
+	return count <= xdr_stream_remaining(xdr) / (2 * sizeof(__be32));
+}
+
 enum {
 	NFS_LSEG_VALID = 0,	/* cleared when lseg is recalled/returned */
 	NFS_LSEG_ROC,		/* roc bit received from server */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 3/4] NFS: bound multipath address count in flexfiles GETDEVICEINFO
  2026-08-23 11:42 [PATCH 0/4] NFS: harden pNFS device address decoding Prabhakar Pujeri
  2026-08-23 11:42 ` [PATCH 1/4] NFS: validate pNFS data server port octets Prabhakar Pujeri
  2026-08-23 11:42 ` [PATCH 2/4] NFS: bound multipath address count in file-layout GETDEVICEINFO Prabhakar Pujeri
@ 2026-08-23 11:42 ` Prabhakar Pujeri
  2026-08-23 11:42 ` [PATCH 4/4] NFS: add KUnit coverage for pNFS address decoding Prabhakar Pujeri
  3 siblings, 0 replies; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-23 11:42 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs, Prabhakar Pujeri, stable

nfs4_ff_alloc_deviceid_node() loops over a server-provided multipath
address count without validating it. Once the XDR stream is exhausted,
nfs4_decode_mp_ds_addr() returns NULL without consuming input, so a count
near U32_MAX can keep the client spinning in kernel context for billions
of iterations.

A netaddr4 contains at least the two XDR length words for its netid and
universal-address strings. Reject a count when that minimum representation
cannot fit in the remaining XDR stream. This makes the loop bound
proportional to the reply length without imposing an arbitrary protocol
limit, matching the protection in the file-layout decoder.

Fixes: d67ae825a59d ("pnfs/flexfiles: Add the FlexFile Layout Driver")
Cc: stable@vger.kernel.org
Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
 fs/nfs/flexfilelayout/flexfilelayoutdev.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/nfs/flexfilelayout/flexfilelayoutdev.c b/fs/nfs/flexfilelayout/flexfilelayoutdev.c
index 8be5c730e101..181a5854fa37 100644
--- a/fs/nfs/flexfilelayout/flexfilelayoutdev.c
+++ b/fs/nfs/flexfilelayout/flexfilelayoutdev.c
@@ -78,6 +78,11 @@ nfs4_ff_alloc_deviceid_node(struct nfs_server *server, struct pnfs_device *pdev,
 		goto out_err_drain_dsaddrs;
 	mp_count = be32_to_cpup(p);
 	dprintk("%s: multipath ds count %d\n", __func__, mp_count);
+	if (!nfs4_pnfs_ds_addr_count_valid(&stream, mp_count)) {
+		pr_warn_ratelimited("NFS: %s: multipath address count %u exceeds XDR capacity\n",
+				    __func__, mp_count);
+		goto out_err_drain_dsaddrs;
+	}
 
 	for (i = 0; i < mp_count; i++) {
 		/* multipath ds */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 4/4] NFS: add KUnit coverage for pNFS address decoding
  2026-08-23 11:42 [PATCH 0/4] NFS: harden pNFS device address decoding Prabhakar Pujeri
                   ` (2 preceding siblings ...)
  2026-08-23 11:42 ` [PATCH 3/4] NFS: bound multipath address count in flexfiles GETDEVICEINFO Prabhakar Pujeri
@ 2026-08-23 11:42 ` Prabhakar Pujeri
  3 siblings, 0 replies; 5+ messages in thread
From: Prabhakar Pujeri @ 2026-08-23 11:42 UTC (permalink / raw)
  To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs, Prabhakar Pujeri

Add page-backed XDR tests for nfs4_decode_mp_ds_addr(). Cover valid IPv4
and IPv6 universal addresses, boundary port values, missing fields,
non-numeric fields, out-of-range values, integer overflow, and trailing
garbage.

Add a separate flexfiles GETDEVICEINFO test module that exercises a valid
single-address payload and verifies that zero and U32_MAX multipath
counts are rejected. Export the two flexfiles decoder helpers only when
KUnit is enabled.

Keep both suites behind guideline-compliant Kconfig entries that default
with KUNIT_ALL_TESTS and can be built as modules. Manage the flexfiles
fixtures and decoded devices as KUnit resources so assertion failures do
not leak them.

Signed-off-by: Prabhakar Pujeri <prabhakar.pujeri@dell.com>
---
 fs/nfs/Kconfig                               |  34 +++
 fs/nfs/Makefile                              |   3 +
 fs/nfs/flexfilelayout/Makefile               |   3 +
 fs/nfs/flexfilelayout/flexfilelayoutdev.c    |   4 +
 fs/nfs/flexfilelayout/tests/deviceid_kunit.c | 188 ++++++++++++++++
 fs/nfs/tests/pnfs_decode_kunit.c             | 222 +++++++++++++++++++
 6 files changed, 454 insertions(+)
 create mode 100644 fs/nfs/flexfilelayout/tests/deviceid_kunit.c
 create mode 100644 fs/nfs/tests/pnfs_decode_kunit.c

diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig
index 6bb30543eff0..0d7a01f7e10b 100644
--- a/fs/nfs/Kconfig
+++ b/fs/nfs/Kconfig
@@ -215,3 +215,37 @@ config NFS_V4_2_READ_PLUS
 	default y
 	help
 	 Choose Y here to enable use of the NFS v4.2 READ_PLUS operation.
+
+config NFS_PNFS_DECODE_KUNIT_TEST
+	tristate "KUnit tests for pNFS data server address decoding" if !KUNIT_ALL_TESTS
+	depends on NFS_V4 && KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  This builds KUnit tests for the pNFS data server address decoder.
+	  The tests cover valid IPv4 and IPv6 universal addresses and malformed
+	  port fields supplied by an NFS server. They verify that invalid port
+	  octets cannot produce a usable data server socket address.
+	  Results are reported in TAP format when the test module is loaded on a
+	  KUnit-enabled kernel.
+
+	  For more information on KUnit and unit tests in general, refer to
+	  Documentation/dev-tools/kunit/.
+
+	  If unsure, say N.
+
+config NFS_FLEXFILE_DEVICEID_KUNIT_TEST
+	tristate "KUnit tests for flexfiles GETDEVICEINFO decoding" if !KUNIT_ALL_TESTS
+	depends on PNFS_FLEXFILE_LAYOUT && KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  This builds KUnit tests for the flexfiles GETDEVICEINFO decoder.
+	  The tests cover valid device information and malformed multipath
+	  address counts supplied by an NFS server. They verify that oversized
+	  counts are rejected before entering the address decode loop.
+	  Results are reported in TAP format when the test module is loaded on a
+	  KUnit-enabled kernel.
+
+	  For more information on KUnit and unit tests in general, refer to
+	  Documentation/dev-tools/kunit/.
+
+	  If unsure, say N.
diff --git a/fs/nfs/Makefile b/fs/nfs/Makefile
index c895521f27f3..e68648f236b0 100644
--- a/fs/nfs/Makefile
+++ b/fs/nfs/Makefile
@@ -33,6 +33,9 @@ nfsv4-$(CONFIG_SYSCTL)	+= nfs4sysctl.o
 nfsv4-$(CONFIG_NFS_V4_0)	+= nfs40client.o nfs40proc.o
 nfsv4-$(CONFIG_NFS_V4_2)	+= nfs42proc.o nfs42xattr.o
 
+obj-$(CONFIG_NFS_PNFS_DECODE_KUNIT_TEST) += nfs_pnfs_decode_kunit.o
+nfs_pnfs_decode_kunit-y := tests/pnfs_decode_kunit.o
+
 obj-$(CONFIG_PNFS_FILE_LAYOUT) += filelayout/
 obj-$(CONFIG_PNFS_BLOCK) += blocklayout/
 obj-$(CONFIG_PNFS_FLEXFILE_LAYOUT) += flexfilelayout/
diff --git a/fs/nfs/flexfilelayout/Makefile b/fs/nfs/flexfilelayout/Makefile
index 49f03422b6ad..8dcc8fad4106 100644
--- a/fs/nfs/flexfilelayout/Makefile
+++ b/fs/nfs/flexfilelayout/Makefile
@@ -4,3 +4,6 @@
 #
 obj-$(CONFIG_PNFS_FLEXFILE_LAYOUT) += nfs_layout_flexfiles.o
 nfs_layout_flexfiles-y := flexfilelayout.o flexfilelayoutdev.o
+
+obj-$(CONFIG_NFS_FLEXFILE_DEVICEID_KUNIT_TEST) += nfs_flexfile_deviceid_kunit.o
+nfs_flexfile_deviceid_kunit-y := tests/deviceid_kunit.o
diff --git a/fs/nfs/flexfilelayout/flexfilelayoutdev.c b/fs/nfs/flexfilelayout/flexfilelayoutdev.c
index 181a5854fa37..1979a8017ad3 100644
--- a/fs/nfs/flexfilelayout/flexfilelayoutdev.c
+++ b/fs/nfs/flexfilelayout/flexfilelayoutdev.c
@@ -12,6 +12,8 @@
 #include <linux/module.h>
 #include <linux/sunrpc/addr.h>
 
+#include <kunit/visibility.h>
+
 #include "../internal.h"
 #include "../nfs4session.h"
 #include "flexfilelayout.h"
@@ -36,6 +38,7 @@ void nfs4_ff_layout_free_deviceid(struct nfs4_ff_layout_ds *mirror_ds)
 	kfree(mirror_ds->ds_versions);
 	kfree_rcu(mirror_ds, id_node.rcu);
 }
+EXPORT_SYMBOL_IF_KUNIT(nfs4_ff_layout_free_deviceid);
 
 /* Decode opaque device data and construct new_ds using it */
 struct nfs4_ff_layout_ds *
@@ -193,6 +196,7 @@ nfs4_ff_alloc_deviceid_node(struct nfs_server *server, struct pnfs_device *pdev,
 	dprintk("%s ERROR: returning %d\n", __func__, ret);
 	return NULL;
 }
+EXPORT_SYMBOL_IF_KUNIT(nfs4_ff_alloc_deviceid_node);
 
 static void extend_ds_error(struct nfs4_ff_layout_ds_err *err,
 			    u64 offset, u64 length)
diff --git a/fs/nfs/flexfilelayout/tests/deviceid_kunit.c b/fs/nfs/flexfilelayout/tests/deviceid_kunit.c
new file mode 100644
index 000000000000..d45fb80fd179
--- /dev/null
+++ b/fs/nfs/flexfilelayout/tests/deviceid_kunit.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for the NFS flexfiles GETDEVICEINFO decoder.
+ */
+
+#include <kunit/test.h>
+#include <linux/mm.h>
+#include <net/net_namespace.h>
+
+#include "../flexfilelayout.h"
+
+static void flexfile_deviceid_put_u32(char **p, u32 value)
+{
+	*(__be32 *)*p = cpu_to_be32(value);
+	*p += XDR_UNIT;
+}
+
+static void flexfile_deviceid_put_string(char **p, const char *string)
+{
+	size_t len = strlen(string);
+
+	flexfile_deviceid_put_u32(p, len);
+	memcpy(*p, string, len);
+	*p += xdr_align_size(len);
+}
+
+static struct nfs_server *flexfile_deviceid_server(struct kunit *test)
+{
+	struct nfs_server *server;
+	struct nfs_client *client;
+
+	server = kunit_kzalloc(test, sizeof(*server), GFP_KERNEL);
+	if (!server)
+		return NULL;
+	client = kunit_kzalloc(test, sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return NULL;
+
+	client->cl_net = &init_net;
+	client->cl_proto = IPPROTO_TCP;
+	server->nfs_client = client;
+	return server;
+}
+
+static struct pnfs_device *
+flexfile_deviceid_pdev(struct kunit *test, struct page *page, size_t len)
+{
+	struct pnfs_device *pdev;
+
+	pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL);
+	if (!pdev)
+		return NULL;
+	pdev->pages = kunit_kzalloc(test, sizeof(*pdev->pages), GFP_KERNEL);
+	if (!pdev->pages)
+		return NULL;
+	pdev->pages[0] = page;
+	pdev->pglen = len;
+	return pdev;
+}
+
+static void flexfile_deviceid_free_page(void *data)
+{
+	__free_page(data);
+}
+
+static struct page *flexfile_deviceid_page(struct kunit *test)
+{
+	struct page *page;
+
+	page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+	if (!page)
+		return NULL;
+	if (kunit_add_action_or_reset(test, flexfile_deviceid_free_page,
+				      page))
+		return NULL;
+	return page;
+}
+
+static void flexfile_deviceid_free(void *data)
+{
+	nfs4_ff_layout_free_deviceid(data);
+}
+
+static int
+flexfile_deviceid_track(struct kunit *test, struct nfs4_ff_layout_ds *device)
+{
+	if (!device)
+		return 0;
+	return kunit_add_action_or_reset(test, flexfile_deviceid_free, device);
+}
+
+static void flexfile_deviceid_reject_huge_multipath_count(struct kunit *test)
+{
+	struct nfs4_ff_layout_ds *device;
+	struct nfs_server *server;
+	struct pnfs_device *pdev;
+	struct page *page;
+	char *start, *p;
+
+	server = flexfile_deviceid_server(test);
+	KUNIT_ASSERT_NOT_NULL(test, server);
+	page = flexfile_deviceid_page(test);
+	KUNIT_ASSERT_NOT_NULL(test, page);
+	start = page_address(page);
+	p = start;
+	flexfile_deviceid_put_u32(&p, U32_MAX);
+	pdev = flexfile_deviceid_pdev(test, page, p - start);
+	KUNIT_ASSERT_NOT_NULL(test, pdev);
+
+	device = nfs4_ff_alloc_deviceid_node(server, pdev, GFP_KERNEL);
+	KUNIT_ASSERT_EQ(test, flexfile_deviceid_track(test, device), 0);
+	KUNIT_EXPECT_NULL(test, device);
+}
+
+static void flexfile_deviceid_reject_zero_multipath_count(struct kunit *test)
+{
+	struct nfs4_ff_layout_ds *device;
+	struct nfs_server *server;
+	struct pnfs_device *pdev;
+	struct page *page;
+	char *start, *p;
+
+	server = flexfile_deviceid_server(test);
+	KUNIT_ASSERT_NOT_NULL(test, server);
+	page = flexfile_deviceid_page(test);
+	KUNIT_ASSERT_NOT_NULL(test, page);
+	start = page_address(page);
+	p = start;
+	flexfile_deviceid_put_u32(&p, 0);
+	pdev = flexfile_deviceid_pdev(test, page, p - start);
+	KUNIT_ASSERT_NOT_NULL(test, pdev);
+
+	device = nfs4_ff_alloc_deviceid_node(server, pdev, GFP_KERNEL);
+	KUNIT_ASSERT_EQ(test, flexfile_deviceid_track(test, device), 0);
+	KUNIT_EXPECT_NULL(test, device);
+}
+
+static void flexfile_deviceid_decode_single_address(struct kunit *test)
+{
+	struct nfs4_ff_layout_ds *device;
+	struct nfs_server *server;
+	struct pnfs_device *pdev;
+	struct page *page;
+	char *start, *p;
+
+	server = flexfile_deviceid_server(test);
+	KUNIT_ASSERT_NOT_NULL(test, server);
+	page = flexfile_deviceid_page(test);
+	KUNIT_ASSERT_NOT_NULL(test, page);
+	start = page_address(page);
+	p = start;
+	flexfile_deviceid_put_u32(&p, 1);
+	flexfile_deviceid_put_string(&p, "tcp");
+	flexfile_deviceid_put_string(&p, "10.0.0.9.8.1");
+	flexfile_deviceid_put_u32(&p, 1);
+	flexfile_deviceid_put_u32(&p, 4);
+	flexfile_deviceid_put_u32(&p, 2);
+	flexfile_deviceid_put_u32(&p, 1048576);
+	flexfile_deviceid_put_u32(&p, 1048576);
+	flexfile_deviceid_put_u32(&p, 0);
+	pdev = flexfile_deviceid_pdev(test, page, p - start);
+	KUNIT_ASSERT_NOT_NULL(test, pdev);
+
+	device = nfs4_ff_alloc_deviceid_node(server, pdev, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, device);
+	KUNIT_ASSERT_EQ(test, flexfile_deviceid_track(test, device), 0);
+	KUNIT_EXPECT_EQ(test, device->ds_versions_cnt, (u32)1);
+	KUNIT_EXPECT_EQ(test, device->ds_versions[0].version, (u32)4);
+	KUNIT_EXPECT_EQ(test, device->ds_versions[0].minor_version, (u32)2);
+}
+
+static struct kunit_case flexfile_deviceid_test_cases[] = {
+	KUNIT_CASE(flexfile_deviceid_reject_huge_multipath_count),
+	KUNIT_CASE(flexfile_deviceid_reject_zero_multipath_count),
+	KUNIT_CASE(flexfile_deviceid_decode_single_address),
+	{}
+};
+
+static struct kunit_suite flexfile_deviceid_test_suite = {
+	.name = "nfs_flexfile_deviceid",
+	.test_cases = flexfile_deviceid_test_cases,
+};
+
+kunit_test_suite(flexfile_deviceid_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for flexfiles GETDEVICEINFO decoding");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/fs/nfs/tests/pnfs_decode_kunit.c b/fs/nfs/tests/pnfs_decode_kunit.c
new file mode 100644
index 000000000000..74ffe14a089c
--- /dev/null
+++ b/fs/nfs/tests/pnfs_decode_kunit.c
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for pNFS data server address decoding.
+ */
+
+#include <kunit/test.h>
+#include <linux/mm.h>
+#include <linux/sunrpc/xdr.h>
+#include <net/net_namespace.h>
+
+#include "../pnfs.h"
+
+static struct page *
+pnfs_decode_make_stream(const char *netid, const char *addr,
+			struct xdr_stream *xdr, struct xdr_buf *buf,
+			struct page **pages)
+{
+	size_t netid_len = strlen(netid);
+	size_t addr_len = strlen(addr);
+	struct page *page;
+	char *p;
+	size_t offset = 0;
+
+	if (XDR_UNIT * 2 + xdr_align_size(netid_len) +
+	    xdr_align_size(addr_len) > PAGE_SIZE)
+		return ERR_PTR(-E2BIG);
+
+	page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+	if (!page)
+		return ERR_PTR(-ENOMEM);
+	pages[0] = page;
+	p = page_address(page);
+
+	*(__be32 *)(p + offset) = cpu_to_be32(netid_len);
+	offset += XDR_UNIT;
+	memcpy(p + offset, netid, netid_len);
+	offset += xdr_align_size(netid_len);
+	*(__be32 *)(p + offset) = cpu_to_be32(addr_len);
+	offset += XDR_UNIT;
+	memcpy(p + offset, addr, addr_len);
+	offset += xdr_align_size(addr_len);
+
+	xdr_init_decode_pages(xdr, buf, pages, offset);
+	return page;
+}
+
+static void pnfs_decode_free_addr(struct nfs4_pnfs_ds_addr *addr)
+{
+	kfree(addr->da_remotestr);
+	kfree(addr->da_netid);
+	kfree(addr);
+}
+
+static struct nfs4_pnfs_ds_addr *
+pnfs_decode_addr(const char *netid, const char *addr)
+{
+	struct page *pages[1] = { NULL };
+	struct xdr_buf buf = {};
+	struct xdr_stream xdr;
+	struct nfs4_pnfs_ds_addr *decoded;
+	struct page *page;
+
+	page = pnfs_decode_make_stream(netid, addr, &xdr, &buf, pages);
+	if (IS_ERR(page))
+		return ERR_CAST(page);
+
+	decoded = nfs4_decode_mp_ds_addr(&init_net, &xdr, GFP_KERNEL);
+	xdr_finish_decode(&xdr);
+	__free_page(page);
+	return decoded;
+}
+
+static void pnfs_decode_valid_ipv4(struct kunit *test)
+{
+	struct nfs4_pnfs_ds_addr *addr;
+	struct sockaddr_in *sin;
+
+	addr = pnfs_decode_addr("tcp", "10.0.0.4.8.1");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, addr);
+	sin = (struct sockaddr_in *)&addr->da_addr;
+	KUNIT_EXPECT_EQ(test, sin->sin_family, AF_INET);
+	KUNIT_EXPECT_EQ(test, ntohs(sin->sin_port), 2049);
+	KUNIT_EXPECT_EQ(test, be32_to_cpu(sin->sin_addr.s_addr), 0x0a000004);
+	pnfs_decode_free_addr(addr);
+}
+
+static void pnfs_decode_valid_ipv4_max_port(struct kunit *test)
+{
+	struct nfs4_pnfs_ds_addr *addr;
+	struct sockaddr_in *sin;
+
+	addr = pnfs_decode_addr("tcp", "10.0.0.4.255.255");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, addr);
+	sin = (struct sockaddr_in *)&addr->da_addr;
+	KUNIT_EXPECT_EQ(test, ntohs(sin->sin_port), 65535);
+	pnfs_decode_free_addr(addr);
+}
+
+static void pnfs_decode_valid_ipv4_zero_port(struct kunit *test)
+{
+	struct nfs4_pnfs_ds_addr *addr;
+	struct sockaddr_in *sin;
+
+	addr = pnfs_decode_addr("tcp", "10.0.0.4.0.0");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, addr);
+	sin = (struct sockaddr_in *)&addr->da_addr;
+	KUNIT_EXPECT_EQ(test, ntohs(sin->sin_port), 0);
+	pnfs_decode_free_addr(addr);
+}
+
+static void pnfs_decode_valid_ipv6(struct kunit *test)
+{
+	struct nfs4_pnfs_ds_addr *addr;
+	struct sockaddr_in6 *sin6;
+
+	addr = pnfs_decode_addr("tcp6", "2001:db8::5.8.1");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, addr);
+	sin6 = (struct sockaddr_in6 *)&addr->da_addr;
+	KUNIT_EXPECT_EQ(test, sin6->sin6_family, AF_INET6);
+	KUNIT_EXPECT_EQ(test, ntohs(sin6->sin6_port), 2049);
+	pnfs_decode_free_addr(addr);
+}
+
+static void pnfs_decode_expect_invalid(struct kunit *test, const char *addr)
+{
+	struct nfs4_pnfs_ds_addr *decoded = pnfs_decode_addr("tcp", addr);
+
+	KUNIT_ASSERT_FALSE(test, IS_ERR(decoded));
+	KUNIT_EXPECT_NULL(test, decoded);
+	if (decoded)
+		pnfs_decode_free_addr(decoded);
+}
+
+static void pnfs_decode_reject_high_octet_over_255(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.300.1");
+}
+
+static void pnfs_decode_reject_low_octet_over_255(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.1.300");
+}
+
+static void pnfs_decode_reject_integer_overflow(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.999999999999999999999.1");
+}
+
+static void pnfs_decode_reject_empty_low_octet(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.8.");
+}
+
+static void pnfs_decode_reject_empty_high_octet(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4..1");
+}
+
+static void pnfs_decode_reject_negative_octet(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.8.-1");
+}
+
+static void pnfs_decode_reject_leading_plus(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.+8.1");
+}
+
+static void pnfs_decode_reject_non_numeric_octet(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.8.x");
+}
+
+static void pnfs_decode_reject_trailing_garbage(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.8.1x");
+}
+
+static void pnfs_decode_reject_trailing_newline(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.8.1\n");
+}
+
+static void pnfs_decode_reject_bad_address(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "999.1.1.1.8.1");
+}
+
+static void pnfs_decode_reject_missing_port_octet(struct kunit *test)
+{
+	pnfs_decode_expect_invalid(test, "10.0.0.4.8");
+}
+
+static struct kunit_case pnfs_decode_test_cases[] = {
+	KUNIT_CASE(pnfs_decode_valid_ipv4),
+	KUNIT_CASE(pnfs_decode_valid_ipv4_max_port),
+	KUNIT_CASE(pnfs_decode_valid_ipv4_zero_port),
+	KUNIT_CASE(pnfs_decode_valid_ipv6),
+	KUNIT_CASE(pnfs_decode_reject_high_octet_over_255),
+	KUNIT_CASE(pnfs_decode_reject_low_octet_over_255),
+	KUNIT_CASE(pnfs_decode_reject_integer_overflow),
+	KUNIT_CASE(pnfs_decode_reject_empty_low_octet),
+	KUNIT_CASE(pnfs_decode_reject_empty_high_octet),
+	KUNIT_CASE(pnfs_decode_reject_negative_octet),
+	KUNIT_CASE(pnfs_decode_reject_leading_plus),
+	KUNIT_CASE(pnfs_decode_reject_non_numeric_octet),
+	KUNIT_CASE(pnfs_decode_reject_trailing_garbage),
+	KUNIT_CASE(pnfs_decode_reject_trailing_newline),
+	KUNIT_CASE(pnfs_decode_reject_bad_address),
+	KUNIT_CASE(pnfs_decode_reject_missing_port_octet),
+	{}
+};
+
+static struct kunit_suite pnfs_decode_test_suite = {
+	.name = "nfs_pnfs_decode",
+	.test_cases = pnfs_decode_test_cases,
+};
+
+kunit_test_suite(pnfs_decode_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for pNFS data server address decoding");
+MODULE_LICENSE("GPL");
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-23 11:43 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-23 11:42 [PATCH 0/4] NFS: harden pNFS device address decoding Prabhakar Pujeri
2026-08-23 11:42 ` [PATCH 1/4] NFS: validate pNFS data server port octets Prabhakar Pujeri
2026-08-23 11:42 ` [PATCH 2/4] NFS: bound multipath address count in file-layout GETDEVICEINFO Prabhakar Pujeri
2026-08-23 11:42 ` [PATCH 3/4] NFS: bound multipath address count in flexfiles GETDEVICEINFO Prabhakar Pujeri
2026-08-23 11:42 ` [PATCH 4/4] NFS: add KUnit coverage for pNFS address decoding Prabhakar Pujeri

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox