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