• 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/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