• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
34 static int fd[3];
35 
do_close_range(unsigned int fd,unsigned int max_fd,unsigned int flags)36 static 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 
setup(void)54 static 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 
check_cloexec(int i,int expected)71 static 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 
check_closed(int min)82 static 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 
child(unsigned int n)92 static 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 
run(unsigned int n)127 static 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 
193 static 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