1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Taken from the kernel self tests, which in turn were based on
4 * a Syzkaller reproducer.
5 *
6 * Self test author and close_range author:
7 * Christian Brauner <christian.brauner@ubuntu.com>
8 *
9 * LTP Author: Richard Palethorpe <rpalethorpe@suse.com>
10 * Copyright (c) 2021 SUSE LLC, other copyrights may apply.
11 */
12/*\
13 * [Description]
14 *
15 * We check that close_range()
16 *
17 * - closes FDs
18 * - UNSHARES some FDs before closing them
19 * - it sets CLOEXEC (in both cloned process and parent)
20 * - combination of CLOEXEC and UNSHARE.
21 *
22 * The final test is the actual bug reproducer. Note that we call
23 * clone directly to share the file table.
24 */
25
26#include <stdlib.h>
27
28#include "tst_test.h"
29#include "tst_clone.h"
30
31#include "lapi/sched.h"
32#include "lapi/close_range.h"
33
34static int fd[3];
35
36static inline void do_close_range(unsigned int fd, unsigned int max_fd,
37				  unsigned int flags)
38{
39	int ret = close_range(fd, max_fd, flags);
40
41	if (!ret)
42		return;
43
44	if (errno == EINVAL) {
45		if (flags & CLOSE_RANGE_UNSHARE)
46			tst_brk(TCONF | TERRNO, "No CLOSE_RANGE_UNSHARE");
47		if (flags & CLOSE_RANGE_CLOEXEC)
48			tst_brk(TCONF | TERRNO, "No CLOSE_RANGE_CLOEXEC");
49	}
50
51	tst_brk(TBROK | TERRNO, "close_range(%d, %d, %d)", fd, max_fd, flags);
52}
53
54static void setup(void)
55{
56	close_range_supported_by_kernel();
57
58	struct rlimit nfd;
59
60	SAFE_GETRLIMIT(RLIMIT_NOFILE, &nfd);
61
62	if (nfd.rlim_max < 1000) {
63		tst_brk(TCONF, "NOFILE limit max too low: %lu < 1000",
64			nfd.rlim_max);
65	}
66
67	nfd.rlim_cur = nfd.rlim_max;
68	SAFE_SETRLIMIT(RLIMIT_NOFILE, &nfd);
69}
70
71static void check_cloexec(int i, int expected)
72{
73	int present = SAFE_FCNTL(fd[i], F_GETFD) & FD_CLOEXEC;
74
75	if (expected && !present)
76		tst_res(TFAIL, "fd[%d] flags do not contain FD_CLOEXEC", i);
77
78	if (!expected && present)
79		tst_res(TFAIL, "fd[%d] flags contain FD_CLOEXEC", i);
80}
81
82static void check_closed(int min)
83{
84	int i;
85
86	for (i = min; i < 3; i++) {
87		if (fcntl(fd[i], F_GETFD) > -1)
88			tst_res(TFAIL, "fd[%d] is still open", i);
89	}
90}
91
92static void child(unsigned int n)
93{
94	switch (n) {
95	case 0:
96		SAFE_DUP2(fd[1], fd[2]);
97		do_close_range(3, ~0U, 0);
98		check_closed(0);
99		break;
100	case 1:
101		SAFE_DUP2(fd[1], fd[2]);
102		do_close_range(3, ~0U, CLOSE_RANGE_UNSHARE);
103		check_closed(0);
104		break;
105	case 2:
106		do_close_range(3, ~0U, CLOSE_RANGE_CLOEXEC);
107		check_cloexec(0, 1);
108		check_cloexec(1, 1);
109
110		SAFE_DUP2(fd[1], fd[2]);
111		check_cloexec(2, 0);
112		break;
113	case 3:
114		do_close_range(3, ~0U,
115			       CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);
116		check_cloexec(0, 1);
117		check_cloexec(1, 1);
118
119		SAFE_DUP2(fd[1], fd[2]);
120		check_cloexec(2, 0);
121		break;
122	}
123
124	exit(0);
125}
126
127static void run(unsigned int n)
128{
129	const struct tst_clone_args args = {
130		.flags = CLONE_FILES,
131		.exit_signal = SIGCHLD,
132	};
133
134	switch (n) {
135	case 0:
136		tst_res(TINFO, "Plain close range");
137		do_close_range(3, ~0U, 0);
138		break;
139	case 1:
140		tst_res(TINFO, "Set UNSHARE and close range");
141		do_close_range(3, ~0U, CLOSE_RANGE_UNSHARE);
142		break;
143	case 2:
144		tst_res(TINFO, "Set CLOEXEC on range");
145		do_close_range(3, ~0U, CLOSE_RANGE_CLOEXEC);
146		break;
147	case 3:
148		tst_res(TINFO, "Set UNSHARE and CLOEXEC on range");
149		do_close_range(3, ~0U,
150			       CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);
151		break;
152	}
153
154	fd[0] = SAFE_OPEN("mnt/tmpfile", O_RDWR | O_CREAT, 0644);
155	fd[1] = SAFE_DUP2(fd[0], 1000);
156	fd[2] = 42;
157
158	if (!SAFE_CLONE(&args))
159		child(n);
160
161	tst_reap_children();
162
163	switch (n) {
164	case 0:
165		check_closed(0);
166		break;
167	case 1:
168		check_cloexec(0, 0);
169		check_cloexec(1, 0);
170		check_cloexec(2, 0);
171		break;
172	case 2:
173		check_cloexec(0, 1);
174		check_cloexec(1, 1);
175		check_cloexec(2, 0);
176		break;
177	case 3:
178		check_cloexec(0, 0);
179		check_cloexec(1, 0);
180		check_closed(2);
181		break;
182	}
183
184	do_close_range(3, ~0U, 0);
185	check_closed(0);
186
187	if (tst_taint_check())
188		tst_res(TFAIL, "Kernel tainted");
189	else
190		tst_res(TPASS, "No kernel taints");
191}
192
193static struct tst_test test = {
194	.tcnt = 4,
195	.forks_child = 1,
196	.mount_device = 1,
197	.mntpoint = "mnt",
198	.all_filesystems = 1,
199	.needs_root = 1,
200	.test = run,
201	.caps = (struct tst_cap []) {
202		TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN),
203		{}
204	},
205	.taint_check = TST_TAINT_W | TST_TAINT_D,
206	.setup = setup,
207	.tags = (const struct tst_tag[]) {
208		{"linux-git", "fec8a6a69103"},
209		{},
210	},
211};
212