]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/namespace: test listns() pagination
authorChristian Brauner <brauner@kernel.org>
Wed, 29 Oct 2025 12:21:25 +0000 (13:21 +0100)
committerChristian Brauner <brauner@kernel.org>
Mon, 3 Nov 2025 16:41:25 +0000 (17:41 +0100)
Minimal test case to reproduce KASAN out-of-bounds in listns pagination.

Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-72-2e6f823ebdc0@kernel.org
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Christian Brauner <brauner@kernel.org>
tools/testing/selftests/namespaces/.gitignore
tools/testing/selftests/namespaces/Makefile
tools/testing/selftests/namespaces/listns_pagination_bug.c [new file with mode: 0644]

index f6dcf769f1505c5f544496244bfc0612df87a1fa..f4d2209ca4e44ba260af6b2762ba1ff97184048c 100644 (file)
@@ -7,3 +7,4 @@ listns_permissions_test
 siocgskns_test
 cred_change_test
 stress_test
+listns_pagination_bug
index 3c776740f3ac41b30d79c91736cacf65edda1c60..01569e0abbdb06f27271f7ad5989c410422b0df8 100644 (file)
@@ -10,7 +10,8 @@ TEST_GEN_PROGS := nsid_test \
                  listns_permissions_test \
                  siocgskns_test \
                  cred_change_test \
-                 stress_test
+                 stress_test \
+                 listns_pagination_bug
 
 include ../lib.mk
 
@@ -20,4 +21,5 @@ $(OUTPUT)/listns_permissions_test: ../filesystems/utils.c
 $(OUTPUT)/siocgskns_test: ../filesystems/utils.c
 $(OUTPUT)/cred_change_test: ../filesystems/utils.c
 $(OUTPUT)/stress_test: ../filesystems/utils.c
+$(OUTPUT)/listns_pagination_bug: ../filesystems/utils.c
 
diff --git a/tools/testing/selftests/namespaces/listns_pagination_bug.c b/tools/testing/selftests/namespaces/listns_pagination_bug.c
new file mode 100644 (file)
index 0000000..da7d33f
--- /dev/null
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <errno.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "../kselftest_harness.h"
+#include "../filesystems/utils.h"
+#include "wrappers.h"
+
+/*
+ * Minimal test case to reproduce KASAN out-of-bounds in listns pagination.
+ *
+ * The bug occurs when:
+ * 1. Filtering by a specific namespace type (e.g., CLONE_NEWUSER)
+ * 2. Using pagination (req.ns_id != 0)
+ * 3. The lookup_ns_id_at() call in do_listns() passes ns_type=0 instead of
+ *    the filtered type, causing it to search the unified tree and potentially
+ *    return a namespace of the wrong type.
+ */
+TEST(pagination_with_type_filter)
+{
+       struct ns_id_req req = {
+               .size = sizeof(req),
+               .spare = 0,
+               .ns_id = 0,
+               .ns_type = CLONE_NEWUSER,  /* Filter by user namespace */
+               .spare2 = 0,
+               .user_ns_id = 0,
+       };
+       pid_t pids[10];
+       int num_children = 10;
+       int i;
+       int sv[2];
+       __u64 first_batch[3];
+       ssize_t ret;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
+
+       /* Create children with user namespaces */
+       for (i = 0; i < num_children; i++) {
+               pids[i] = fork();
+               ASSERT_GE(pids[i], 0);
+
+               if (pids[i] == 0) {
+                       char c;
+                       close(sv[0]);
+
+                       if (setup_userns() < 0) {
+                               close(sv[1]);
+                               exit(1);
+                       }
+
+                       /* Signal parent we're ready */
+                       if (write(sv[1], &c, 1) != 1) {
+                               close(sv[1]);
+                               exit(1);
+                       }
+
+                       /* Wait for parent signal to exit */
+                       if (read(sv[1], &c, 1) != 1) {
+                               close(sv[1]);
+                               exit(1);
+                       }
+
+                       close(sv[1]);
+                       exit(0);
+               }
+       }
+
+       close(sv[1]);
+
+       /* Wait for all children to signal ready */
+       for (i = 0; i < num_children; i++) {
+               char c;
+               if (read(sv[0], &c, 1) != 1) {
+                       close(sv[0]);
+                       for (int j = 0; j < num_children; j++)
+                               kill(pids[j], SIGKILL);
+                       for (int j = 0; j < num_children; j++)
+                               waitpid(pids[j], NULL, 0);
+                       ASSERT_TRUE(false);
+               }
+       }
+
+       /* First batch - this should work */
+       ret = sys_listns(&req, first_batch, 3, 0);
+       if (ret < 0) {
+               if (errno == ENOSYS) {
+                       close(sv[0]);
+                       for (i = 0; i < num_children; i++)
+                               kill(pids[i], SIGKILL);
+                       for (i = 0; i < num_children; i++)
+                               waitpid(pids[i], NULL, 0);
+                       SKIP(return, "listns() not supported");
+               }
+               ASSERT_GE(ret, 0);
+       }
+
+       TH_LOG("First batch returned %zd entries", ret);
+
+       if (ret == 3) {
+               __u64 second_batch[3];
+
+               /* Second batch - pagination triggers the bug */
+               req.ns_id = first_batch[2];  /* Continue from last ID */
+               ret = sys_listns(&req, second_batch, 3, 0);
+
+               TH_LOG("Second batch returned %zd entries", ret);
+               ASSERT_GE(ret, 0);
+       }
+
+       /* Signal all children to exit */
+       for (i = 0; i < num_children; i++) {
+               char c = 'X';
+               if (write(sv[0], &c, 1) != 1) {
+                       close(sv[0]);
+                       for (int j = i; j < num_children; j++)
+                               kill(pids[j], SIGKILL);
+                       for (int j = 0; j < num_children; j++)
+                               waitpid(pids[j], NULL, 0);
+                       ASSERT_TRUE(false);
+               }
+       }
+
+       close(sv[0]);
+
+       /* Cleanup */
+       for (i = 0; i < num_children; i++) {
+               int status;
+               waitpid(pids[i], &status, 0);
+       }
+}
+
+TEST_HARNESS_MAIN