xref: /illumos-gate/usr/src/test/os-tests/tests/oclo/oclo.c (revision b3ff81dc6673bee7f291d9d66a832cb3e1004f49)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2025 Oxide Computer Company
14  */
15 
16 /*
17  * Verify the behavior of the various O_CLOFORK and O_CLOEXEC variants. In
18  * particular getting this via:
19  *
20  *  - open(2): O_CLOFORK/O_CLOEXEC
21  *  - fcntl(2): F_SETFD FD_CLOFORK/FD_CLOEXEC
22  *  - fcntl(2): F_DUPFD_CLOFORK/F_DUPFD_CLOEXEC
23  *  - fcntl(2): F_DUP2FD_CLOFORK/F_DUP2FD_CLOEXEC
24  *  - dup2(3C)
25  *  - dup3(3C): argument translation
26  *  - pipe2(2)
27  *  - socket(2): SOCK_CLOEXEC/SOCK_CLOFORK
28  *  - accept(2): flags on the listen socket aren't inherited on accept
29  *  - socketpair(3SOCKET)
30  *  - accept4(2): SOCK_CLOEXEC/SOCK_CLOFORK
31  *  - recvmsg(2): SCM_RIGHTS MSG_CMSG_CLOFORK/MSG_CMSG_CLOEXEC
32  *
33  * The test is designed such that we have an array of functions that are used to
34  * create file descriptors with different rules. This is found in the
35  * oclo_create array. Each file descriptor that is created is then registered
36  * with information about what is expected about it. A given creation function
37  * can create more than one file descriptor; however, our expectation is that
38  * every file descriptor is accounted for (ignoring stdin, stdout, and stderr).
39  *
40  * We pass a record of each file descriptor that was recorded to a verification
41  * program that will verify everything is correctly honored after an exec. Note
42  * that O_CLOFORK is cleared after exec. The original specification in POSIX has
43  * it being retained; however, this issue was raised after the spec was
44  * published as folks went to implement it and we have ended up following along
45  * with the divergence of other implementations.
46  */
47 
48 #include <stdlib.h>
49 #include <unistd.h>
50 #include <stdbool.h>
51 #include <err.h>
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <fcntl.h>
55 #include <sys/sysmacros.h>
56 #include <sys/fork.h>
57 #include <wait.h>
58 #include <errno.h>
59 #include <string.h>
60 #include <limits.h>
61 #include <libgen.h>
62 #include <sys/socket.h>
63 
64 /*
65  * Verification program name.
66  */
67 #define	OCLO_VERIFY	"ocloexec_verify"
68 
69 /*
70  * This structure represents a table of ways we expect to create file
71  * descriptors that should have the resulting flags set when done. The table is
72  * ordered and subsequent iterations are allowed to assume that the ones that
73  * have gone ahead of them have run and are therefore allowed to access them.
74  * The create function is expected to return the created fd.
75  */
76 typedef struct clo_create clo_create_t;
77 struct clo_create {
78 	const char *clo_desc;
79 	int clo_flags;
80 	void (*clo_func)(const clo_create_t *);
81 };
82 
83 /*
84  * This is our run-time data. We expect all file descriptors to be registered by
85  * our calling functions through oclo_record().
86  */
87 typedef struct clo_rtdata {
88 	const clo_create_t *crt_data;
89 	size_t crt_idx;
90 	int crt_fd;
91 	int crt_flags;
92 	const char *crt_desc;
93 } clo_rtdata_t;
94 
95 static clo_rtdata_t *oclo_rtdata;
96 size_t oclo_rtdata_nents = 0;
97 size_t oclo_rtdata_next = 0;
98 static int oclo_nextfd = STDERR_FILENO + 1;
99 
100 static bool
oclo_flags_match(const clo_rtdata_t * rt,bool child)101 oclo_flags_match(const clo_rtdata_t *rt, bool child)
102 {
103 	const char *pass = child ? "post-fork" : "pre-fork";
104 	bool fail = child && (rt->crt_flags & FD_CLOFORK) != 0;
105 	int flags = fcntl(rt->crt_fd, F_GETFD, NULL);
106 
107 	if (flags < 0) {
108 		int e = errno;
109 
110 		if (fail) {
111 			if (e == EBADF) {
112 				(void) printf("TEST PASSED: %s (%s): fd %d: "
113 				    "correctly closed\n",
114 				    rt->crt_data->clo_desc, pass, rt->crt_fd);
115 				return (true);
116 			}
117 
118 			warn("TEST FAILED: %s (%s): fd %d: expected fcntl to "
119 			    "fail with EBADF, but found %s",
120 			    rt->crt_data->clo_desc, pass, rt->crt_fd,
121 			    strerrorname_np(e));
122 			return (false);
123 		}
124 
125 		warnx("TEST FAILED: %s (%s): fd %d: fcntl(F_GETFD) "
126 		    "unexpectedly failed", rt->crt_data->clo_desc, pass,
127 		    rt->crt_fd);
128 		return (false);
129 	}
130 
131 	if (fail) {
132 		warnx("TEST FAILED: %s (%s): fd %d: received flags %d, but "
133 		    "expected to fail based on flags %d",
134 		    rt->crt_data->clo_desc, pass, rt->crt_fd, flags,
135 		    rt->crt_fd);
136 		return (false);
137 	}
138 
139 	if (flags != rt->crt_flags) {
140 		warnx("TEST FAILED: %s (%s): fd %d: discovered flags 0x%x do "
141 		    "not match expected flags 0x%x", rt->crt_data->clo_desc,
142 		    pass, rt->crt_fd, flags, rt->crt_fd);
143 		return (false);
144 	}
145 
146 	(void) printf("TEST PASSED: %s (%s): fd %d discovered flags match "
147 	    "(0x%x)\n", rt->crt_data->clo_desc, pass, rt->crt_fd, flags);
148 	return (true);
149 }
150 
151 
152 static void
oclo_record(const clo_create_t * c,int fd,int exp_flags,const char * desc)153 oclo_record(const clo_create_t *c, int fd, int exp_flags, const char *desc)
154 {
155 	if (oclo_rtdata_next == oclo_rtdata_nents) {
156 		size_t newrt = oclo_rtdata_nents + 8;
157 		clo_rtdata_t *rt;
158 		rt = recallocarray(oclo_rtdata, oclo_rtdata_nents, newrt,
159 		    sizeof (clo_rtdata_t));
160 		if (rt == NULL) {
161 			err(EXIT_FAILURE, "TEST_FAILED: internal error "
162 			    "expanding fd records to %zu entries", newrt);
163 		}
164 
165 		oclo_rtdata_nents = newrt;
166 		oclo_rtdata = rt;
167 	}
168 
169 	if (fd != oclo_nextfd) {
170 		errx(EXIT_FAILURE, "TEST FAILED: internal test error: expected "
171 		    "to record next fd %d, given %d", oclo_nextfd, fd);
172 	}
173 
174 	oclo_rtdata[oclo_rtdata_next].crt_data = c;
175 	oclo_rtdata[oclo_rtdata_next].crt_fd = fd;
176 	oclo_rtdata[oclo_rtdata_next].crt_flags = exp_flags;
177 	oclo_rtdata[oclo_rtdata_next].crt_desc = desc;
178 
179 	/*
180 	 * Matching errors at this phase are fatal as it means we screwed up the
181 	 * program pretty badly.
182 	 */
183 	if (!oclo_flags_match(&oclo_rtdata[oclo_rtdata_next], false)) {
184 		exit(EXIT_FAILURE);
185 	}
186 
187 	oclo_rtdata_next++;
188 	oclo_nextfd++;
189 }
190 
191 static int
oclo_file(const clo_create_t * c)192 oclo_file(const clo_create_t *c)
193 {
194 	int flags = O_RDWR, fd;
195 
196 	if ((c->clo_flags & FD_CLOEXEC) != 0)
197 		flags |= O_CLOEXEC;
198 	if ((c->clo_flags & FD_CLOFORK) != 0)
199 		flags |= O_CLOFORK;
200 	fd = open("/dev/null", flags);
201 	if (fd < 0) {
202 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to open /dev/null",
203 		    c->clo_desc);
204 	}
205 
206 	return (fd);
207 }
208 
209 static void
oclo_open(const clo_create_t * c)210 oclo_open(const clo_create_t *c)
211 {
212 	oclo_record(c, oclo_file(c), c->clo_flags, NULL);
213 }
214 
215 static void
oclo_setfd_common(const clo_create_t * c,int targ_flags)216 oclo_setfd_common(const clo_create_t *c, int targ_flags)
217 {
218 	int fd = oclo_file(c);
219 	if (fcntl(fd, F_SETFD, targ_flags) < 0) {
220 		err(EXIT_FAILURE, "TEST FAILED: %s: F_SETFD failed to set "
221 		    "flags to %d", c->clo_desc, targ_flags);
222 	}
223 
224 	oclo_record(c, fd, targ_flags, NULL);
225 }
226 
227 static void
oclo_setfd_none(const clo_create_t * c)228 oclo_setfd_none(const clo_create_t *c)
229 {
230 	oclo_setfd_common(c, 0);
231 }
232 
233 static void
oclo_setfd_exec(const clo_create_t * c)234 oclo_setfd_exec(const clo_create_t *c)
235 {
236 	oclo_setfd_common(c, FD_CLOEXEC);
237 }
238 
239 static void
oclo_setfd_fork(const clo_create_t * c)240 oclo_setfd_fork(const clo_create_t *c)
241 {
242 	oclo_setfd_common(c, FD_CLOFORK);
243 }
244 
245 static void
oclo_setfd_both(const clo_create_t * c)246 oclo_setfd_both(const clo_create_t *c)
247 {
248 	oclo_setfd_common(c, FD_CLOFORK | FD_CLOEXEC);
249 }
250 
251 /*
252  * Open an fd with flags in a certain form and then use one of the F_DUPFD or
253  * F_DUP2FD variants and ensure that flags are properly propagated as expected.
254  */
255 static void
oclo_fdup_common(const clo_create_t * c,int targ_flags,int cmd)256 oclo_fdup_common(const clo_create_t *c, int targ_flags, int cmd)
257 {
258 	int dup, fd;
259 
260 	fd = oclo_file(c);
261 	oclo_record(c, fd, c->clo_flags, "base");
262 	switch (cmd) {
263 	case F_DUPFD:
264 	case F_DUPFD_CLOEXEC:
265 	case F_DUPFD_CLOFORK:
266 		dup = fcntl(fd, cmd, fd);
267 		break;
268 	case F_DUP2FD:
269 	case F_DUP2FD_CLOEXEC:
270 	case F_DUP2FD_CLOFORK:
271 		dup = fcntl(fd, cmd, fd + 1);
272 		break;
273 	case F_DUP3FD:
274 		dup = fcntl(fd, cmd, fd + 1, targ_flags);
275 		break;
276 	default:
277 		errx(EXIT_FAILURE, "TEST FAILURE: %s: internal error: "
278 		    "unexpected fcntl cmd: 0x%x", c->clo_desc, cmd);
279 	}
280 
281 	if (dup < 0) {
282 		err(EXIT_FAILURE, "TEST FAILURE: %s: failed to dup fd with "
283 		    "fcntl command 0x%x", c->clo_desc, cmd);
284 	}
285 
286 	oclo_record(c, dup, targ_flags, "dup");
287 }
288 
289 static void
oclo_fdupfd(const clo_create_t * c)290 oclo_fdupfd(const clo_create_t *c)
291 {
292 	oclo_fdup_common(c, 0, F_DUPFD);
293 }
294 
295 static void
oclo_fdupfd_fork(const clo_create_t * c)296 oclo_fdupfd_fork(const clo_create_t *c)
297 {
298 	oclo_fdup_common(c, FD_CLOFORK, F_DUPFD_CLOFORK);
299 }
300 
301 static void
oclo_fdupfd_exec(const clo_create_t * c)302 oclo_fdupfd_exec(const clo_create_t *c)
303 {
304 	oclo_fdup_common(c, FD_CLOEXEC, F_DUPFD_CLOEXEC);
305 }
306 
307 static void
oclo_fdup2fd(const clo_create_t * c)308 oclo_fdup2fd(const clo_create_t *c)
309 {
310 	oclo_fdup_common(c, 0, F_DUP2FD);
311 }
312 
313 static void
oclo_fdup2fd_fork(const clo_create_t * c)314 oclo_fdup2fd_fork(const clo_create_t *c)
315 {
316 	oclo_fdup_common(c, FD_CLOFORK, F_DUP2FD_CLOFORK);
317 }
318 
319 static void
oclo_fdup2fd_exec(const clo_create_t * c)320 oclo_fdup2fd_exec(const clo_create_t *c)
321 {
322 	oclo_fdup_common(c, FD_CLOEXEC, F_DUP2FD_CLOEXEC);
323 }
324 
325 static void
oclo_fdup3fd_none(const clo_create_t * c)326 oclo_fdup3fd_none(const clo_create_t *c)
327 {
328 	oclo_fdup_common(c, 0, F_DUP3FD);
329 }
330 
331 static void
oclo_fdup3fd_exec(const clo_create_t * c)332 oclo_fdup3fd_exec(const clo_create_t *c)
333 {
334 	oclo_fdup_common(c, FD_CLOEXEC, F_DUP3FD);
335 }
336 
337 static void
oclo_fdup3fd_fork(const clo_create_t * c)338 oclo_fdup3fd_fork(const clo_create_t *c)
339 {
340 	oclo_fdup_common(c, FD_CLOFORK, F_DUP3FD);
341 }
342 
343 static void
oclo_fdup3fd_both(const clo_create_t * c)344 oclo_fdup3fd_both(const clo_create_t *c)
345 {
346 	oclo_fdup_common(c, FD_CLOEXEC | FD_CLOFORK, F_DUP3FD);
347 }
348 
349 static void
oclo_dup_common(const clo_create_t * c,int targ_flags,bool v3)350 oclo_dup_common(const clo_create_t *c, int targ_flags, bool v3)
351 {
352 	int dup, fd;
353 	fd = oclo_file(c);
354 	oclo_record(c, fd, c->clo_flags, "base");
355 	if (v3) {
356 		int dflags = 0;
357 		if ((targ_flags & FD_CLOEXEC) != 0)
358 			dflags |= O_CLOEXEC;
359 		if ((targ_flags & FD_CLOFORK) != 0)
360 			dflags |= O_CLOFORK;
361 		dup = dup3(fd, fd + 1, dflags);
362 	} else {
363 		dup = dup2(fd, fd + 1);
364 	}
365 
366 	oclo_record(c, dup, targ_flags, "dup");
367 }
368 
369 static void
oclo_dup2(const clo_create_t * c)370 oclo_dup2(const clo_create_t *c)
371 {
372 	oclo_dup_common(c, 0, false);
373 }
374 
375 static void
oclo_dup3_none(const clo_create_t * c)376 oclo_dup3_none(const clo_create_t *c)
377 {
378 	oclo_dup_common(c, 0, true);
379 }
380 
381 static void
oclo_dup3_exec(const clo_create_t * c)382 oclo_dup3_exec(const clo_create_t *c)
383 {
384 	oclo_dup_common(c, FD_CLOEXEC, true);
385 }
386 
387 static void
oclo_dup3_fork(const clo_create_t * c)388 oclo_dup3_fork(const clo_create_t *c)
389 {
390 	oclo_dup_common(c, FD_CLOFORK, true);
391 }
392 
393 static void
oclo_dup3_both(const clo_create_t * c)394 oclo_dup3_both(const clo_create_t *c)
395 {
396 	oclo_dup_common(c, FD_CLOEXEC | FD_CLOFORK, true);
397 }
398 
399 static void
oclo_pipe(const clo_create_t * c)400 oclo_pipe(const clo_create_t *c)
401 {
402 	int flags = 0, fds[2];
403 
404 	if ((c->clo_flags & FD_CLOEXEC) != 0)
405 		flags |= O_CLOEXEC;
406 	if ((c->clo_flags & FD_CLOFORK) != 0)
407 		flags |= O_CLOFORK;
408 
409 	if (pipe2(fds, flags) < 0) {
410 		err(EXIT_FAILURE, "TEST FAILED: %s: pipe2() with flags %d "
411 		    "failed", c->clo_desc, flags);
412 	}
413 
414 	oclo_record(c, fds[0], c->clo_flags, "pipe[0]");
415 	oclo_record(c, fds[1], c->clo_flags, "pipe[1]");
416 }
417 
418 static void
oclo_socket(const clo_create_t * c)419 oclo_socket(const clo_create_t *c)
420 {
421 	int type = SOCK_DGRAM, fd;
422 
423 	if ((c->clo_flags & FD_CLOEXEC) != 0)
424 		type |= SOCK_CLOEXEC;
425 	if ((c->clo_flags & FD_CLOFORK) != 0)
426 		type |= SOCK_CLOFORK;
427 	fd = socket(PF_INET, type, 0);
428 	if (fd < 0) {
429 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to create socket "
430 		    "with flags: 0x%x\n", c->clo_desc, c->clo_flags);
431 	}
432 
433 	oclo_record(c, fd, c->clo_flags, NULL);
434 }
435 
436 static void
oclo_accept_common(const clo_create_t * c,int targ_flags,bool a4)437 oclo_accept_common(const clo_create_t *c, int targ_flags, bool a4)
438 {
439 	int lsock, csock, asock;
440 	int ltype = SOCK_STREAM, atype = 0;
441 	struct sockaddr_in in;
442 	socklen_t slen;
443 
444 	if ((c->clo_flags & FD_CLOEXEC) != 0)
445 		ltype |= SOCK_CLOEXEC;
446 	if ((c->clo_flags & FD_CLOFORK) != 0)
447 		ltype |= SOCK_CLOFORK;
448 
449 	if ((targ_flags & FD_CLOEXEC) != 0)
450 		atype |= SOCK_CLOEXEC;
451 	if ((targ_flags & FD_CLOFORK) != 0)
452 		atype |= SOCK_CLOFORK;
453 
454 	lsock = socket(PF_INET, ltype, 0);
455 	if (lsock < 0) {
456 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to create listen "
457 		    "socket with flags: 0x%x\n", c->clo_desc, c->clo_flags);
458 	}
459 
460 	oclo_record(c, lsock, c->clo_flags, "listen");
461 	(void) memset(&in, 0, sizeof (in));
462 	in.sin_family = AF_INET;
463 	in.sin_port = 0;
464 	in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
465 
466 	if (bind(lsock, (struct sockaddr *)&in, sizeof (in)) != 0) {
467 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to bind socket",
468 		    c->clo_desc);
469 	}
470 
471 	slen = sizeof (struct sockaddr_in);
472 	if (getsockname(lsock, (struct sockaddr *)&in, &slen) != 0) {
473 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to discover bound "
474 		    "socket address", c->clo_desc);
475 	}
476 
477 	if (listen(lsock, 5) < 0) {
478 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to listen on socket",
479 		    c->clo_desc);
480 	}
481 
482 	csock = socket(PF_INET, SOCK_STREAM, 0);
483 	if (csock < 0) {
484 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to create client "
485 		    "socket", c->clo_desc);
486 	}
487 	oclo_record(c, csock, 0, "connect");
488 
489 	if (connect(csock, (struct sockaddr *)&in, sizeof (in)) != 0) {
490 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to connect to "
491 		    "server socket", c->clo_desc);
492 	}
493 
494 	if (a4) {
495 		asock = accept4(lsock, NULL, NULL, atype);
496 	} else {
497 		asock = accept(lsock, NULL, NULL);
498 	}
499 	if (asock < 0) {
500 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to accept client "
501 		    "connection", c->clo_desc);
502 	}
503 	oclo_record(c, asock, targ_flags, "accept");
504 }
505 
506 static void
oclo_accept(const clo_create_t * c)507 oclo_accept(const clo_create_t *c)
508 {
509 	oclo_accept_common(c, 0, false);
510 }
511 
512 static void
oclo_accept4_none(const clo_create_t * c)513 oclo_accept4_none(const clo_create_t *c)
514 {
515 	oclo_accept_common(c, 0, true);
516 }
517 
518 static void
oclo_accept4_fork(const clo_create_t * c)519 oclo_accept4_fork(const clo_create_t *c)
520 {
521 	oclo_accept_common(c, FD_CLOFORK, true);
522 }
523 
524 static void
oclo_accept4_exec(const clo_create_t * c)525 oclo_accept4_exec(const clo_create_t *c)
526 {
527 	oclo_accept_common(c, FD_CLOEXEC, true);
528 }
529 
530 static void
oclo_accept4_both(const clo_create_t * c)531 oclo_accept4_both(const clo_create_t *c)
532 {
533 	oclo_accept_common(c, FD_CLOEXEC | FD_CLOFORK, true);
534 }
535 
536 /*
537  * Go through the process of sending ourselves a file descriptor.
538  */
539 static void
oclo_rights_common(const clo_create_t * c,int targ_flags)540 oclo_rights_common(const clo_create_t *c, int targ_flags)
541 {
542 	int pair[2], type = SOCK_DGRAM, sflags = 0;
543 	int tosend = oclo_file(c), recvfd;
544 	uint32_t data = 0x7777;
545 	struct iovec iov;
546 	struct msghdr msg;
547 	struct cmsghdr *cm;
548 
549 	if ((c->clo_flags & FD_CLOEXEC) != 0)
550 		type |= SOCK_CLOEXEC;
551 	if ((c->clo_flags & FD_CLOFORK) != 0)
552 		type |= SOCK_CLOFORK;
553 
554 	if (socketpair(PF_UNIX, type, 0, pair) < 0) {
555 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to create socket "
556 		    "pair", c->clo_desc);
557 	}
558 
559 	oclo_record(c, tosend, c->clo_flags, "send fd");
560 	oclo_record(c, pair[0], c->clo_flags, "pair[0]");
561 	oclo_record(c, pair[1], c->clo_flags, "pair[1]");
562 
563 	iov.iov_base = (void *)&data;
564 	iov.iov_len = sizeof (data);
565 
566 	(void) memset(&msg, 0, sizeof (msg));
567 	msg.msg_iov = &iov;
568 	msg.msg_iovlen = 1;
569 	msg.msg_controllen = CMSG_SPACE(sizeof (int));
570 
571 	msg.msg_control = calloc(1, msg.msg_controllen);
572 	if (msg.msg_control == NULL) {
573 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to allocate %u "
574 		    "bytes for SCM_RIGHTS control message", c->clo_desc,
575 		    msg.msg_controllen);
576 	}
577 
578 	cm = CMSG_FIRSTHDR(&msg);
579 	cm->cmsg_len = CMSG_LEN(sizeof (int));
580 	cm->cmsg_level = SOL_SOCKET;
581 	cm->cmsg_type = SCM_RIGHTS;
582 	(void) memcpy(CMSG_DATA(cm), &tosend, sizeof (tosend));
583 
584 	if ((targ_flags & FD_CLOEXEC) != 0)
585 		sflags |= MSG_CMSG_CLOEXEC;
586 	if ((targ_flags & FD_CLOFORK) != 0)
587 		sflags |= MSG_CMSG_CLOFORK;
588 
589 	if (sendmsg(pair[0], &msg, 0) < 0) {
590 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to send fd",
591 		    c->clo_desc);
592 	}
593 
594 	data = 0;
595 	if (recvmsg(pair[1], &msg, sflags) < 0) {
596 		err(EXIT_FAILURE, "TEST FAILED: %s: failed to get fd",
597 		    c->clo_desc);
598 	}
599 
600 	if (data != 0x7777) {
601 		errx(EXIT_FAILURE, "TEST FAILED: %s: did not receive correct "
602 		    "data: expected 0x7777, found 0x%x", c->clo_desc, data);
603 	}
604 
605 	if (msg.msg_controllen < CMSG_SPACE(sizeof (int))) {
606 		errx(EXIT_FAILURE, "TEST FAILED: %s: found insufficient "
607 		    "message control length: expected at least 0x%x, found "
608 		    "0x%x", c->clo_desc, CMSG_SPACE(sizeof (int)),
609 		    msg.msg_controllen);
610 	}
611 
612 	cm = CMSG_FIRSTHDR(&msg);
613 	if (cm->cmsg_level != SOL_SOCKET || cm->cmsg_type != SCM_RIGHTS) {
614 		errx(EXIT_FAILURE, "TEST FAILED: %s: found surprising cmsg "
615 		    "0x%x/0x%x, expected 0x%x/0x%x", c->clo_desc,
616 		    cm->cmsg_level, cm->cmsg_type, SOL_SOCKET, SCM_RIGHTS);
617 	}
618 
619 	if (cm->cmsg_len != CMSG_LEN(sizeof (int))) {
620 		errx(EXIT_FAILURE, "TEST FAILED: %s: found unexpected "
621 		    "SCM_RIGHTS length 0x%x: expected 0x%zx", c->clo_desc,
622 		    cm->cmsg_len, CMSG_LEN(sizeof (int)));
623 	}
624 
625 	(void) memcpy(&recvfd, CMSG_DATA(cm), sizeof (recvfd));
626 	oclo_record(c, recvfd, targ_flags, "SCM_RIGHTS");
627 }
628 
629 static void
oclo_rights_none(const clo_create_t * c)630 oclo_rights_none(const clo_create_t *c)
631 {
632 	oclo_rights_common(c, 0);
633 }
634 
635 static void
oclo_rights_exec(const clo_create_t * c)636 oclo_rights_exec(const clo_create_t *c)
637 {
638 	oclo_rights_common(c, FD_CLOEXEC);
639 }
640 
641 static void
oclo_rights_fork(const clo_create_t * c)642 oclo_rights_fork(const clo_create_t *c)
643 {
644 	oclo_rights_common(c, FD_CLOFORK);
645 }
646 
647 static void
oclo_rights_both(const clo_create_t * c)648 oclo_rights_both(const clo_create_t *c)
649 {
650 	oclo_rights_common(c, FD_CLOEXEC | FD_CLOFORK);
651 }
652 
653 static const clo_create_t oclo_create[] = { {
654 	.clo_desc = "open(2), no flags",
655 	.clo_flags = 0,
656 	.clo_func = oclo_open
657 }, {
658 	.clo_desc = "open(2), O_CLOEXEC",
659 	.clo_flags = FD_CLOEXEC,
660 	.clo_func = oclo_open
661 }, {
662 	.clo_desc = "open(2), O_CLOFORK",
663 	.clo_flags = FD_CLOFORK,
664 	.clo_func = oclo_open
665 }, {
666 	.clo_desc = "open(2), O_CLOEXEC|O_CLOFORK",
667 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
668 	.clo_func = oclo_open
669 }, {
670 	.clo_desc = "fcntl(F_SETFD) no flags->no flags",
671 	.clo_flags = 0,
672 	.clo_func = oclo_setfd_none
673 }, {
674 	.clo_desc = "fcntl(F_SETFD) O_CLOFORK|O_CLOEXEC->no flags",
675 	.clo_flags = O_CLOFORK | O_CLOEXEC,
676 	.clo_func = oclo_setfd_none
677 }, {
678 	.clo_desc = "fcntl(F_SETFD) O_CLOEXEC->no flags",
679 	.clo_flags = O_CLOEXEC,
680 	.clo_func = oclo_setfd_none
681 }, {
682 	.clo_desc = "fcntl(F_SETFD) O_CLOFORK->no flags",
683 	.clo_flags = O_CLOFORK,
684 	.clo_func = oclo_setfd_none
685 }, {
686 	.clo_desc = "fcntl(F_SETFD) no flags->O_CLOEXEC",
687 	.clo_flags = 0,
688 	.clo_func = oclo_setfd_exec
689 }, {
690 	.clo_desc = "fcntl(F_SETFD) O_CLOFORK|O_CLOEXEC->O_CLOEXEC",
691 	.clo_flags = O_CLOFORK | O_CLOEXEC,
692 	.clo_func = oclo_setfd_exec
693 }, {
694 	.clo_desc = "fcntl(F_SETFD) O_CLOEXEC->O_CLOEXEC",
695 	.clo_flags = O_CLOEXEC,
696 	.clo_func = oclo_setfd_exec
697 }, {
698 	.clo_desc = "fcntl(F_SETFD) O_CLOFORK->O_CLOEXEC",
699 	.clo_flags = O_CLOFORK,
700 	.clo_func = oclo_setfd_exec
701 }, {
702 	.clo_desc = "fcntl(F_SETFD) no flags->O_CLOFORK",
703 	.clo_flags = 0,
704 	.clo_func = oclo_setfd_fork
705 }, {
706 	.clo_desc = "fcntl(F_SETFD) O_CLOFORK|O_CLOEXEC->O_CLOFORK",
707 	.clo_flags = O_CLOFORK | O_CLOEXEC,
708 	.clo_func = oclo_setfd_fork
709 }, {
710 	.clo_desc = "fcntl(F_SETFD) O_CLOEXEC->O_CLOFORK",
711 	.clo_flags = O_CLOEXEC,
712 	.clo_func = oclo_setfd_fork
713 }, {
714 	.clo_desc = "fcntl(F_SETFD) O_CLOFORK->O_CLOFORK",
715 	.clo_flags = O_CLOFORK,
716 	.clo_func = oclo_setfd_fork
717 }, {
718 	.clo_desc = "fcntl(F_SETFD) no flags->O_CLOFORK|O_CLOEXEC",
719 	.clo_flags = 0,
720 	.clo_func = oclo_setfd_both
721 }, {
722 	.clo_desc = "fcntl(F_SETFD) O_CLOFORK|O_CLOEXEC->O_CLOFORK|O_CLOEXEC",
723 	.clo_flags = O_CLOFORK | O_CLOEXEC,
724 	.clo_func = oclo_setfd_both
725 }, {
726 	.clo_desc = "fcntl(F_SETFD) O_CLOEXEC->O_CLOFORK|O_CLOEXEC",
727 	.clo_flags = O_CLOEXEC,
728 	.clo_func = oclo_setfd_both
729 }, {
730 	.clo_desc = "fcntl(F_SETFD) O_CLOFORK->O_CLOFORK|O_CLOEXEC",
731 	.clo_flags = O_CLOFORK,
732 	.clo_func = oclo_setfd_both
733 }, {
734 	.clo_desc = "fcntl(F_DUPFD) none->none",
735 	.clo_flags = 0,
736 	.clo_func = oclo_fdupfd
737 }, {
738 	.clo_desc = "fcntl(F_DUPFD) FD_CLOEXEC->none",
739 	.clo_flags = FD_CLOEXEC,
740 	.clo_func = oclo_fdupfd
741 }, {
742 	.clo_desc = "fcntl(F_DUPFD) FD_CLOFORK->none",
743 	.clo_flags = FD_CLOFORK,
744 	.clo_func = oclo_fdupfd
745 }, {
746 	.clo_desc = "fcntl(F_DUPFD) FD_CLOEXEC|FD_CLOFORK->none",
747 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
748 	.clo_func = oclo_fdupfd
749 }, {
750 	.clo_desc = "fcntl(F_DUPFD_CLOFORK) none",
751 	.clo_flags = 0,
752 	.clo_func = oclo_fdupfd_fork
753 }, {
754 	.clo_desc = "fcntl(F_DUPFD_CLOFORK) FD_CLOEXEC",
755 	.clo_flags = FD_CLOEXEC,
756 	.clo_func = oclo_fdupfd_fork
757 }, {
758 	.clo_desc = "fcntl(F_DUPFD_CLOFORK) FD_CLOFORK",
759 	.clo_flags = FD_CLOFORK,
760 	.clo_func = oclo_fdupfd_fork
761 }, {
762 	.clo_desc = "fcntl(F_DUPFD_CLOFORK) FD_CLOEXEC|FD_CLOFORK",
763 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
764 	.clo_func = oclo_fdupfd_fork
765 }, {
766 	.clo_desc = "fcntl(F_DUPFD_CLOEXEC) none",
767 	.clo_flags = 0,
768 	.clo_func = oclo_fdupfd_exec
769 }, {
770 	.clo_desc = "fcntl(F_DUPFD_CLOEXEC) FD_CLOEXEC",
771 	.clo_flags = FD_CLOEXEC,
772 	.clo_func = oclo_fdupfd_exec
773 }, {
774 	.clo_desc = "fcntl(F_DUPFD_CLOEXEC) FD_CLOFORK",
775 	.clo_flags = FD_CLOFORK,
776 	.clo_func = oclo_fdupfd_exec
777 }, {
778 	.clo_desc = "fcntl(F_DUPFD_CLOEXEC) FD_CLOEXEC|FD_CLOFORK",
779 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
780 	.clo_func = oclo_fdupfd_exec
781 }, {
782 	.clo_desc = "fcntl(F_DUP2FD) none->none",
783 	.clo_flags = 0,
784 	.clo_func = oclo_fdup2fd
785 }, {
786 	.clo_desc = "fcntl(F_DUP2FD) FD_CLOEXEC->none",
787 	.clo_flags = FD_CLOEXEC,
788 	.clo_func = oclo_fdup2fd
789 }, {
790 	.clo_desc = "fcntl(F_DUP2FD) FD_CLOFORK->none",
791 	.clo_flags = FD_CLOFORK,
792 	.clo_func = oclo_fdup2fd
793 }, {
794 	.clo_desc = "fcntl(F_DUP2FD) FD_CLOEXEC|FD_CLOFORK->none",
795 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
796 	.clo_func = oclo_fdup2fd
797 }, {
798 	.clo_desc = "fcntl(F_DUP2FD_CLOFORK) none",
799 	.clo_flags = 0,
800 	.clo_func = oclo_fdup2fd_fork
801 }, {
802 	.clo_desc = "fcntl(F_DUP2FD_CLOFORK) FD_CLOEXEC",
803 	.clo_flags = FD_CLOEXEC,
804 	.clo_func = oclo_fdup2fd_fork
805 }, {
806 	.clo_desc = "fcntl(F_DUP2FD_CLOFORK) FD_CLOFORK",
807 	.clo_flags = FD_CLOFORK,
808 	.clo_func = oclo_fdup2fd_fork
809 }, {
810 	.clo_desc = "fcntl(F_DUP2FD_CLOFORK) FD_CLOEXEC|FD_CLOFORK",
811 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
812 	.clo_func = oclo_fdup2fd_fork
813 }, {
814 	.clo_desc = "fcntl(F_DUP2FD_CLOEXEC) none",
815 	.clo_flags = 0,
816 	.clo_func = oclo_fdup2fd_exec
817 }, {
818 	.clo_desc = "fcntl(F_DUP2FD_CLOEXEC) FD_CLOEXEC",
819 	.clo_flags = FD_CLOEXEC,
820 	.clo_func = oclo_fdup2fd_exec
821 }, {
822 	.clo_desc = "fcntl(F_DUP2FD_CLOEXEC) FD_CLOFORK",
823 	.clo_flags = FD_CLOFORK,
824 	.clo_func = oclo_fdup2fd_exec
825 }, {
826 	.clo_desc = "fcntl(F_DUP2FD_CLOEXEC) FD_CLOEXEC|FD_CLOFORK",
827 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
828 	.clo_func = oclo_fdup2fd_exec
829 }, {
830 	.clo_desc = "fcntl(F_DUP3FD) none->none",
831 	.clo_flags = 0,
832 	.clo_func = oclo_fdup3fd_none
833 }, {
834 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC->none",
835 	.clo_flags = FD_CLOEXEC,
836 	.clo_func = oclo_fdup3fd_none
837 }, {
838 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOFORK->none",
839 	.clo_flags = FD_CLOFORK,
840 	.clo_func = oclo_fdup3fd_none
841 }, {
842 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC|FD_CLOFORK->none",
843 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
844 	.clo_func = oclo_fdup3fd_none
845 }, {
846 	.clo_desc = "fcntl(F_DUP3FD) none->FD_CLOEXEC",
847 	.clo_flags = 0,
848 	.clo_func = oclo_fdup3fd_exec
849 }, {
850 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC->FD_CLOEXEC",
851 	.clo_flags = FD_CLOEXEC,
852 	.clo_func = oclo_fdup3fd_exec
853 }, {
854 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOFORK->FD_CLOEXEC",
855 	.clo_flags = FD_CLOFORK,
856 	.clo_func = oclo_fdup3fd_exec
857 }, {
858 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC|FD_CLOFORK->FD_CLOEXEC",
859 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
860 	.clo_func = oclo_fdup3fd_exec
861 }, {
862 	.clo_desc = "fcntl(F_DUP3FD) none->FD_CLOFORK|FD_CLOEXEC",
863 	.clo_flags = 0,
864 	.clo_func = oclo_fdup3fd_both
865 }, {
866 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC->FD_CLOFORK|FD_CLOEXEC",
867 	.clo_flags = FD_CLOEXEC,
868 	.clo_func = oclo_fdup3fd_both
869 }, {
870 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOFORK->FD_CLOFORK|FD_CLOEXEC",
871 	.clo_flags = FD_CLOFORK,
872 	.clo_func = oclo_fdup3fd_both
873 }, {
874 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC|FD_CLOFORK->"
875 	    "FD_CLOFORK|FD_CLOEXEC",
876 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
877 	.clo_func = oclo_fdup3fd_both
878 }, {
879 	.clo_desc = "fcntl(F_DUP3FD) none->FD_CLOFORK",
880 	.clo_flags = 0,
881 	.clo_func = oclo_fdup3fd_fork
882 }, {
883 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC->FD_CLOFORK",
884 	.clo_flags = FD_CLOEXEC,
885 	.clo_func = oclo_fdup3fd_fork
886 }, {
887 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOFORK->FD_CLOFORK",
888 	.clo_flags = FD_CLOFORK,
889 	.clo_func = oclo_fdup3fd_fork
890 }, {
891 	.clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC|FD_CLOFORK->FD_CLOFORK",
892 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
893 	.clo_func = oclo_fdup3fd_fork
894 }, {
895 	.clo_desc = "dup2() none->none",
896 	.clo_flags = 0,
897 	.clo_func = oclo_dup2
898 }, {
899 	.clo_desc = "dup2() FD_CLOEXEC->none",
900 	.clo_flags = FD_CLOEXEC,
901 	.clo_func = oclo_dup2
902 }, {
903 	.clo_desc = "dup2() FD_CLOFORK->none",
904 	.clo_flags = FD_CLOFORK,
905 	.clo_func = oclo_dup2
906 }, {
907 	.clo_desc = "dup2() FD_CLOEXEC|FD_CLOFORK->none",
908 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
909 	.clo_func = oclo_dup2
910 }, {
911 	.clo_desc = "dup3() none->none",
912 	.clo_flags = 0,
913 	.clo_func = oclo_dup3_none
914 }, {
915 	.clo_desc = "dup3() FD_CLOEXEC->none",
916 	.clo_flags = FD_CLOEXEC,
917 	.clo_func = oclo_dup3_none
918 }, {
919 	.clo_desc = "dup3() FD_CLOFORK->none",
920 	.clo_flags = FD_CLOFORK,
921 	.clo_func = oclo_dup3_none
922 }, {
923 	.clo_desc = "dup3() FD_CLOEXEC|FD_CLOFORK->none",
924 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
925 	.clo_func = oclo_dup3_none
926 }, {
927 	.clo_desc = "dup3() none->FD_CLOEXEC",
928 	.clo_flags = 0,
929 	.clo_func = oclo_dup3_exec
930 }, {
931 	.clo_desc = "dup3() FD_CLOEXEC->FD_CLOEXEC",
932 	.clo_flags = FD_CLOEXEC,
933 	.clo_func = oclo_dup3_exec
934 }, {
935 	.clo_desc = "dup3() FD_CLOFORK->FD_CLOEXEC",
936 	.clo_flags = FD_CLOFORK,
937 	.clo_func = oclo_dup3_exec
938 }, {
939 	.clo_desc = "dup3() FD_CLOEXEC|FD_CLOFORK->FD_CLOEXEC",
940 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
941 	.clo_func = oclo_dup3_exec
942 }, {
943 	.clo_desc = "dup3() none->FD_CLOFORK|FD_CLOEXEC",
944 	.clo_flags = 0,
945 	.clo_func = oclo_dup3_both
946 }, {
947 	.clo_desc = "dup3() FD_CLOEXEC->FD_CLOFORK|FD_CLOEXEC",
948 	.clo_flags = FD_CLOEXEC,
949 	.clo_func = oclo_dup3_both
950 }, {
951 	.clo_desc = "dup3() FD_CLOFORK->FD_CLOFORK|FD_CLOEXEC",
952 	.clo_flags = FD_CLOFORK,
953 	.clo_func = oclo_dup3_both
954 }, {
955 	.clo_desc = "dup3() FD_CLOEXEC|FD_CLOFORK->FD_CLOFORK|FD_CLOEXEC",
956 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
957 	.clo_func = oclo_dup3_both
958 }, {
959 	.clo_desc = "dup3() none->FD_CLOFORK",
960 	.clo_flags = 0,
961 	.clo_func = oclo_dup3_fork
962 }, {
963 	.clo_desc = "dup3() FD_CLOEXEC->FD_CLOFORK",
964 	.clo_flags = FD_CLOEXEC,
965 	.clo_func = oclo_dup3_fork
966 }, {
967 	.clo_desc = "dup3() FD_CLOFORK->FD_CLOFORK",
968 	.clo_flags = FD_CLOFORK,
969 	.clo_func = oclo_dup3_fork
970 }, {
971 	.clo_desc = "dup3() FD_CLOEXEC|FD_CLOFORK->FD_CLOFORK",
972 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
973 	.clo_func = oclo_dup3_fork
974 }, {
975 	.clo_desc = "pipe(2), no flags",
976 	.clo_flags = 0,
977 	.clo_func = oclo_pipe
978 }, {
979 	.clo_desc = "pipe(2), O_CLOEXEC",
980 	.clo_flags = FD_CLOEXEC,
981 	.clo_func = oclo_pipe
982 }, {
983 	.clo_desc = "pipe(2), O_CLOFORK",
984 	.clo_flags = FD_CLOFORK,
985 	.clo_func = oclo_pipe
986 }, {
987 	.clo_desc = "pipe(2), O_CLOEXEC|O_CLOFORK",
988 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
989 	.clo_func = oclo_pipe
990 }, {
991 	.clo_desc = "socket(2), no flags",
992 	.clo_flags = 0,
993 	.clo_func = oclo_socket
994 }, {
995 	.clo_desc = "socket(2), O_CLOEXEC",
996 	.clo_flags = FD_CLOEXEC,
997 	.clo_func = oclo_socket
998 }, {
999 	.clo_desc = "socket(2), O_CLOFORK",
1000 	.clo_flags = FD_CLOFORK,
1001 	.clo_func = oclo_socket
1002 }, {
1003 	.clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK",
1004 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1005 	.clo_func = oclo_socket
1006 }, {
1007 	.clo_desc = "socket(2), no flags->accept() none",
1008 	.clo_flags = 0,
1009 	.clo_func = oclo_accept
1010 }, {
1011 	.clo_desc = "socket(2), O_CLOEXEC->accept() none",
1012 	.clo_flags = FD_CLOEXEC,
1013 	.clo_func = oclo_accept
1014 }, {
1015 	.clo_desc = "socket(2), O_CLOFORK->accept() none",
1016 	.clo_flags = FD_CLOFORK,
1017 	.clo_func = oclo_accept
1018 }, {
1019 	.clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept() none",
1020 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1021 	.clo_func = oclo_accept
1022 }, {
1023 	.clo_desc = "socket(2), no flags->accept4() none",
1024 	.clo_flags = 0,
1025 	.clo_func = oclo_accept4_none
1026 }, {
1027 	.clo_desc = "socket(2), O_CLOEXEC->accept4() none",
1028 	.clo_flags = FD_CLOEXEC,
1029 	.clo_func = oclo_accept4_none
1030 }, {
1031 	.clo_desc = "socket(2), O_CLOFORK->accept4() none",
1032 	.clo_flags = FD_CLOFORK,
1033 	.clo_func = oclo_accept4_none
1034 }, {
1035 	.clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept4() none",
1036 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1037 	.clo_func = oclo_accept4_none
1038 }, {
1039 	.clo_desc = "socket(2), no flags->accept4() SOCK_CLOFORK|SOCK_CLOEXEC",
1040 	.clo_flags = 0,
1041 	.clo_func = oclo_accept4_both
1042 }, {
1043 	.clo_desc = "socket(2), O_CLOEXEC->accept4() SOCK_CLOFORK|SOCK_CLOEXEC",
1044 	.clo_flags = FD_CLOEXEC,
1045 	.clo_func = oclo_accept4_both
1046 }, {
1047 	.clo_desc = "socket(2), O_CLOFORK->accept4() SOCK_CLOFORK|SOCK_CLOEXEC",
1048 	.clo_flags = FD_CLOFORK,
1049 	.clo_func = oclo_accept4_both
1050 }, {
1051 	.clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept4() "
1052 	    "SOCK_CLOFORK|SOCK_CLOEXEC",
1053 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1054 	.clo_func = oclo_accept4_both
1055 }, {
1056 	.clo_desc = "socket(2), no flags->accept4() SOCK_CLOFORK",
1057 	.clo_flags = 0,
1058 	.clo_func = oclo_accept4_fork
1059 }, {
1060 	.clo_desc = "socket(2), O_CLOEXEC->accept4() SOCK_CLOFORK",
1061 	.clo_flags = FD_CLOEXEC,
1062 	.clo_func = oclo_accept4_fork
1063 }, {
1064 	.clo_desc = "socket(2), O_CLOFORK->accept4() SOCK_CLOFORK",
1065 	.clo_flags = FD_CLOFORK,
1066 	.clo_func = oclo_accept4_fork
1067 }, {
1068 	.clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept4() SOCK_CLOFORK",
1069 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1070 	.clo_func = oclo_accept4_fork
1071 }, {
1072 	.clo_desc = "socket(2), no flags->accept4() SOCK_CLOEXEC",
1073 	.clo_flags = 0,
1074 	.clo_func = oclo_accept4_exec
1075 }, {
1076 	.clo_desc = "socket(2), O_CLOEXEC->accept4() SOCK_CLOEXEC",
1077 	.clo_flags = FD_CLOEXEC,
1078 	.clo_func = oclo_accept4_exec
1079 }, {
1080 	.clo_desc = "socket(2), O_CLOFORK->accept4() SOCK_CLOEXEC",
1081 	.clo_flags = FD_CLOFORK,
1082 	.clo_func = oclo_accept4_exec
1083 }, {
1084 	.clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept4() SOCK_CLOEXEC",
1085 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1086 	.clo_func = oclo_accept4_exec
1087 }, {
1088 	.clo_desc = "SCM_RIGHTS none->none",
1089 	.clo_flags = 0,
1090 	.clo_func = oclo_rights_none
1091 }, {
1092 	.clo_desc = "SCM_RIGHTS FD_CLOFORK->none",
1093 	.clo_flags = FD_CLOFORK,
1094 	.clo_func = oclo_rights_none
1095 }, {
1096 	.clo_desc = "SCM_RIGHTS FD_CLOEXEC->none",
1097 	.clo_flags = FD_CLOEXEC,
1098 	.clo_func = oclo_rights_none
1099 }, {
1100 	.clo_desc = "SCM_RIGHTS FD_CLOEXEC|FD_CLOFORK->none",
1101 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1102 	.clo_func = oclo_rights_none
1103 }, {
1104 	.clo_desc = "SCM_RIGHTS none->MSG_CMSG_CLOEXEC",
1105 	.clo_flags = 0,
1106 	.clo_func = oclo_rights_exec
1107 }, {
1108 	.clo_desc = "SCM_RIGHTS FD_CLOFORK->MSG_CMSG_CLOEXEC",
1109 	.clo_flags = FD_CLOFORK,
1110 	.clo_func = oclo_rights_exec
1111 }, {
1112 	.clo_desc = "SCM_RIGHTS FD_CLOEXEC->MSG_CMSG_CLOEXEC",
1113 	.clo_flags = FD_CLOEXEC,
1114 	.clo_func = oclo_rights_exec
1115 }, {
1116 	.clo_desc = "SCM_RIGHTS FD_CLOEXEC|FD_CLOFORK->MSG_CMSG_CLOEXEC",
1117 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1118 	.clo_func = oclo_rights_exec
1119 }, {
1120 	.clo_desc = "SCM_RIGHTS MSG_CMSG_CLOFORK->nMSG_CMSG_CLOFORK",
1121 	.clo_flags = 0,
1122 	.clo_func = oclo_rights_fork
1123 }, {
1124 	.clo_desc = "SCM_RIGHTS FD_CLOFORK->MSG_CMSG_CLOFORK",
1125 	.clo_flags = FD_CLOFORK,
1126 	.clo_func = oclo_rights_fork
1127 }, {
1128 	.clo_desc = "SCM_RIGHTS FD_CLOEXEC->MSG_CMSG_CLOFORK",
1129 	.clo_flags = FD_CLOEXEC,
1130 	.clo_func = oclo_rights_fork
1131 }, {
1132 	.clo_desc = "SCM_RIGHTS FD_CLOEXEC|FD_CLOFORK->MSG_CMSG_CLOFORK",
1133 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1134 	.clo_func = oclo_rights_fork
1135 }, {
1136 	.clo_desc = "SCM_RIGHTS none->MSG_CMSG_CLOEXEC|MSG_CMSG_CLOFORK",
1137 	.clo_flags = 0,
1138 	.clo_func = oclo_rights_both
1139 }, {
1140 	.clo_desc = "SCM_RIGHTS FD_CLOFORK->MSG_CMSG_CLOEXEC|MSG_CMSG_CLOFORK",
1141 	.clo_flags = FD_CLOFORK,
1142 	.clo_func = oclo_rights_both
1143 }, {
1144 	.clo_desc = "SCM_RIGHTS FD_CLOEXEC->MSG_CMSG_CLOEXEC|MSG_CMSG_CLOFORK",
1145 	.clo_flags = FD_CLOEXEC,
1146 	.clo_func = oclo_rights_both
1147 }, {
1148 	.clo_desc = "SCM_RIGHTS FD_CLOEXEC|FD_CLOFORK->"
1149 	    "MSG_CMSG_CLOEXEC|MSG_CMSG_CLOFORK",
1150 	.clo_flags = FD_CLOEXEC | FD_CLOFORK,
1151 	.clo_func = oclo_rights_both
1152 } };
1153 
1154 static bool
oclo_verify_fork(void)1155 oclo_verify_fork(void)
1156 {
1157 	bool ret = true;
1158 
1159 	for (size_t i = 0; i < oclo_rtdata_next; i++) {
1160 		if (!oclo_flags_match(&oclo_rtdata[i], true)) {
1161 			ret = false;
1162 		}
1163 	}
1164 
1165 	return (ret);
1166 }
1167 
1168 /*
1169  * Here we proceed to re-open any fd that was closed due to O_CLOFORK again to
1170  * make sure that the file descriptor makes it to our child verifier.
1171  * Importantly, with the changes that cause O_CLOFORK to be cleared on exec, we
1172  * should not see any file descriptors with the flag in the child. This allows
1173  * us to confirm that this is what we expect.
1174  *
1175  * In addition, this serves as a test to make sure that our opening of the
1176  * lowest fd is correct. While this doesn't actually use the same method as was
1177  * done previously, this should get us most of the way there.
1178  */
1179 static void
oclo_child_reopen(void)1180 oclo_child_reopen(void)
1181 {
1182 	for (size_t i = 0; i < oclo_rtdata_next; i++) {
1183 		int fd;
1184 		int flags = O_RDWR | O_CLOFORK;
1185 
1186 		if ((oclo_rtdata[i].crt_flags & FD_CLOFORK) == 0)
1187 			continue;
1188 
1189 		if ((oclo_rtdata[i].crt_flags & FD_CLOEXEC) != 0)
1190 			flags |= O_CLOEXEC;
1191 
1192 		fd = open("/dev/zero", flags);
1193 		if (fd < 0) {
1194 			err(EXIT_FAILURE, "TEST FAILED: failed to re-open fd "
1195 			    "%d with flags %d", oclo_rtdata[i].crt_fd, flags);
1196 		}
1197 
1198 		if (fd != oclo_rtdata[i].crt_fd) {
1199 			errx(EXIT_FAILURE, "TEST FAILED: re-opening fd %d "
1200 			    "returned fd %d: test design issue or lowest fd "
1201 			    "algorithm is broken", oclo_rtdata[i].crt_fd, fd);
1202 		}
1203 	}
1204 
1205 	(void) printf("TEST PASSED: successfully reopened fds post-fork");
1206 }
1207 
1208 /*
1209  * Look for the verification program in the same directory that this program is
1210  * found in. Note, that isn't the same thing as the current working directory.
1211  */
1212 static void
oclo_exec(void)1213 oclo_exec(void)
1214 {
1215 	ssize_t ret;
1216 	char dir[PATH_MAX], file[PATH_MAX];
1217 	char **argv;
1218 
1219 	ret = readlink("/proc/self/path/a.out", dir, sizeof (dir));
1220 	if (ret < 0) {
1221 		err(EXIT_FAILURE, "TEST FAILED: failed to read our a.out path "
1222 		    "from /proc");
1223 	} else if (ret == 0) {
1224 		errx(EXIT_FAILURE, "TEST FAILED: reading /proc/self/path/a.out "
1225 		    "returned 0 bytes");
1226 	} else if (ret == sizeof (dir)) {
1227 		errx(EXIT_FAILURE, "TEST FAILED: Using /proc/self/path/a.out "
1228 		    "requires truncation");
1229 	}
1230 
1231 	if (snprintf(file, sizeof (file), "%s/%s", dirname(dir), OCLO_VERIFY) >=
1232 	    sizeof (file)) {
1233 		errx(EXIT_FAILURE, "TEST FAILED: cannot assemble exec path "
1234 		    "name: internal buffer overflow");
1235 	}
1236 
1237 	/* We need an extra for both the NULL terminator and the program name */
1238 	argv = calloc(oclo_rtdata_next + 2, sizeof (char *));
1239 	if (argv == NULL) {
1240 		err(EXIT_FAILURE, "TEST FAILED: failed to allocate exec "
1241 		    "argument array");
1242 	}
1243 
1244 	argv[0] = file;
1245 	for (size_t i = 0; i < oclo_rtdata_next; i++) {
1246 		if (asprintf(&argv[i + 1], "0x%x", oclo_rtdata[i].crt_flags) ==
1247 		    -1) {
1248 			err(EXIT_FAILURE, "TEST FAILED: failed to assemble "
1249 			    "exec argument %zu", i + 1);
1250 		}
1251 	}
1252 
1253 	(void) execv(file, argv);
1254 	warn("TEST FAILED: failed to exec verifier %s", file);
1255 }
1256 
1257 int
main(void)1258 main(void)
1259 {
1260 	int ret = EXIT_SUCCESS;
1261 	siginfo_t cret;
1262 
1263 	/*
1264 	 * Before we do anything else close all FDs that aren't standard. We
1265 	 * don't want anything the test suite environment may have left behind.
1266 	 */
1267 	(void) closefrom(STDERR_FILENO + 1);
1268 
1269 	/*
1270 	 * Treat failure during this set up phase as a hard failure. There's no
1271 	 * reason to continue if we can't successfully create the FDs we expect.
1272 	 */
1273 	for (size_t i = 0; i < ARRAY_SIZE(oclo_create); i++) {
1274 		oclo_create[i].clo_func(&oclo_create[i]);
1275 	}
1276 
1277 	pid_t child = forkx(FORK_NOSIGCHLD | FORK_WAITPID);
1278 	if (child == 0) {
1279 		if (!oclo_verify_fork()) {
1280 			ret = EXIT_FAILURE;
1281 		}
1282 
1283 		oclo_child_reopen();
1284 
1285 		oclo_exec();
1286 		ret = EXIT_FAILURE;
1287 		_exit(ret);
1288 	}
1289 
1290 	if (waitid(P_PID, child, &cret, WEXITED) < 0) {
1291 		err(EXIT_FAILURE, "TEST FAILED: internal test failure waiting "
1292 		    "for forked child to report");
1293 	}
1294 
1295 	if (cret.si_code != CLD_EXITED) {
1296 		warnx("TEST FAILED: child process did not successfully exit: "
1297 		    "found si_code: %d", cret.si_code);
1298 		ret = EXIT_FAILURE;
1299 	} else if (cret.si_status != 0) {
1300 		warnx("TEST FAILED: child process did not exit with code 0: "
1301 		    "found %d", cret.si_status);
1302 		ret = EXIT_FAILURE;
1303 	}
1304 
1305 	if (ret == EXIT_SUCCESS) {
1306 		(void) printf("All tests passed successfully\n");
1307 	}
1308 
1309 	return (ret);
1310 }
1311