1 /*	$OpenBSD: kqueue-regress.c,v 1.5 2022/03/29 19:04:19 millert 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/mman.h>
9 #include <sys/resource.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <sys/wait.h>
13 
14 #include <assert.h>
15 #include <err.h>
16 #include <poll.h>
17 #include <signal.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include "main.h"
24 
25 static int do_regress1(void);
26 static int do_regress2(void);
27 static int do_regress3(void);
28 static int do_regress4(void);
29 static int do_regress5(void);
30 static int do_regress6(void);
31 
32 static void make_chain(int);
33 
34 int
do_regress(int n)35 do_regress(int n)
36 {
37 	switch (n) {
38 	case 1:
39 		return do_regress1();
40 	case 2:
41 		return do_regress2();
42 	case 3:
43 		return do_regress3();
44 	case 4:
45 		return do_regress4();
46 	case 5:
47 		return do_regress5();
48 	case 6:
49 		return do_regress6();
50 	default:
51 		errx(1, "unknown regress test number %d", n);
52 	}
53 }
54 
55 /*
56  * Regression test for NULL-deref in knote_processexit().
57  */
58 static int
do_regress1(void)59 do_regress1(void)
60 {
61 	struct kevent kev[2];
62 	int kq;
63 
64 	ASS((kq = kqueue()) >= 0,
65 	    warn("kqueue"));
66 
67 	EV_SET(&kev[0], kq, EVFILT_READ, EV_ADD, 0, 0, NULL);
68 	EV_SET(&kev[1], SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
69 	ASS(kevent(kq, kev, 2, NULL, 0, NULL) == 0,
70 	    warn("can't register events on kqueue"));
71 
72 	/* kq intentionally left open */
73 
74 	return 0;
75 }
76 
77 /*
78  * Regression test for use-after-free in kqueue_close().
79  */
80 static int
do_regress2(void)81 do_regress2(void)
82 {
83 	pid_t pid;
84 	int i, status;
85 
86 	/* Run twice in order to trigger the panic faster, if still present. */
87 	for (i = 0; i < 2; i++) {
88 		pid = fork();
89 		if (pid == -1)
90 			err(1, "fork");
91 
92 		if (pid == 0) {
93 			struct kevent kev[1];
94 			int p0[2], p1[2];
95 			int kq;
96 
97 			if (pipe(p0) == -1)
98 				err(1, "pipe");
99 			if (pipe(p1) == -1)
100 				err(1, "pipe");
101 
102 			kq = kqueue();
103 			if (kq == -1)
104 				err(1, "kqueue");
105 
106 			EV_SET(&kev[0], p0[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
107 			if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
108 				err(1, "kevent");
109 
110 			EV_SET(&kev[0], p1[1], EVFILT_READ, EV_ADD, 0, 0, NULL);
111 			if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
112 				err(1, "kevent");
113 
114 			EV_SET(&kev[0], p1[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
115 			if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
116 				err(1, "kevent");
117 
118 			_exit(0);
119 		}
120 
121 		if (waitpid(pid, &status, 0) == -1)
122 			err(1, "waitpid");
123 		assert(WIFEXITED(status));
124 		assert(WEXITSTATUS(status) == 0);
125 	}
126 
127 	return 0;
128 }
129 
130 /*
131  * Regression test for kernel stack exhaustion.
132  */
133 static int
do_regress3(void)134 do_regress3(void)
135 {
136 	pid_t pid;
137 	int dir, status;
138 
139 	for (dir = 0; dir < 2; dir++) {
140 		pid = fork();
141 		if (pid == -1)
142 			err(1, "fork");
143 
144 		if (pid == 0) {
145 			make_chain(dir);
146 			_exit(0);
147 		}
148 
149 		if (waitpid(pid, &status, 0) == -1)
150 			err(1, "waitpid");
151 		assert(WIFEXITED(status));
152 		assert(WEXITSTATUS(status) == 0);
153 	}
154 
155 	return 0;
156 }
157 
158 static void
make_chain(int dir)159 make_chain(int dir)
160 {
161 	struct kevent kev[1];
162 	int i, kq, prev;
163 
164 	/*
165 	 * Build a chain of kqueues and leave the files open.
166 	 * If the chain is long enough and properly oriented, a broken kernel
167 	 * can exhaust the stack when this process exits.
168 	 */
169 	for (i = 0, prev = -1; i < 120; i++, prev = kq) {
170 		kq = kqueue();
171 		if (kq == -1)
172 			err(1, "kqueue");
173 		if (prev == -1)
174 			continue;
175 
176 		if (dir == 0) {
177 			EV_SET(&kev[0], prev, EVFILT_READ, EV_ADD, 0, 0, NULL);
178 			if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
179 				err(1, "kevent");
180 		} else {
181 			EV_SET(&kev[0], kq, EVFILT_READ, EV_ADD, 0, 0, NULL);
182 			if (kevent(prev, kev, 1, NULL, 0, NULL) == -1)
183 				err(1, "kevent");
184 		}
185 	}
186 }
187 
188 /*
189  * Regression test for kernel stack exhaustion.
190  */
191 static int
do_regress4(void)192 do_regress4(void)
193 {
194 	static const int nkqueues = 500;
195 	struct kevent kev[1];
196 	struct rlimit rlim;
197 	struct timespec ts;
198 	int fds[2], i, kq = -1, prev;
199 
200 	if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
201 		err(1, "getrlimit");
202 	if (rlim.rlim_cur < nkqueues + 8) {
203 		rlim.rlim_cur = nkqueues + 8;
204 		if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) {
205 			printf("RLIMIT_NOFILE is too low and can't raise it\n");
206 			printf("SKIPPED\n");
207 			exit(0);
208 		}
209 	}
210 
211 	if (pipe(fds) == -1)
212 		err(1, "pipe");
213 
214 	/* Build a chain of kqueus. The first kqueue refers to the pipe. */
215 	for (i = 0, prev = fds[0]; i < nkqueues; i++, prev = kq) {
216 		kq = kqueue();
217 		if (kq == -1)
218 			err(1, "kqueue");
219 
220 		EV_SET(&kev[0], prev, EVFILT_READ, EV_ADD, 0, 0, NULL);
221 		if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
222 			err(1, "kevent");
223 	}
224 
225 	/*
226 	 * Trigger a cascading event through the chain.
227 	 * If the chain is long enough, a broken kernel can run out
228 	 * of kernel stack space.
229 	 */
230 	write(fds[1], "x", 1);
231 
232 	/*
233 	 * Check that the event gets propagated.
234 	 * The propagation is not instantaneous, so allow a brief pause.
235 	 */
236 	ts.tv_sec = 5;
237 	ts.tv_nsec = 0;
238 	assert(kevent(kq, NULL, 0, kev, 1, NULL) == 1);
239 
240 	return 0;
241 }
242 
243 /*
244  * Regression test for select and poll with kqueue.
245  */
246 static int
do_regress5(void)247 do_regress5(void)
248 {
249 	fd_set fdset;
250 	struct kevent kev[1];
251 	struct pollfd pfd[1];
252 	struct timeval tv;
253 	int fds[2], kq, ret;
254 
255 	if (pipe(fds) == -1)
256 		err(1, "pipe");
257 
258 	kq = kqueue();
259 	if (kq == -1)
260 		err(1, "kqueue");
261 	EV_SET(&kev[0], fds[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
262 	if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
263 		err(1, "kevent");
264 
265 	/* Check that no event is reported. */
266 
267 	FD_ZERO(&fdset);
268 	FD_SET(kq, &fdset);
269 	tv.tv_sec = 0;
270 	tv.tv_usec = 0;
271 	ret = select(kq + 1, &fdset, NULL, NULL, &tv);
272 	if (ret == -1)
273 		err(1, "select");
274 	assert(ret == 0);
275 
276 	pfd[0].fd = kq;
277 	pfd[0].events = POLLIN;
278 	pfd[0].revents = 0;
279 	ret = poll(pfd, 1, 0);
280 	if (ret == -1)
281 		err(1, "poll");
282 	assert(ret == 0);
283 
284 	/* Trigger an event. */
285 	write(fds[1], "x", 1);
286 
287 	/* Check that the event gets reported. */
288 
289 	FD_ZERO(&fdset);
290 	FD_SET(kq, &fdset);
291 	tv.tv_sec = 5;
292 	tv.tv_usec = 0;
293 	ret = select(kq + 1, &fdset, NULL, NULL, &tv);
294 	if (ret == -1)
295 		err(1, "select");
296 	assert(ret == 1);
297 	assert(FD_ISSET(kq, &fdset));
298 
299 	pfd[0].fd = kq;
300 	pfd[0].events = POLLIN;
301 	pfd[0].revents = 0;
302 	ret = poll(pfd, 1, 5000);
303 	if (ret == -1)
304 		err(1, "poll");
305 	assert(ret == 1);
306 	assert(pfd[0].revents & POLLIN);
307 
308 	return 0;
309 }
310 
311 int
test_regress6(int kq,size_t len)312 test_regress6(int kq, size_t len)
313 {
314 	const struct timespec nap_time = { 0, 1 };
315 	int i, kstatus, wstatus;
316 	struct kevent event;
317 	pid_t child, pid;
318 	void *addr;
319 
320 	child = fork();
321 	switch (child) {
322 	case -1:
323 		warn("fork");
324 		return -1;
325 	case 0:
326 		/* fork a bunch of zombies to keep the reaper busy, then exit */
327 		signal(SIGCHLD, SIG_IGN);
328 		for (i = 0; i < 1000; i++) {
329 			if (fork() == 0) {
330 				/* Dirty some memory so uvm_exit has work. */
331 				addr = mmap(NULL, len, PROT_READ|PROT_WRITE,
332 				    MAP_ANON, -1, 0);
333 				if (addr == MAP_FAILED)
334 					err(1, "mmap");
335 				memset(addr, 'A', len);
336 				nanosleep(&nap_time, NULL);
337 				_exit(2);
338 			}
339 		}
340 		nanosleep(&nap_time, NULL);
341 		_exit(1);
342 	default:
343 		/* parent */
344 		break;
345 	}
346 
347 	/* Register NOTE_EXIT and wait for child. */
348 	EV_SET(&event, child, EVFILT_PROC, EV_ADD|EV_ONESHOT, NOTE_EXIT, 0,
349 	    NULL);
350 	if (kevent(kq, &event, 1, &event, 1, NULL) != 1)
351 		err(1, "kevent");
352 	if (event.flags & EV_ERROR)
353 		errx(1, "kevent: %s", strerror(event.data));
354 	if (event.ident != child)
355 		errx(1, "expected child %d, got %lu", child, event.ident);
356 	kstatus = event.data;
357 	if (!WIFEXITED(kstatus))
358 		errx(1, "child did not exit?");
359 
360 	pid = waitpid(child, &wstatus, WNOHANG);
361 	switch (pid) {
362 	case -1:
363 		err(1, "waitpid %d", child);
364 	case 0:
365 		printf("kevent: child %d exited %d\n", child,
366 		    WEXITSTATUS(kstatus));
367 		printf("waitpid: child %d not ready\n", child);
368 		break;
369 	default:
370 		if (wstatus != kstatus) {
371 			/* macOS has a bug where kstatus is 0 */
372 			warnx("kevent status 0x%x != waitpid status 0x%x",
373 			    kstatus, wstatus);
374 		}
375 		break;
376 	}
377 
378 	return pid;
379 }
380 
381 /*
382  * Regression test for NOTE_EXIT waitability.
383  */
384 static int
do_regress6(void)385 do_regress6(void)
386 {
387 	int i, kq, page_size, rc;
388 	struct rlimit rlim;
389 
390 	/* Bump process limits since we fork a lot. */
391 	if (getrlimit(RLIMIT_NPROC, &rlim) == -1)
392 		err(1, "getrlimit(RLIMIT_NPROC)");
393 	rlim.rlim_cur = rlim.rlim_max;
394 	if (setrlimit(RLIMIT_NPROC, &rlim) == -1)
395 		err(1, "setrlimit(RLIMIT_NPROC)");
396 
397 	kq = kqueue();
398 	if (kq == -1)
399 		err(1, "kqueue");
400 
401 	page_size = getpagesize();
402 
403 	/* This test is inherently racey but fails within a few iterations. */
404 	for (i = 0; i < 25; i++) {
405 		rc = test_regress6(kq, page_size);
406 		switch (rc) {
407 		case -1:
408 			goto done;
409 		case 0:
410 			printf("child not ready when NOTE_EXIT received");
411 			if (i != 0)
412 				printf(" (%d iterations)", i + 1);
413 			putchar('\n');
414 			goto done;
415 		default:
416 			/* keep trying */
417 			continue;
418 		}
419 	}
420 	printf("child exited as expected when NOTE_EXIT received\n");
421 
422 done:
423 	close(kq);
424 	return rc <= 0;
425 }
426