Test spurious ENOENT which occurs when it tried to start pagination from
an inactivated or destroyed namespace. The new test is almost identical
to pagination_with_type_filter, except that it calls run_noisy_children()
which creates and lists namespaces to disturb nstree.

As far as the author tested, this bug only reproduced on a baremetal
environment, probably because the test relies on the RCU behavior and
the kernel behaves differently on VM.

Signed-off-by: Yohei Kojima <[email protected]>
---
 .../namespaces/listns_pagination_bug.c        | 200 ++++++++++++++++++
 1 file changed, 200 insertions(+)

diff --git a/tools/testing/selftests/namespaces/listns_pagination_bug.c 
b/tools/testing/selftests/namespaces/listns_pagination_bug.c
index da7d33f96397..f71d8f4d64bb 100644
--- a/tools/testing/selftests/namespaces/listns_pagination_bug.c
+++ b/tools/testing/selftests/namespaces/listns_pagination_bug.c
@@ -135,4 +135,204 @@ TEST(pagination_with_type_filter)
        }
 }
 
+static void run_noisy_children(int num_workers)
+{
+       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[num_workers];
+       int num_forked = 0;
+       int i;
+
+       /*
+        * Create worker processes that do concurrent operations;
+        * most of this part is borrowed from concurrent_namespace_operations
+        * test in stress_test.c
+        */
+       for (i = 0; i < num_workers; i++) {
+               pids[i] = fork();
+               if (pids[i] < 0)
+                       goto failure;
+               if (pids[i] > 0)
+                       num_forked++;
+
+               if (pids[i] == 0) {
+                       /* Each worker: create namespaces, list them, repeat */
+                       int iterations;
+
+                       for (iterations = 0; iterations < 10; iterations++) {
+                               int userns_fd;
+                               __u64 temp_ns_ids[100];
+                               ssize_t ret;
+
+                               /* Create a user namespace */
+                               userns_fd = get_userns_fd(0, getuid(), 1);
+                               if (userns_fd < 0)
+                                       continue;
+
+                               /* List namespaces */
+                               ret = sys_listns(&req, temp_ns_ids, 
ARRAY_SIZE(temp_ns_ids), 0);
+                               (void)ret;
+
+                               close(userns_fd);
+
+                               /* Small delay */
+                               usleep(1000);
+                       }
+
+                       exit(0);
+               }
+       }
+
+       /*
+        * Return after waiting for children; this is enough for
+        * reproduction, and help keeping the test code simple.
+        */
+       for (i = 0; i < num_forked; i++)
+               waitpid(pids[i], NULL, 0);
+
+       return;
+
+failure:
+       for (i = 0; i < num_forked; i++)
+               kill(pids[i], SIGKILL);
+       for (i = 0; i < num_forked; i++)
+               waitpid(pids[i], NULL, 0);
+}
+
+/*
+ * A test case to reproduce spurious ENOENT in listns pagination
+ *
+ * The bug occurs when the ns id to start pagination is inactivated or
+ * destroyed before listns is called (or during listns is processed).
+ *
+ * This test is almost identical to pagination_with_type_filter test
+ * except that this calls run_noisy_children().
+ */
+TEST(pagination_during_grace_period)
+{
+       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;
+       const int num_noisy_children = 10;
+       int i;
+       int sv[2];
+       __u64 first_batch[3];
+       ssize_t ret;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
+
+       run_noisy_children(num_noisy_children);
+
+       /* 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
-- 
2.52.0


Reply via email to