linux/tools/testing/selftests/namespaces/listns_pagination_bug.c

139 lines
3.0 KiB
C

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