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/clone.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 struct rlimit nfd;
57
58 SAFE_GETRLIMIT(RLIMIT_NOFILE, &nfd);
59
60 if (nfd.rlim_max < 1000) {
61 tst_brk(TCONF, "NOFILE limit max too low: %lu < 1000",
62 nfd.rlim_max);
63 }
64
65 nfd.rlim_cur = nfd.rlim_max;
66 SAFE_SETRLIMIT(RLIMIT_NOFILE, &nfd);
67 }
68
check_cloexec(int i,int expected)69 static void check_cloexec(int i, int expected)
70 {
71 int present = SAFE_FCNTL(fd[i], F_GETFD) & FD_CLOEXEC;
72
73 if (expected && !present)
74 tst_res(TFAIL, "fd[%d] flags do not contain FD_CLOEXEC", i);
75
76 if (!expected && present)
77 tst_res(TFAIL, "fd[%d] flags contain FD_CLOEXEC", i);
78 }
79
check_closed(int min)80 static void check_closed(int min)
81 {
82 int i;
83
84 for (i = min; i < 3; i++) {
85 if (fcntl(fd[i], F_GETFD) > -1)
86 tst_res(TFAIL, "fd[%d] is still open", i);
87 }
88 }
89
child(unsigned int n)90 static void child(unsigned int n)
91 {
92 switch (n) {
93 case 0:
94 SAFE_DUP2(fd[1], fd[2]);
95 do_close_range(3, ~0U, 0);
96 check_closed(0);
97 break;
98 case 1:
99 SAFE_DUP2(fd[1], fd[2]);
100 do_close_range(3, ~0U, CLOSE_RANGE_UNSHARE);
101 check_closed(0);
102 break;
103 case 2:
104 do_close_range(3, ~0U, CLOSE_RANGE_CLOEXEC);
105 check_cloexec(0, 1);
106 check_cloexec(1, 1);
107
108 SAFE_DUP2(fd[1], fd[2]);
109 check_cloexec(2, 0);
110 break;
111 case 3:
112 do_close_range(3, ~0U,
113 CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);
114 check_cloexec(0, 1);
115 check_cloexec(1, 1);
116
117 SAFE_DUP2(fd[1], fd[2]);
118 check_cloexec(2, 0);
119 break;
120 }
121
122 exit(0);
123 }
124
run(unsigned int n)125 static void run(unsigned int n)
126 {
127 const struct tst_clone_args args = {
128 .flags = CLONE_FILES,
129 .exit_signal = SIGCHLD,
130 };
131
132 switch (n) {
133 case 0:
134 tst_res(TINFO, "Plain close range");
135 do_close_range(3, ~0U, 0);
136 break;
137 case 1:
138 tst_res(TINFO, "Set UNSHARE and close range");
139 do_close_range(3, ~0U, CLOSE_RANGE_UNSHARE);
140 break;
141 case 2:
142 tst_res(TINFO, "Set CLOEXEC on range");
143 do_close_range(3, ~0U, CLOSE_RANGE_CLOEXEC);
144 break;
145 case 3:
146 tst_res(TINFO, "Set UNSHARE and CLOEXEC on range");
147 do_close_range(3, ~0U,
148 CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);
149 break;
150 }
151
152 fd[0] = SAFE_OPEN("mnt/tmpfile", O_RDWR | O_CREAT, 0644);
153 fd[1] = SAFE_DUP2(fd[0], 1000);
154 fd[2] = 42;
155
156 if (!SAFE_CLONE(&args))
157 child(n);
158
159 tst_reap_children();
160
161 switch (n) {
162 case 0:
163 check_closed(0);
164 break;
165 case 1:
166 check_cloexec(0, 0);
167 check_cloexec(1, 0);
168 check_cloexec(2, 0);
169 break;
170 case 2:
171 check_cloexec(0, 1);
172 check_cloexec(1, 1);
173 check_cloexec(2, 0);
174 break;
175 case 3:
176 check_cloexec(0, 0);
177 check_cloexec(1, 0);
178 check_closed(2);
179 break;
180 }
181
182 do_close_range(3, ~0U, 0);
183 check_closed(0);
184
185 if (tst_taint_check())
186 tst_res(TFAIL, "Kernel tainted");
187 else
188 tst_res(TPASS, "No kernel taints");
189 }
190
191 static struct tst_test test = {
192 .tcnt = 4,
193 .forks_child = 1,
194 .mount_device = 1,
195 .mntpoint = "mnt",
196 .all_filesystems = 1,
197 .needs_root = 1,
198 .test = run,
199 .caps = (struct tst_cap []) {
200 TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN),
201 {}
202 },
203 .taint_check = TST_TAINT_W | TST_TAINT_D,
204 .setup = setup,
205 .tags = (const struct tst_tag[]) {
206 {"linux-git", "fec8a6a69103"},
207 {},
208 },
209 };
210