[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