summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/namespaces/listns_pagination_bug.c
blob: da7d33f96397232efd6579c9446cc7bbe765181a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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