[PATCH 4/4] NFS: add KUnit coverage for pNFS address decoding

Prabhakar Pujeri <[email protected]>
Newsgroups org.kernel.vger.linux-nfs
Message-ID <[email protected]>
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 <[email protected]>
---
 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
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.