1 /*	$OpenBSD: kqueue-regress.c,v 1.4 2020/03/08 09:40:52 visa Exp $	*/
2 /*
3  *	Written by Anton Lindqvist <anton@openbsd.org> 2018 Public Domain
4  */
5 
6 #include <sys/types.h>
7 #include <sys/event.h>
8 #include <sys/resource.h>
9 #include <sys/select.h>
10 #include <sys/time.h>
11 #include <sys/wait.h>
12 
13 #include <assert.h>
14 #include <err.h>
15 #include <poll.h>
16 #include <signal.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 
21 #include "main.h"
22 
23 static int do_regress1(void);
24 static int do_regress2(void);
25 static int do_regress3(void);
26 static int do_regress4(void);
27 static int do_regress5(void);
28 
29 static void make_chain(int);
30 
31 int
32 do_regress(int n)
33 {
34 	switch (n) {
35 	case 1:
36 		return do_regress1();
37 	case 2:
38 		return do_regress2();
39 	case 3:
40 		return do_regress3();
41 	case 4:
42 		return do_regress4();
43 	case 5:
44 		return do_regress5();
45 	default:
46 		errx(1, "unknown regress test number %d", n);
47 	}
48 }
49 
50 /*
51  * Regression test for NULL-deref in knote_processexit().
52  */
53 static int
54 do_regress1(void)
55 {
56 	struct kevent kev[2];
57 	int kq;
58 
59 	ASS((kq = kqueue()) >= 0,
60 	    warn("kqueue"));
61 
62 	EV_SET(&kev[0], kq, EVFILT_READ, EV_ADD, 0, 0, NULL);
63 	EV_SET(&kev[1], SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
64 	ASS(kevent(kq, kev, 2, NULL, 0, NULL) == 0,
65 	    warn("can't register events on kqueue"));
66 
67 	/* kq intentionally left open */
68 
69 	return 0;
70 }
71 
72 /*
73  * Regression test for use-after-free in kqueue_close().
74  */
75 static int
76 do_regress2(void)
77 {
78 	pid_t pid;
79 	int i, status;
80 
81 	/* Run twice in order to trigger the panic faster, if still present. */
82 	for (i = 0; i < 2; i++) {
83 		pid = fork();
84 		if (pid == -1)
85 			err(1, "fork");
86 
87 		if (pid == 0) {
88 			struct kevent kev[1];
89 			int p0[2], p1[2];
90 			int kq;
91 
92 			if (pipe(p0) == -1)
93 				err(1, "pipe");
94 			if (pipe(p1) == -1)
95 				err(1, "pipe");
96 
97 			kq = kqueue();
98 			if (kq == -1)
99 				err(1, "kqueue");
100 
101 			EV_SET(&kev[0], p0[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
102 			if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
103 				err(1, "kevent");
104 
105 			EV_SET(&kev[0], p1[1], EVFILT_READ, EV_ADD, 0, 0, NULL);
106 			if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
107 				err(1, "kevent");
108 
109 			EV_SET(&kev[0], p1[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
110 			if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
111 				err(1, "kevent");
112 
113 			_exit(0);
114 		}
115 
116 		if (waitpid(pid, &status, 0) == -1)
117 			err(1, "waitpid");
118 		assert(WIFEXITED(status));
119 		assert(WEXITSTATUS(status) == 0);
120 	}
121 
122 	return 0;
123 }
124 
125 /*
126  * Regression test for kernel stack exhaustion.
127  */
128 static int
129 do_regress3(void)
130 {
131 	pid_t pid;
132 	int dir, status;
133 
134 	for (dir = 0; dir < 2; dir++) {
135 		pid = fork();
136 		if (pid == -1)
137 			err(1, "fork");
138 
139 		if (pid == 0) {
140 			make_chain(dir);
141 			_exit(0);
142 		}
143 
144 		if (waitpid(pid, &status, 0) == -1)
145 			err(1, "waitpid");
146 		assert(WIFEXITED(status));
147 		assert(WEXITSTATUS(status) == 0);
148 	}
149 
150 	return 0;
151 }
152 
153 static void
154 make_chain(int dir)
155 {
156 	struct kevent kev[1];
157 	int i, kq, prev;
158 
159 	/*
160 	 * Build a chain of kqueues and leave the files open.
161 	 * If the chain is long enough and properly oriented, a broken kernel
162 	 * can exhaust the stack when this process exits.
163 	 */
164 	for (i = 0, prev = -1; i < 120; i++, prev = kq) {
165 		kq = kqueue();
166 		if (kq == -1)
167 			err(1, "kqueue");
168 		if (prev == -1)
169 			continue;
170 
171 		if (dir == 0) {
172 			EV_SET(&kev[0], prev, EVFILT_READ, EV_ADD, 0, 0, NULL);
173 			if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
174 				err(1, "kevent");
175 		} else {
176 			EV_SET(&kev[0], kq, EVFILT_READ, EV_ADD, 0, 0, NULL);
177 			if (kevent(prev, kev, 1, NULL, 0, NULL) == -1)
178 				err(1, "kevent");
179 		}
180 	}
181 }
182 
183 /*
184  * Regression test for kernel stack exhaustion.
185  */
186 static int
187 do_regress4(void)
188 {
189 	static const int nkqueues = 500;
190 	struct kevent kev[1];
191 	struct rlimit rlim;
192 	struct timespec ts;
193 	int fds[2], i, kq = -1, prev;
194 
195 	if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
196 		err(1, "getrlimit");
197 	if (rlim.rlim_cur < nkqueues + 8) {
198 		rlim.rlim_cur = nkqueues + 8;
199 		if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) {
200 			printf("RLIMIT_NOFILE is too low and can't raise it\n");
201 			printf("SKIPPED\n");
202 			exit(0);
203 		}
204 	}
205 
206 	if (pipe(fds) == -1)
207 		err(1, "pipe");
208 
209 	/* Build a chain of kqueus. The first kqueue refers to the pipe. */
210 	for (i = 0, prev = fds[0]; i < nkqueues; i++, prev = kq) {
211 		kq = kqueue();
212 		if (kq == -1)
213 			err(1, "kqueue");
214 
215 		EV_SET(&kev[0], prev, EVFILT_READ, EV_ADD, 0, 0, NULL);
216 		if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
217 			err(1, "kevent");
218 	}
219 
220 	/*
221 	 * Trigger a cascading event through the chain.
222 	 * If the chain is long enough, a broken kernel can run out
223 	 * of kernel stack space.
224 	 */
225 	write(fds[1], "x", 1);
226 
227 	/*
228 	 * Check that the event gets propagated.
229 	 * The propagation is not instantaneous, so allow a brief pause.
230 	 */
231 	ts.tv_sec = 5;
232 	ts.tv_nsec = 0;
233 	assert(kevent(kq, NULL, 0, kev, 1, NULL) == 1);
234 
235 	return 0;
236 }
237 
238 /*
239  * Regression test for select and poll with kqueue.
240  */
241 static int
242 do_regress5(void)
243 {
244 	fd_set fdset;
245 	struct kevent kev[1];
246 	struct pollfd pfd[1];
247 	struct timeval tv;
248 	int fds[2], kq, ret;
249 
250 	if (pipe(fds) == -1)
251 		err(1, "pipe");
252 
253 	kq = kqueue();
254 	if (kq == -1)
255 		err(1, "kqueue");
256 	EV_SET(&kev[0], fds[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
257 	if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
258 		err(1, "kevent");
259 
260 	/* Check that no event is reported. */
261 
262 	FD_ZERO(&fdset);
263 	FD_SET(kq, &fdset);
264 	tv.tv_sec = 0;
265 	tv.tv_usec = 0;
266 	ret = select(kq + 1, &fdset, NULL, NULL, &tv);
267 	if (ret == -1)
268 		err(1, "select");
269 	assert(ret == 0);
270 
271 	pfd[0].fd = kq;
272 	pfd[0].events = POLLIN;
273 	pfd[0].revents = 0;
274 	ret = poll(pfd, 1, 0);
275 	if (ret == -1)
276 		err(1, "poll");
277 	assert(ret == 0);
278 
279 	/* Trigger an event. */
280 	write(fds[1], "x", 1);
281 
282 	/* Check that the event gets reported. */
283 
284 	FD_ZERO(&fdset);
285 	FD_SET(kq, &fdset);
286 	tv.tv_sec = 5;
287 	tv.tv_usec = 0;
288 	ret = select(kq + 1, &fdset, NULL, NULL, &tv);
289 	if (ret == -1)
290 		err(1, "select");
291 	assert(ret == 1);
292 	assert(FD_ISSET(kq, &fdset));
293 
294 	pfd[0].fd = kq;
295 	pfd[0].events = POLLIN;
296 	pfd[0].revents = 0;
297 	ret = poll(pfd, 1, 5000);
298 	if (ret == -1)
299 		err(1, "poll");
300 	assert(ret == 1);
301 	assert(pfd[0].revents & POLLIN);
302 
303 	return 0;
304 }
305