| // 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 |