Home | History | Annotate | Line # | Download | only in kernel
      1 /*	$NetBSD: t_cloexec.c,v 1.1 2024/11/10 15:57:32 riastradh Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2024 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     26  * POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include <sys/cdefs.h>
     30 __RCSID("$NetBSD: t_cloexec.c,v 1.1 2024/11/10 15:57:32 riastradh Exp $");
     31 
     32 #include <sys/types.h>
     33 
     34 #include <sys/bitops.h>
     35 #include <sys/event.h>
     36 #include <sys/ioctl.h>
     37 #include <sys/socket.h>
     38 #include <sys/un.h>
     39 #include <sys/wait.h>
     40 
     41 #include <atf-c.h>
     42 #include <fcntl.h>
     43 #include <limits.h>
     44 #include <spawn.h>
     45 #include <stdio.h>
     46 #include <unistd.h>
     47 
     48 #include "h_macros.h"
     49 
     50 /*
     51  * Test close-on-exec as set in various ways
     52  */
     53 
     54 static int
     55 open_via_accept4(void)
     56 {
     57 	static const union {
     58 		struct sockaddr sa;
     59 		struct sockaddr_un sun;
     60 	} name = { .sun = {
     61 		.sun_family = AF_LOCAL,
     62 		.sun_path = "socket",
     63 	} };
     64 	int slisten, saccept, c;
     65 
     66 	/*
     67 	 * Create a listening server socket and bind it to the path.
     68 	 */
     69 	RL(slisten = socket(PF_LOCAL, SOCK_STREAM, 0));
     70 	RL(bind(slisten, &name.sa, sizeof(name)));
     71 	RL(listen(slisten, SOMAXCONN));
     72 
     73 	/*
     74 	 * Create an active client socket and connect it to the path --
     75 	 * nonblocking, so we don't deadlock here.  If connect doesn't
     76 	 * succeed immediately, it had better fail immediately with
     77 	 * EINPROGRESS.
     78 	 */
     79 	RL(c = socket(PF_LOCAL, SOCK_STREAM|SOCK_NONBLOCK, 0));
     80 	if (connect(c, &name.sa, sizeof(name)) == -1) {
     81 		ATF_CHECK_EQ_MSG(errno, EINPROGRESS, "connect failed %d: %s",
     82 		    errno, strerror(errno));
     83 	}
     84 
     85 	/*
     86 	 * Accept a socket on the server side with SOCK_CLOEXEC.
     87 	 */
     88 	RL(saccept = accept4(slisten, /*addr*/NULL, /*addrlen*/NULL,
     89 		SOCK_CLOEXEC));
     90 	return saccept;
     91 }
     92 
     93 static int
     94 open_via_clonedev(void)
     95 {
     96 	int fd;
     97 
     98 	RL(fd = open("/dev/drvctl", O_RDONLY|O_CLOEXEC));
     99 
    100 	return fd;
    101 }
    102 
    103 static int
    104 open_via_dup3(void)
    105 {
    106 	int fd3;
    107 
    108 	RL(fd3 = dup3(STDIN_FILENO, 3, O_CLOEXEC));
    109 	ATF_REQUIRE_EQ_MSG(fd3, 3, "dup3(STDIN_FILENO, 3, ...)"
    110 	    " failed to return 3: %d", fd3);
    111 
    112 	return fd3;
    113 }
    114 
    115 static int
    116 open_via_fcntldupfd(void)
    117 {
    118 	int fd;
    119 
    120 	RL(fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 0));
    121 
    122 	return fd;
    123 }
    124 
    125 static int
    126 open_via_kqueue(void)
    127 {
    128 	int fd;
    129 
    130 	RL(fd = kqueue1(O_CLOEXEC));
    131 
    132 	return fd;
    133 }
    134 
    135 static int
    136 open_via_opencloexec(void)
    137 {
    138 	int fd;
    139 
    140 	RL(fd = open("file", O_RDWR|O_CREAT|O_CLOEXEC, 0644));
    141 
    142 	return fd;
    143 }
    144 
    145 static int
    146 open_via_openfcntlcloexec(void)
    147 {
    148 	int fd;
    149 
    150 	RL(fd = open("file", O_RDWR|O_CREAT, 0644));
    151 	RL(fcntl(fd, F_SETFD, FD_CLOEXEC));
    152 
    153 	return fd;
    154 }
    155 
    156 static int
    157 open_via_openioctlfioclex(void)
    158 {
    159 	int fd;
    160 
    161 	RL(fd = open("file", O_RDWR|O_CREAT, 0644));
    162 	RL(ioctl(fd, FIOCLEX));
    163 
    164 	return fd;
    165 }
    166 
    167 static int
    168 open_via_pipe2rd(void)
    169 {
    170 	int fd[2];
    171 
    172 	RL(pipe2(fd, O_CLOEXEC));
    173 
    174 	return fd[0];
    175 }
    176 
    177 static int
    178 open_via_pipe2wr(void)
    179 {
    180 	int fd[2];
    181 
    182 	RL(pipe2(fd, O_CLOEXEC));
    183 
    184 	return fd[1];
    185 }
    186 
    187 static int
    188 open_via_paccept(void)
    189 {
    190 	static const union {
    191 		struct sockaddr sa;
    192 		struct sockaddr_un sun;
    193 	} name = { .sun = {
    194 		.sun_family = AF_LOCAL,
    195 		.sun_path = "socket",
    196 	} };
    197 	int slisten, saccept, c;
    198 
    199 	/*
    200 	 * Create a listening server socket and bind it to the path.
    201 	 */
    202 	RL(slisten = socket(PF_LOCAL, SOCK_STREAM, 0));
    203 	RL(bind(slisten, &name.sa, sizeof(name)));
    204 	RL(listen(slisten, SOMAXCONN));
    205 
    206 	/*
    207 	 * Create an active client socket and connect it to the path --
    208 	 * nonblocking, so we don't deadlock here.  If connect doesn't
    209 	 * succeed immediately, it had better fail immediately with
    210 	 * EINPROGRESS.
    211 	 */
    212 	RL(c = socket(PF_LOCAL, SOCK_STREAM|SOCK_NONBLOCK, 0));
    213 	if (connect(c, &name.sa, sizeof(name)) == -1) {
    214 		ATF_CHECK_EQ_MSG(errno, EINPROGRESS, "connect failed %d: %s",
    215 		    errno, strerror(errno));
    216 	}
    217 
    218 	/*
    219 	 * Accept a socket on the server side with SOCK_CLOEXEC.
    220 	 */
    221 	RL(saccept = paccept(slisten, /*addr*/NULL, /*addrlen*/NULL,
    222 		/*sigmask*/NULL, SOCK_CLOEXEC));
    223 	return saccept;
    224 }
    225 
    226 static int
    227 open_via_socket(void)
    228 {
    229 	int fd;
    230 
    231 	RL(fd = socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0));
    232 
    233 	return fd;
    234 }
    235 
    236 static int
    237 open_via_socketpair0(void)
    238 {
    239 	int fd[2];
    240 
    241 	RL(socketpair(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0, fd));
    242 
    243 	return fd[0];
    244 }
    245 
    246 static int
    247 open_via_socketpair1(void)
    248 {
    249 	int fd[2];
    250 
    251 	RL(socketpair(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0, fd));
    252 
    253 	return fd[1];
    254 }
    255 
    256 /*
    257  * XXX Close-on-exec paths still missing:
    258  * XXX
    259  * XXX compat_linux inotify
    260  * XXX compat_linux close_range
    261  * XXX drm i915_perf_open_ioctl
    262  * XXX drm dma_buf
    263  * XXX eventfd(2)
    264  * XXX memfd(2)
    265  * XXX timerfd(2)
    266  * XXX recvmsg/recvmmsg with MSG_CMSG_CLOEXEC
    267  */
    268 
    269 static void
    270 check_cloexec(const struct atf_tc *tc, int fd,
    271     pid_t (*execfn)(char *, char *const[]))
    272 {
    273 	char h_cloexec[PATH_MAX];
    274 	char fdstr[(ilog2(INT_MAX) + 1)/(ilog2(10) - 1) + 1];
    275 	char *const argv[] = {__UNCONST("h_cloexec"), fdstr, NULL};
    276 	pid_t child, waitedpid;
    277 	int status;
    278 
    279 	/*
    280 	 * Format the h_cloexec helper executable path, which lives in
    281 	 * the test's directory (typically /usr/tests/kernel), and the
    282 	 * argument of a file descriptor in decimal.
    283 	 */
    284 	snprintf(h_cloexec, sizeof(h_cloexec), "%s/h_cloexec",
    285 	    atf_tc_get_config_var(tc, "srcdir"));
    286 	snprintf(fdstr, sizeof(fdstr), "%d", fd);
    287 
    288 	/*
    289 	 * Execute h_cloexec as a subprocess.
    290 	 */
    291 	child = (*execfn)(h_cloexec, argv);
    292 
    293 	/*
    294 	 * Wait for the child to complete.
    295 	 */
    296 	RL(waitedpid = waitpid(child, &status, 0));
    297 	ATF_CHECK_EQ_MSG(child, waitedpid, "waited for %jd, got %jd",
    298 	    (intmax_t)child, (intmax_t)waitedpid);
    299 
    300 	/*
    301 	 * Verify the child exited normally.
    302 	 */
    303 	if (WIFSIGNALED(status)) {
    304 		atf_tc_fail("subprocess terminated on signal %d",
    305 		    WTERMSIG(status));
    306 		return;
    307 	} else if (!WIFEXITED(status)) {
    308 		atf_tc_fail("subprocess failed to exit normally: status=0x%x",
    309 		    status);
    310 		return;
    311 	}
    312 
    313 	/*
    314 	 * h_cloexec is supposed to exit status 0 if an operation on
    315 	 * the fd failed with EBADFD, 1 if it unexpectedly succeeded,
    316 	 * 127 if exec returned, or something else if anything else
    317 	 * happened.
    318 	 */
    319 	switch (WEXITSTATUS(status)) {
    320 	case 0:			/* success -- closed on exec */
    321 		return;
    322 	case 1:			/* fail -- not closed on exec */
    323 		atf_tc_fail("fd was not closed on exec");
    324 		return;
    325 	case 127:		/* exec failed */
    326 		atf_tc_fail("failed to exec h_cloexec");
    327 		return;
    328 	default:		/* something else went wong */
    329 		atf_tc_fail("h_cloexec failed unexpectedly: %d",
    330 		    WEXITSTATUS(status));
    331 		return;
    332 	}
    333 }
    334 
    335 static pid_t
    336 exec_via_forkexecve(char *prog, char *const argv[])
    337 {
    338 	pid_t pid;
    339 
    340 	RL(pid = fork());
    341 	if (pid == 0) {		/* child */
    342 		if (execve(prog, argv, /*envp*/NULL) == -1)
    343 			_exit(127);
    344 		abort();
    345 	}
    346 
    347 	/* parent */
    348 	return pid;
    349 }
    350 
    351 static pid_t
    352 exec_via_vforkexecve(char *prog, char *const argv[])
    353 {
    354 	pid_t pid;
    355 
    356 	RL(pid = vfork());
    357 	if (pid == 0) {		/* child */
    358 		if (execve(prog, argv, /*envp*/NULL) == -1)
    359 			_exit(127);
    360 		abort();
    361 	}
    362 
    363 	/* parent */
    364 	return pid;
    365 }
    366 
    367 static pid_t
    368 exec_via_posixspawn(char *prog, char *const argv[])
    369 {
    370 	pid_t pid;
    371 
    372 	RZ(posix_spawn(&pid, prog, /*file_actions*/NULL, /*attrp*/NULL, argv,
    373 		/*envp*/NULL));
    374 
    375 	return pid;
    376 }
    377 
    378 /*
    379  * Full cartesian product is not really important here -- the paths for
    380  * open and the paths for exec are independent.  So we try
    381  * pipe2(O_CLOEXEC) with each exec path, and we try each open path with
    382  * posix_spawn.
    383  */
    384 
    385 #define	CLOEXEC_TEST(test, openvia, execvia, descr)			      \
    386 ATF_TC(test);								      \
    387 ATF_TC_HEAD(test, tc)							      \
    388 {									      \
    389 	atf_tc_set_md_var(tc, "descr", descr);				      \
    390 }									      \
    391 ATF_TC_BODY(test, tc)							      \
    392 {									      \
    393 	check_cloexec(tc, openvia(), &execvia);				      \
    394 }
    395 
    396 CLOEXEC_TEST(pipe2rd_forkexecve, open_via_pipe2rd, exec_via_forkexecve,
    397     "pipe2(O_CLOEXEC) reader is closed in child on fork/exec")
    398 CLOEXEC_TEST(pipe2rd_vforkexecve, open_via_pipe2rd, exec_via_vforkexecve,
    399     "pipe2(O_CLOEXEC) reader is closed in child on vfork/exec")
    400 CLOEXEC_TEST(pipe2rd_posixspawn, open_via_pipe2rd, exec_via_posixspawn,
    401     "pipe2(O_CLOEXEC) reader is closed in child on posix_spawn")
    402 
    403 CLOEXEC_TEST(accept4_posixspawn, open_via_accept4, exec_via_posixspawn,
    404     "accept4(SOCK_CLOEXEC) is closed in child on posix_spawn");
    405 CLOEXEC_TEST(clonedev_posixspawn, open_via_clonedev, exec_via_posixspawn,
    406     "open(\"/dev/drvctl\") is closed in child on posix_spawn");
    407 CLOEXEC_TEST(dup3_posixspawn, open_via_dup3, exec_via_posixspawn,
    408     "dup3(..., O_CLOEXEC) is closed in child on posix_spawn");
    409 CLOEXEC_TEST(fcntldupfd_posixspawn, open_via_fcntldupfd, exec_via_posixspawn,
    410     "fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC) is closed in child on posix_spawn");
    411 CLOEXEC_TEST(kqueue_posixspawn, open_via_kqueue, exec_via_posixspawn,
    412     "kqueue1(O_CLOEXEC) is closed in child on posix_spawn");
    413 CLOEXEC_TEST(opencloexec_posixspawn, open_via_opencloexec, exec_via_posixspawn,
    414     "open(O_CLOEXEC) is closed in child on posix_spawn");
    415 CLOEXEC_TEST(openfcntlcloexec_posixspawn, open_via_openfcntlcloexec,
    416     exec_via_posixspawn,
    417     "fcntl(open(...), F_SETFD, O_CLOEXEC) is closed in child on posix_spawn");
    418 CLOEXEC_TEST(openioctlfioclex_posixspawn, open_via_openioctlfioclex,
    419     exec_via_posixspawn,
    420     "ioctl(open(...), FIOCLEX) is closed in child on posix_spawn");
    421 #if 0				/* already done above */
    422 CLOEXEC_TEST(pipe2rd_posixspawn, open_via_pipe2rd, exec_via_posixspawn,
    423     "pipe2(O_CLOEXEC) reader is closed in child on posix_spawn")
    424 #endif
    425 CLOEXEC_TEST(pipe2wr_posixspawn, open_via_pipe2wr, exec_via_posixspawn,
    426     "pipe2(O_CLOEXEC) writer is closed in child on posix_spawn")
    427 CLOEXEC_TEST(paccept_posixspawn, open_via_paccept, exec_via_posixspawn,
    428     "paccept(..., SOCK_CLOEXEC) is closed in child on posix_spawn")
    429 CLOEXEC_TEST(socket_posixspawn, open_via_socket, exec_via_posixspawn,
    430     "socket(SOCK_CLOEXEC) is closed in child on posix_spawn")
    431 CLOEXEC_TEST(socketpair0_posixspawn, open_via_socketpair0, exec_via_posixspawn,
    432     "socketpair(SOCK_CLOEXEC) side 0 is closed in child on posix_spawn")
    433 CLOEXEC_TEST(socketpair1_posixspawn, open_via_socketpair1, exec_via_posixspawn,
    434     "socketpair(SOCK_CLOEXEC) side 1 is closed in child on posix_spawn")
    435 
    436 ATF_TP_ADD_TCS(tp)
    437 {
    438 
    439 	ATF_TP_ADD_TC(tp, accept4_posixspawn);
    440 	ATF_TP_ADD_TC(tp, clonedev_posixspawn);
    441 	ATF_TP_ADD_TC(tp, dup3_posixspawn);
    442 	ATF_TP_ADD_TC(tp, fcntldupfd_posixspawn);
    443 	ATF_TP_ADD_TC(tp, kqueue_posixspawn);
    444 	ATF_TP_ADD_TC(tp, opencloexec_posixspawn);
    445 	ATF_TP_ADD_TC(tp, openfcntlcloexec_posixspawn);
    446 	ATF_TP_ADD_TC(tp, openioctlfioclex_posixspawn);
    447 	ATF_TP_ADD_TC(tp, paccept_posixspawn);
    448 	ATF_TP_ADD_TC(tp, pipe2rd_forkexecve);
    449 	ATF_TP_ADD_TC(tp, pipe2rd_posixspawn);
    450 	ATF_TP_ADD_TC(tp, pipe2rd_vforkexecve);
    451 	ATF_TP_ADD_TC(tp, pipe2wr_posixspawn);
    452 	ATF_TP_ADD_TC(tp, socket_posixspawn);
    453 	ATF_TP_ADD_TC(tp, socketpair0_posixspawn);
    454 	ATF_TP_ADD_TC(tp, socketpair1_posixspawn);
    455 
    456 	return atf_no_error();
    457 }
    458