diff --git a/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo.c b/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo.c new file mode 100644 index 00000000000000..639a7c9a441de5 --- /dev/null +++ b/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo.c @@ -0,0 +1,1369 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2024 Oxide Computer Company + */ + +/* + * Verify the behavior of the various O_CLOFORK and O_CLOEXEC variants. In + * particular getting this via: + * + * - open(2): O_CLOFORK/O_CLOEXEC + * - fcntl(2): F_SETFD FD_CLOFORK/FD_CLOEXEC + * - fcntl(2): F_DUPFD_CLOFORK/F_DUPFD_CLOEXEC + * - fcntl(2): F_DUP2FD_CLOFORK/F_DUP2FD_CLOEXEC + * - dup2(3C) + * - dup3(3C): argument translation + * - pipe2(2) + * - socket(2): SOCK_CLOEXEC/SOCK_CLOFORK + * - accept(2): flags on the listen socket aren't inherited on accept + * - socketpair(3SOCKET) + * - accept4(2): SOCK_CLOEXEC/SOCK_CLOFORK + * - recvmsg(2): SCM_RIGHTS MSG_CMSG_CLOFORK/MSG_CMSG_CLOEXEC + * + * The test is designed such that we have an array of functions that are used to + * create file descriptors with different rules. This is found in the + * oclo_create array. Each file descriptor that is created is then registered + * with information about what is expected about it. A given creation function + * can create more than one file descriptor; however, our expectation is that + * every file descriptor is accounted for (ignoring stdin, stdout, and stderr). + * + * We pass a record of each file descriptor that was recorded to a verification + * program that will verify everything is correctly honored after an exec. + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void *recallocarray(void *, size_t, size_t, size_t); + +#define strerrorname_np(e) (sys_errlist[e]) + +#ifndef FORK_NOSIGCHLD +#define FORK_NOSIGCHLD 0x01 +#endif + +#ifndef FORK_WAITPID +#define FORK_WAITPID 0x02 +#endif + +/* + * Simulate Solaris' forkx() with rfork() + */ +static pid_t +forkx(int flags) +{ + int rfork_flags = RFPROC | RFFDG; + pid_t pid; + + if ((flags & ~(FORK_NOSIGCHLD | FORK_WAITPID)) != 0) { + errno = EINVAL; + return (-1); + } + + if ((flags & FORK_WAITPID) == 0) + rfork_flags |= RFNOWAIT; + + pid = rfork(rfork_flags); + if (pid == -1) + return (-1); + + return (pid); +} + +/* + * Get pathname to avoid reading /proc/curproc/exe + * + * Taken from procstat_getpathname_sysctl() + */ +static int +getpathname(pid_t pid, char *pathname, size_t maxlen) +{ + int error, name[4]; + size_t len; + + name[0] = CTL_KERN; + name[1] = KERN_PROC; + name[2] = KERN_PROC_PATHNAME; + name[3] = pid; + len = maxlen; + error = sysctl(name, nitems(name), pathname, &len, NULL, 0); + if (error != 0 && errno != ESRCH) + warn("sysctl: kern.proc.pathname: %d", pid); + if (len == 0) + pathname[0] = '\0'; + return (error); +} + +/* + * Verification program name. + */ +#define OCLO_VERIFY "ocloexec_verify" + +/* + * This structure represents a table of ways we expect to create file + * descriptors that should have the resulting flags set when done. The table is + * ordered and subsequent iterations are allowed to assume that the ones that + * have gone ahead of them have run and are therefore allowed to access them. + * The create function is expected to return the created fd. + */ +typedef struct clo_create clo_create_t; +struct clo_create { + const char *clo_desc; + int clo_flags; + void (*clo_func)(const clo_create_t *); +}; + +/* + * This is our run-time data. We expect all file descriptors to be registered by + * our calling functions through oclo_record(). + */ +typedef struct clo_rtdata { + const clo_create_t *crt_data; + size_t crt_idx; + int crt_fd; + int crt_flags; + const char *crt_desc; +} clo_rtdata_t; + +static clo_rtdata_t *oclo_rtdata; +static size_t oclo_rtdata_nents = 0; +static size_t oclo_rtdata_next = 0; +static int oclo_nextfd = STDERR_FILENO + 1; + +static bool +oclo_flags_match(const clo_rtdata_t *rt, bool child) +{ + const char *pass = child ? "post-fork" : "pre-fork"; + bool fail = child && (rt->crt_flags & FD_CLOFORK) != 0; + int flags = fcntl(rt->crt_fd, F_GETFD, NULL); + + if (flags < 0) { + int e = errno; + + if (fail) { + if (e == EBADF) { + (void) printf("TEST PASSED: %s (%s): fd %d: " + "correctly closed\n", + rt->crt_data->clo_desc, pass, rt->crt_fd); + return (true); + } + + warn("TEST FAILED: %s (%s): fd %d: expected fcntl to " + "fail with EBADF, but found %s", + rt->crt_data->clo_desc, pass, rt->crt_fd, + strerrorname_np(e)); + return (false); + } + + warnx("TEST FAILED: %s (%s): fd %d: fcntl(F_GETFD) " + "unexpectedly failed", rt->crt_data->clo_desc, pass, + rt->crt_fd); + return (false); + } + + if (fail) { + warnx("TEST FAILED: %s (%s): fd %d: received flags %d, but " + "expected to fail based on flags %d", + rt->crt_data->clo_desc, pass, rt->crt_fd, flags, + rt->crt_fd); + return (false); + } + + if (flags != rt->crt_flags) { + warnx("TEST FAILED: %s (%s): fd %d: discovered flags 0x%x do " + "not match expected flags 0x%x", rt->crt_data->clo_desc, + pass, rt->crt_fd, flags, rt->crt_fd); + return (false); + } + + (void) printf("TEST PASSED: %s (%s): fd %d discovered flags match " + "(0x%x)\n", rt->crt_data->clo_desc, pass, rt->crt_fd, flags); + return (true); +} + + +static void +oclo_record(const clo_create_t *c, int fd, int exp_flags, const char *desc) +{ + if (oclo_rtdata_next == oclo_rtdata_nents) { + size_t newrt = oclo_rtdata_nents + 8; + clo_rtdata_t *rt; + rt = recallocarray(oclo_rtdata, oclo_rtdata_nents, newrt, + sizeof (clo_rtdata_t)); + if (rt == NULL) { + err(EXIT_FAILURE, "TEST_FAILED: internal error " + "expanding fd records to %zu entries", newrt); + } + + oclo_rtdata_nents = newrt; + oclo_rtdata = rt; + } + + if (fd != oclo_nextfd) { + errx(EXIT_FAILURE, "TEST FAILED: internal test error: expected " + "to record next fd %d, given %d", oclo_nextfd, fd); + } + + oclo_rtdata[oclo_rtdata_next].crt_data = c; + oclo_rtdata[oclo_rtdata_next].crt_fd = fd; + oclo_rtdata[oclo_rtdata_next].crt_flags = exp_flags; + oclo_rtdata[oclo_rtdata_next].crt_desc = desc; + + /* + * Matching errors at this phase are fatal as it means we screwed up the + * program pretty badly. + */ + if (!oclo_flags_match(&oclo_rtdata[oclo_rtdata_next], false)) { + exit(EXIT_FAILURE); + } + + oclo_rtdata_next++; + oclo_nextfd++; +} + +static int +oclo_file(const clo_create_t *c) +{ + int flags = O_RDWR, fd; + + if ((c->clo_flags & FD_CLOEXEC) != 0) + flags |= O_CLOEXEC; + if ((c->clo_flags & FD_CLOFORK) != 0) + flags |= O_CLOFORK; + fd = open("/dev/null", flags); + if (fd < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to open /dev/null", + c->clo_desc); + } + + return (fd); +} + +static void +oclo_open(const clo_create_t *c) +{ + oclo_record(c, oclo_file(c), c->clo_flags, NULL); +} + +static void +oclo_setfd_common(const clo_create_t *c, int targ_flags) +{ + int fd = oclo_file(c); + if (fcntl(fd, F_SETFD, targ_flags) < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: F_SETFD failed to set " + "flags to %d", c->clo_desc, targ_flags); + } + + oclo_record(c, fd, targ_flags, NULL); +} + +static void +oclo_setfd_none(const clo_create_t *c) +{ + oclo_setfd_common(c, 0); +} + +static void +oclo_setfd_exec(const clo_create_t *c) +{ + oclo_setfd_common(c, FD_CLOEXEC); +} + +static void +oclo_setfd_fork(const clo_create_t *c) +{ + oclo_setfd_common(c, FD_CLOFORK); +} + +static void +oclo_setfd_both(const clo_create_t *c) +{ + oclo_setfd_common(c, FD_CLOFORK | FD_CLOEXEC); +} + +/* + * Open an fd with flags in a certain form and then use one of the F_DUPFD or + * F_DUP2FD variants and ensure that flags are properly propagated as expected. + */ +static void +oclo_fdup_common(const clo_create_t *c, int targ_flags, int cmd) +{ + int dup, fd; + + fd = oclo_file(c); + oclo_record(c, fd, c->clo_flags, "base"); + switch (cmd) { + case F_DUPFD: + case F_DUPFD_CLOEXEC: + case F_DUPFD_CLOFORK: + dup = fcntl(fd, cmd, fd); + break; + case F_DUP2FD: + case F_DUP2FD_CLOEXEC: +#ifdef F_DUP2FD_CLOFORK + case F_DUP2FD_CLOFORK: +#endif + dup = fcntl(fd, cmd, fd + 1); + break; + case F_DUP3FD: + dup = fcntl(fd, cmd | (targ_flags << F_DUP3FD_SHIFT), fd + 1); + break; + default: + errx(EXIT_FAILURE, "TEST FAILURE: %s: internal error: " + "unexpected fcntl cmd: 0x%x", c->clo_desc, cmd); + } + + if (dup < 0) { + err(EXIT_FAILURE, "TEST FAILURE: %s: failed to dup fd with " + "fcntl command 0x%x", c->clo_desc, cmd); + } + + oclo_record(c, dup, targ_flags, "dup"); +} + +static void +oclo_fdupfd(const clo_create_t *c) +{ + oclo_fdup_common(c, 0, F_DUPFD); +} + +static void +oclo_fdupfd_fork(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOFORK, F_DUPFD_CLOFORK); +} + +static void +oclo_fdupfd_exec(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOEXEC, F_DUPFD_CLOEXEC); +} + +static void +oclo_fdup2fd(const clo_create_t *c) +{ + oclo_fdup_common(c, 0, F_DUP2FD); +} + +#ifdef F_DUP2FD_CLOFORK +static void +oclo_fdup2fd_fork(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOFORK, F_DUP2FD_CLOFORK); +} +#endif + +static void +oclo_fdup2fd_exec(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOEXEC, F_DUP2FD_CLOEXEC); +} + +static void +oclo_fdup3fd_none(const clo_create_t *c) +{ + oclo_fdup_common(c, 0, F_DUP3FD); +} + +static void +oclo_fdup3fd_exec(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOEXEC, F_DUP3FD); +} + +static void +oclo_fdup3fd_fork(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOFORK, F_DUP3FD); +} + +static void +oclo_fdup3fd_both(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOEXEC | FD_CLOFORK, F_DUP3FD); +} + +static void +oclo_dup_common(const clo_create_t *c, int targ_flags, bool v3) +{ + int dup, fd; + fd = oclo_file(c); + oclo_record(c, fd, c->clo_flags, "base"); + if (v3) { + int dflags = 0; + if ((targ_flags & FD_CLOEXEC) != 0) + dflags |= O_CLOEXEC; + if ((targ_flags & FD_CLOFORK) != 0) + dflags |= O_CLOFORK; + dup = dup3(fd, fd + 1, dflags); + } else { + dup = dup2(fd, fd + 1); + } + + oclo_record(c, dup, targ_flags, "dup"); +} + +static void +oclo_dup2(const clo_create_t *c) +{ + oclo_dup_common(c, 0, false); +} + +static void +oclo_dup3_none(const clo_create_t *c) +{ + oclo_dup_common(c, 0, true); +} + +static void +oclo_dup3_exec(const clo_create_t *c) +{ + oclo_dup_common(c, FD_CLOEXEC, true); +} + +static void +oclo_dup3_fork(const clo_create_t *c) +{ + oclo_dup_common(c, FD_CLOFORK, true); +} + +static void +oclo_dup3_both(const clo_create_t *c) +{ + oclo_dup_common(c, FD_CLOEXEC | FD_CLOFORK, true); +} + +static void +oclo_pipe(const clo_create_t *c) +{ + int flags = 0, fds[2]; + + if ((c->clo_flags & FD_CLOEXEC) != 0) + flags |= O_CLOEXEC; + if ((c->clo_flags & FD_CLOFORK) != 0) + flags |= O_CLOFORK; + + if (pipe2(fds, flags) < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: pipe2() with flags %d " + "failed", c->clo_desc, flags); + } + + oclo_record(c, fds[0], c->clo_flags, "pipe[0]"); + oclo_record(c, fds[1], c->clo_flags, "pipe[1]"); +} + +static void +oclo_socket(const clo_create_t *c) +{ + int type = SOCK_DGRAM, fd; + + if ((c->clo_flags & FD_CLOEXEC) != 0) + type |= SOCK_CLOEXEC; + if ((c->clo_flags & FD_CLOFORK) != 0) + type |= SOCK_CLOFORK; + fd = socket(PF_INET, type, 0); + if (fd < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to create socket " + "with flags: 0x%x\n", c->clo_desc, c->clo_flags); + } + + oclo_record(c, fd, c->clo_flags, NULL); +} + +static void +oclo_accept_common(const clo_create_t *c, int targ_flags, bool a4) +{ + int lsock, csock, asock; + int ltype = SOCK_STREAM, atype = 0; + struct sockaddr_in in; + socklen_t slen; + + if ((c->clo_flags & FD_CLOEXEC) != 0) + ltype |= SOCK_CLOEXEC; + if ((c->clo_flags & FD_CLOFORK) != 0) + ltype |= SOCK_CLOFORK; + + if ((targ_flags & FD_CLOEXEC) != 0) + atype |= SOCK_CLOEXEC; + if ((targ_flags & FD_CLOFORK) != 0) + atype |= SOCK_CLOFORK; + + lsock = socket(PF_INET, ltype, 0); + if (lsock < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to create listen " + "socket with flags: 0x%x\n", c->clo_desc, c->clo_flags); + } + + oclo_record(c, lsock, c->clo_flags, "listen"); + (void) memset(&in, 0, sizeof (in)); + in.sin_family = AF_INET; + in.sin_port = 0; + in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(lsock, (struct sockaddr *)&in, sizeof (in)) != 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to bind socket", + c->clo_desc); + } + + slen = sizeof (struct sockaddr_in); + if (getsockname(lsock, (struct sockaddr *)&in, &slen) != 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to discover bound " + "socket address", c->clo_desc); + } + + if (listen(lsock, 5) < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to listen on socket", + c->clo_desc); + } + + csock = socket(PF_INET, SOCK_STREAM, 0); + if (csock < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to create client " + "socket", c->clo_desc); + } + oclo_record(c, csock, 0, "connect"); + + if (connect(csock, (struct sockaddr *)&in, sizeof (in)) != 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to connect to " + "server socket", c->clo_desc); + } + + if (a4) { + asock = accept4(lsock, NULL, NULL, atype); + } else { + asock = accept(lsock, NULL, NULL); + } + if (asock < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to accept client " + "connection", c->clo_desc); + } + oclo_record(c, asock, targ_flags, "accept"); +} + +static void +oclo_accept(const clo_create_t *c) +{ + oclo_accept_common(c, 0, false); +} + +static void +oclo_accept4_none(const clo_create_t *c) +{ + oclo_accept_common(c, 0, true); +} + +static void +oclo_accept4_fork(const clo_create_t *c) +{ + oclo_accept_common(c, FD_CLOFORK, true); +} + +static void +oclo_accept4_exec(const clo_create_t *c) +{ + oclo_accept_common(c, FD_CLOEXEC, true); +} + +static void +oclo_accept4_both(const clo_create_t *c) +{ + oclo_accept_common(c, FD_CLOEXEC | FD_CLOFORK, true); +} + +/* + * Go through the process of sending ourselves a file descriptor. + */ +static void +oclo_rights_common(const clo_create_t *c, int targ_flags) +{ + int pair[2], type = SOCK_DGRAM, sflags = 0; + int tosend = oclo_file(c), recvfd; + uint32_t data = 0x7777; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cm; + + if ((c->clo_flags & FD_CLOEXEC) != 0) + type |= SOCK_CLOEXEC; + if ((c->clo_flags & FD_CLOFORK) != 0) + type |= SOCK_CLOFORK; + + if (socketpair(PF_UNIX, type, 0, pair) < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to create socket " + "pair", c->clo_desc); + } + + oclo_record(c, tosend, c->clo_flags, "send fd"); + oclo_record(c, pair[0], c->clo_flags, "pair[0]"); + oclo_record(c, pair[1], c->clo_flags, "pair[1]"); + + iov.iov_base = (void *)&data; + iov.iov_len = sizeof (data); + + (void) memset(&msg, 0, sizeof (msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_controllen = CMSG_SPACE(sizeof (int)); + + msg.msg_control = calloc(1, msg.msg_controllen); + if (msg.msg_control == NULL) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to allocate %u " + "bytes for SCM_RIGHTS control message", c->clo_desc, + msg.msg_controllen); + } + + cm = CMSG_FIRSTHDR(&msg); + cm->cmsg_len = CMSG_LEN(sizeof (int)); + cm->cmsg_level = SOL_SOCKET; + cm->cmsg_type = SCM_RIGHTS; + (void) memcpy(CMSG_DATA(cm), &tosend, sizeof (tosend)); + + if ((targ_flags & FD_CLOEXEC) != 0) + sflags |= MSG_CMSG_CLOEXEC; + if ((targ_flags & FD_CLOFORK) != 0) + sflags |= MSG_CMSG_CLOFORK; + + if (sendmsg(pair[0], &msg, 0) < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to send fd", + c->clo_desc); + } + + data = 0; + if (recvmsg(pair[1], &msg, sflags) < 0) { + err(EXIT_FAILURE, "TEST FAILED: %s: failed to get fd", + c->clo_desc); + } + + if (data != 0x7777) { + errx(EXIT_FAILURE, "TEST FAILED: %s: did not receive correct " + "data: expected 0x7777, found 0x%x", c->clo_desc, data); + } + + if (msg.msg_controllen < CMSG_SPACE(sizeof (int))) { + errx(EXIT_FAILURE, "TEST FAILED: %s: found insufficient " + "message control length: expected at least 0x%lx, found " + "0x%x", c->clo_desc, CMSG_SPACE(sizeof (int)), + msg.msg_controllen); + } + + cm = CMSG_FIRSTHDR(&msg); + if (cm->cmsg_level != SOL_SOCKET || cm->cmsg_type != SCM_RIGHTS) { + errx(EXIT_FAILURE, "TEST FAILED: %s: found surprising cmsg " + "0x%x/0x%x, expected 0x%x/0x%x", c->clo_desc, + cm->cmsg_level, cm->cmsg_type, SOL_SOCKET, SCM_RIGHTS); + } + + if (cm->cmsg_len != CMSG_LEN(sizeof (int))) { + errx(EXIT_FAILURE, "TEST FAILED: %s: found unexpected " + "SCM_RIGHTS length 0x%x: expected 0x%zx", c->clo_desc, + cm->cmsg_len, CMSG_LEN(sizeof (int))); + } + + (void) memcpy(&recvfd, CMSG_DATA(cm), sizeof (recvfd)); + oclo_record(c, recvfd, targ_flags, "SCM_RIGHTS"); +} + +static void +oclo_rights_none(const clo_create_t *c) +{ + oclo_rights_common(c, 0); +} + +static void +oclo_rights_exec(const clo_create_t *c) +{ + oclo_rights_common(c, FD_CLOEXEC); +} + +static void +oclo_rights_fork(const clo_create_t *c) +{ + oclo_rights_common(c, FD_CLOFORK); +} + +static void +oclo_rights_both(const clo_create_t *c) +{ + oclo_rights_common(c, FD_CLOEXEC | FD_CLOFORK); +} + +static const clo_create_t oclo_create[] = { { + .clo_desc = "open(2), no flags", + .clo_flags = 0, + .clo_func = oclo_open +}, { + .clo_desc = "open(2), O_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_open +}, { + .clo_desc = "open(2), O_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_open +}, { + .clo_desc = "open(2), O_CLOEXEC|O_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_open +}, { + .clo_desc = "fcntl(F_SETFD) no flags->no flags", + .clo_flags = 0, + .clo_func = oclo_setfd_none +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOFORK|O_CLOEXEC->no flags", + .clo_flags = O_CLOFORK | O_CLOEXEC, + .clo_func = oclo_setfd_none +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOEXEC->no flags", + .clo_flags = O_CLOEXEC, + .clo_func = oclo_setfd_none +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOFORK->no flags", + .clo_flags = O_CLOFORK, + .clo_func = oclo_setfd_none +}, { + .clo_desc = "fcntl(F_SETFD) no flags->O_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_setfd_exec +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOFORK|O_CLOEXEC->O_CLOEXEC", + .clo_flags = O_CLOFORK | O_CLOEXEC, + .clo_func = oclo_setfd_exec +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOEXEC->O_CLOEXEC", + .clo_flags = O_CLOEXEC, + .clo_func = oclo_setfd_exec +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOFORK->O_CLOEXEC", + .clo_flags = O_CLOFORK, + .clo_func = oclo_setfd_exec +}, { + .clo_desc = "fcntl(F_SETFD) no flags->O_CLOFORK", + .clo_flags = 0, + .clo_func = oclo_setfd_fork +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOFORK|O_CLOEXEC->O_CLOFORK", + .clo_flags = O_CLOFORK | O_CLOEXEC, + .clo_func = oclo_setfd_fork +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOEXEC->O_CLOFORK", + .clo_flags = O_CLOEXEC, + .clo_func = oclo_setfd_fork +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOFORK->O_CLOFORK", + .clo_flags = O_CLOFORK, + .clo_func = oclo_setfd_fork +}, { + .clo_desc = "fcntl(F_SETFD) no flags->O_CLOFORK|O_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_setfd_both +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOFORK|O_CLOEXEC->O_CLOFORK|O_CLOEXEC", + .clo_flags = O_CLOFORK | O_CLOEXEC, + .clo_func = oclo_setfd_both +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOEXEC->O_CLOFORK|O_CLOEXEC", + .clo_flags = O_CLOEXEC, + .clo_func = oclo_setfd_both +}, { + .clo_desc = "fcntl(F_SETFD) O_CLOFORK->O_CLOFORK|O_CLOEXEC", + .clo_flags = O_CLOFORK, + .clo_func = oclo_setfd_both +}, { + .clo_desc = "fcntl(F_DUPFD) none->none", + .clo_flags = 0, + .clo_func = oclo_fdupfd +}, { + .clo_desc = "fcntl(F_DUPFD) FD_CLOEXEC->none", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdupfd +}, { + .clo_desc = "fcntl(F_DUPFD) FD_CLOFORK->none", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdupfd +}, { + .clo_desc = "fcntl(F_DUPFD) FD_CLOEXEC|FD_CLOFORK->none", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdupfd +}, { + .clo_desc = "fcntl(F_DUPFD_CLOFORK) none", + .clo_flags = 0, + .clo_func = oclo_fdupfd_fork +}, { + .clo_desc = "fcntl(F_DUPFD_CLOFORK) FD_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdupfd_fork +}, { + .clo_desc = "fcntl(F_DUPFD_CLOFORK) FD_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdupfd_fork +}, { + .clo_desc = "fcntl(F_DUPFD_CLOFORK) FD_CLOEXEC|FD_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdupfd_fork +}, { + .clo_desc = "fcntl(F_DUPFD_CLOEXEC) none", + .clo_flags = 0, + .clo_func = oclo_fdupfd_exec +}, { + .clo_desc = "fcntl(F_DUPFD_CLOEXEC) FD_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdupfd_exec +}, { + .clo_desc = "fcntl(F_DUPFD_CLOEXEC) FD_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdupfd_exec +}, { + .clo_desc = "fcntl(F_DUPFD_CLOEXEC) FD_CLOEXEC|FD_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdupfd_exec +}, { + .clo_desc = "fcntl(F_DUP2FD) none->none", + .clo_flags = 0, + .clo_func = oclo_fdup2fd +}, { + .clo_desc = "fcntl(F_DUP2FD) FD_CLOEXEC->none", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdup2fd +}, { + .clo_desc = "fcntl(F_DUP2FD) FD_CLOFORK->none", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdup2fd +}, { + .clo_desc = "fcntl(F_DUP2FD) FD_CLOEXEC|FD_CLOFORK->none", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdup2fd +}, { +#ifdef F_DUP2FD_CLOFORK + .clo_desc = "fcntl(F_DUP2FD_CLOFORK) none", + .clo_flags = 0, + .clo_func = oclo_fdup2fd_fork +}, { + .clo_desc = "fcntl(F_DUP2FD_CLOFORK) FD_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdup2fd_fork +}, { + .clo_desc = "fcntl(F_DUP2FD_CLOFORK) FD_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdup2fd_fork +}, { + .clo_desc = "fcntl(F_DUP2FD_CLOFORK) FD_CLOEXEC|FD_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdup2fd_fork +}, { +#endif + .clo_desc = "fcntl(F_DUP2FD_CLOEXEC) none", + .clo_flags = 0, + .clo_func = oclo_fdup2fd_exec +}, { + .clo_desc = "fcntl(F_DUP2FD_CLOEXEC) FD_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdup2fd_exec +}, { + .clo_desc = "fcntl(F_DUP2FD_CLOEXEC) FD_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdup2fd_exec +}, { + .clo_desc = "fcntl(F_DUP2FD_CLOEXEC) FD_CLOEXEC|FD_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdup2fd_exec +}, { + .clo_desc = "fcntl(F_DUP3FD) none->none", + .clo_flags = 0, + .clo_func = oclo_fdup3fd_none +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC->none", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdup3fd_none +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOFORK->none", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdup3fd_none +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC|FD_CLOFORK->none", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdup3fd_none +}, { + .clo_desc = "fcntl(F_DUP3FD) none->FD_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_fdup3fd_exec +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC->FD_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdup3fd_exec +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOFORK->FD_CLOEXEC", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdup3fd_exec +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC|FD_CLOFORK->FD_CLOEXEC", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdup3fd_exec +}, { + .clo_desc = "fcntl(F_DUP3FD) none->FD_CLOFORK|FD_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_fdup3fd_both +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC->FD_CLOFORK|FD_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdup3fd_both +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOFORK->FD_CLOFORK|FD_CLOEXEC", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdup3fd_both +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC|FD_CLOFORK->" + "FD_CLOFORK|FD_CLOEXEC", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdup3fd_both +}, { + .clo_desc = "fcntl(F_DUP3FD) none->FD_CLOFORK", + .clo_flags = 0, + .clo_func = oclo_fdup3fd_fork +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC->FD_CLOFORK", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_fdup3fd_fork +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOFORK->FD_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_fdup3fd_fork +}, { + .clo_desc = "fcntl(F_DUP3FD) FD_CLOEXEC|FD_CLOFORK->FD_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_fdup3fd_fork +}, { + .clo_desc = "dup2() none->none", + .clo_flags = 0, + .clo_func = oclo_dup2 +}, { + .clo_desc = "dup2() FD_CLOEXEC->none", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_dup2 +}, { + .clo_desc = "dup2() FD_CLOFORK->none", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_dup2 +}, { + .clo_desc = "dup2() FD_CLOEXEC|FD_CLOFORK->none", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_dup2 +}, { + .clo_desc = "dup3() none->none", + .clo_flags = 0, + .clo_func = oclo_dup3_none +}, { + .clo_desc = "dup3() FD_CLOEXEC->none", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_dup3_none +}, { + .clo_desc = "dup3() FD_CLOFORK->none", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_dup3_none +}, { + .clo_desc = "dup3() FD_CLOEXEC|FD_CLOFORK->none", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_dup3_none +}, { + .clo_desc = "dup3() none->FD_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_dup3_exec +}, { + .clo_desc = "dup3() FD_CLOEXEC->FD_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_dup3_exec +}, { + .clo_desc = "dup3() FD_CLOFORK->FD_CLOEXEC", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_dup3_exec +}, { + .clo_desc = "dup3() FD_CLOEXEC|FD_CLOFORK->FD_CLOEXEC", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_dup3_exec +}, { + .clo_desc = "dup3() none->FD_CLOFORK|FD_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_dup3_both +}, { + .clo_desc = "dup3() FD_CLOEXEC->FD_CLOFORK|FD_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_dup3_both +}, { + .clo_desc = "dup3() FD_CLOFORK->FD_CLOFORK|FD_CLOEXEC", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_dup3_both +}, { + .clo_desc = "dup3() FD_CLOEXEC|FD_CLOFORK->FD_CLOFORK|FD_CLOEXEC", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_dup3_both +}, { + .clo_desc = "dup3() none->FD_CLOFORK", + .clo_flags = 0, + .clo_func = oclo_dup3_fork +}, { + .clo_desc = "dup3() FD_CLOEXEC->FD_CLOFORK", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_dup3_fork +}, { + .clo_desc = "dup3() FD_CLOFORK->FD_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_dup3_fork +}, { + .clo_desc = "dup3() FD_CLOEXEC|FD_CLOFORK->FD_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_dup3_fork +}, { + .clo_desc = "pipe(2), no flags", + .clo_flags = 0, + .clo_func = oclo_pipe +}, { + .clo_desc = "pipe(2), O_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_pipe +}, { + .clo_desc = "pipe(2), O_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_pipe +}, { + .clo_desc = "pipe(2), O_CLOEXEC|O_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_pipe +}, { + .clo_desc = "socket(2), no flags", + .clo_flags = 0, + .clo_func = oclo_socket +}, { + .clo_desc = "socket(2), O_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_socket +}, { + .clo_desc = "socket(2), O_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_socket +}, { + .clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_socket +}, { + .clo_desc = "socket(2), no flags->accept() none", + .clo_flags = 0, + .clo_func = oclo_accept +}, { + .clo_desc = "socket(2), O_CLOEXEC->accept() none", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_accept +}, { + .clo_desc = "socket(2), O_CLOFORK->accept() none", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_accept +}, { + .clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept() none", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_accept +}, { + .clo_desc = "socket(2), no flags->accept4() none", + .clo_flags = 0, + .clo_func = oclo_accept4_none +}, { + .clo_desc = "socket(2), O_CLOEXEC->accept4() none", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_accept4_none +}, { + .clo_desc = "socket(2), O_CLOFORK->accept4() none", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_accept4_none +}, { + .clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept4() none", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_accept4_none +}, { + .clo_desc = "socket(2), no flags->accept4() SOCK_CLOFORK|SOCK_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_accept4_both +}, { + .clo_desc = "socket(2), O_CLOEXEC->accept4() SOCK_CLOFORK|SOCK_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_accept4_both +}, { + .clo_desc = "socket(2), O_CLOFORK->accept4() SOCK_CLOFORK|SOCK_CLOEXEC", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_accept4_both +}, { + .clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept4() " + "SOCK_CLOFORK|SOCK_CLOEXEC", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_accept4_both +}, { + .clo_desc = "socket(2), no flags->accept4() SOCK_CLOFORK", + .clo_flags = 0, + .clo_func = oclo_accept4_fork +}, { + .clo_desc = "socket(2), O_CLOEXEC->accept4() SOCK_CLOFORK", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_accept4_fork +}, { + .clo_desc = "socket(2), O_CLOFORK->accept4() SOCK_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_accept4_fork +}, { + .clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept4() SOCK_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_accept4_fork +}, { + .clo_desc = "socket(2), no flags->accept4() SOCK_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_accept4_exec +}, { + .clo_desc = "socket(2), O_CLOEXEC->accept4() SOCK_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_accept4_exec +}, { + .clo_desc = "socket(2), O_CLOFORK->accept4() SOCK_CLOEXEC", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_accept4_exec +}, { + .clo_desc = "socket(2), O_CLOEXEC|O_CLOFORK->accept4() SOCK_CLOEXEC", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_accept4_exec +}, { + .clo_desc = "SCM_RIGHTS none->none", + .clo_flags = 0, + .clo_func = oclo_rights_none +}, { + .clo_desc = "SCM_RIGHTS FD_CLOFORK->none", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_rights_none +}, { + .clo_desc = "SCM_RIGHTS FD_CLOEXEC->none", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_rights_none +}, { + .clo_desc = "SCM_RIGHTS FD_CLOEXEC|FD_CLOFORK->none", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_rights_none +}, { + .clo_desc = "SCM_RIGHTS none->MSG_CMSG_CLOEXEC", + .clo_flags = 0, + .clo_func = oclo_rights_exec +}, { + .clo_desc = "SCM_RIGHTS FD_CLOFORK->MSG_CMSG_CLOEXEC", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_rights_exec +}, { + .clo_desc = "SCM_RIGHTS FD_CLOEXEC->MSG_CMSG_CLOEXEC", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_rights_exec +}, { + .clo_desc = "SCM_RIGHTS FD_CLOEXEC|FD_CLOFORK->MSG_CMSG_CLOEXEC", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_rights_exec +}, { + .clo_desc = "SCM_RIGHTS MSG_CMSG_CLOFORK->nMSG_CMSG_CLOFORK", + .clo_flags = 0, + .clo_func = oclo_rights_fork +}, { + .clo_desc = "SCM_RIGHTS FD_CLOFORK->MSG_CMSG_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_rights_fork +}, { + .clo_desc = "SCM_RIGHTS FD_CLOEXEC->MSG_CMSG_CLOFORK", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_rights_fork +}, { + .clo_desc = "SCM_RIGHTS FD_CLOEXEC|FD_CLOFORK->MSG_CMSG_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_rights_fork +}, { + .clo_desc = "SCM_RIGHTS none->MSG_CMSG_CLOEXEC|MSG_CMSG_CLOFORK", + .clo_flags = 0, + .clo_func = oclo_rights_both +}, { + .clo_desc = "SCM_RIGHTS FD_CLOFORK->MSG_CMSG_CLOEXEC|MSG_CMSG_CLOFORK", + .clo_flags = FD_CLOFORK, + .clo_func = oclo_rights_both +}, { + .clo_desc = "SCM_RIGHTS FD_CLOEXEC->MSG_CMSG_CLOEXEC|MSG_CMSG_CLOFORK", + .clo_flags = FD_CLOEXEC, + .clo_func = oclo_rights_both +}, { + .clo_desc = "SCM_RIGHTS FD_CLOEXEC|FD_CLOFORK->" + "MSG_CMSG_CLOEXEC|MSG_CMSG_CLOFORK", + .clo_flags = FD_CLOEXEC | FD_CLOFORK, + .clo_func = oclo_rights_both +} }; + +static bool +oclo_verify_fork(void) +{ + bool ret = true; + + for (size_t i = 0; i < oclo_rtdata_next; i++) { + if (!oclo_flags_match(&oclo_rtdata[i], true)) { + ret = false; + } + } + + return (ret); +} + +/* + * Here we proceed to re-open any fd that was closed due to O_CLOFORK again to + * make sure it makes it to our child verifier. This also serves as a test to + * make sure that our opening of the lowest fd is correct. While this doesn't + * actually use the same method as was done previously, While it might be ideal + * to use the method as originally, this should get us most of the way there. + */ +static void +oclo_child_reopen(void) +{ + for (size_t i = 0; i < oclo_rtdata_next; i++) { + int fd; + int flags = O_RDWR | O_CLOFORK; + + if ((oclo_rtdata[i].crt_flags & FD_CLOFORK) == 0) + continue; + + if ((oclo_rtdata[i].crt_flags & FD_CLOEXEC) != 0) + flags |= O_CLOEXEC; + + fd = open("/dev/zero", flags); + if (fd < 0) { + err(EXIT_FAILURE, "TEST FAILED: failed to re-open fd " + "%d with flags %d", oclo_rtdata[i].crt_fd, flags); + } + + if (fd != oclo_rtdata[i].crt_fd) { + errx(EXIT_FAILURE, "TEST FAILED: re-opening fd %d " + "returned fd %d: test design issue or lowest fd " + "algorithm is broken", oclo_rtdata[i].crt_fd, fd); + } + } + + (void) printf("TEST PASSED: successfully reopened fds post-fork"); +} + +/* + * Look for the verification program in the same directory that this program is + * found in. Note, that isn't the same thing as the current working directory. + */ +static void +oclo_exec(void) +{ + ssize_t ret; + char dir[PATH_MAX], file[PATH_MAX]; + char **argv; + + ret = getpathname(getpid(), dir, sizeof(dir)); + if (ret < 0) + err(EXIT_FAILURE, "TEST FAILED: failed to read executable path"); + + if (snprintf(file, sizeof (file), "%s/%s", dirname(dir), OCLO_VERIFY) >= + (int)sizeof (file)) { + errx(EXIT_FAILURE, "TEST FAILED: cannot assemble exec path " + "name: internal buffer overflow"); + } + + /* We need an extra for both the NULL terminator and the program name */ + argv = calloc(oclo_rtdata_next + 2, sizeof (char *)); + if (argv == NULL) { + err(EXIT_FAILURE, "TEST FAILED: failed to allocate exec " + "argument array"); + } + + argv[0] = file; + for (size_t i = 0; i < oclo_rtdata_next; i++) { + /* + * https://austingroupbugs.net/view.php?id=1851 + * FD_CLOFORK should not be preserved across exec + */ + int flags = oclo_rtdata[i].crt_flags & ~FD_CLOFORK; + if (asprintf(&argv[i + 1], "0x%x", flags) == -1) { + err(EXIT_FAILURE, "TEST FAILED: failed to assemble " + "exec argument %zu", i + 1); + } + } + + (void) execv(file, argv); + warn("TEST FAILED: failed to exec verifier %s", file); +} + +int +main(void) +{ + int ret = EXIT_SUCCESS; + siginfo_t cret; + + /* + * Before we do anything else close all FDs that aren't standard. We + * don't want anything the test suite environment may have left behind. + */ + (void) closefrom(STDERR_FILENO + 1); + + /* + * Treat failure during this set up phase as a hard failure. There's no + * reason to continue if we can't successfully create the FDs we expect. + */ + for (size_t i = 0; i < nitems(oclo_create); i++) { + oclo_create[i].clo_func(&oclo_create[i]); + } + + pid_t child = forkx(FORK_NOSIGCHLD | FORK_WAITPID); + if (child == 0) { + if (!oclo_verify_fork()) { + ret = EXIT_FAILURE; + } + + oclo_child_reopen(); + + oclo_exec(); + ret = EXIT_FAILURE; + _exit(ret); + } + + if (waitid(P_PID, child, &cret, WEXITED) < 0) { + err(EXIT_FAILURE, "TEST FAILED: internal test failure waiting " + "for forked child to report"); + } + + if (cret.si_code != CLD_EXITED) { + warnx("TEST FAILED: child process did not successfully exit: " + "found si_code: %d", cret.si_code); + ret = EXIT_FAILURE; + } else if (cret.si_status != 0) { + warnx("TEST FAILED: child process did not exit with code 0: " + "found %d", cret.si_status); + ret = EXIT_FAILURE; + } + + if (ret == EXIT_SUCCESS) { + (void) printf("All tests passed successfully\n"); + } + + return (ret); +} diff --git a/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo_errors.c b/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo_errors.c new file mode 100644 index 00000000000000..05b0c1a0839be8 --- /dev/null +++ b/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo_errors.c @@ -0,0 +1,202 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2024 Oxide Computer Company + */ + +/* + * Verify that unsupported flags will properly generate errors across the + * functions that we know perform strict error checking. This includes: + * + * o fcntl(..., F_DUP3FD, ...) + * o dup3() + * o pipe2() + * o socket() + * o accept4() + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define strerrorname_np(e) (sys_errlist[e]) + +static bool +oclo_check(const char *desc, const char *act, int ret, int e) +{ + if (ret >= 0) { + warnx("TEST FAILED: %s: fd was %s!", desc, act); + return (false); + } else if (errno != EINVAL) { + e = errno; + warnx("TEST FAILED: %s: failed with %s, expected " + "EINVAL", desc, strerrorname_np(e)); + return (false); + } + + (void) printf("TEST PASSED: %s: correctly failed with EINVAL\n", + desc); + return (true); +} + +static bool +oclo_dup3(const char *desc, int flags) +{ + int fd = dup3(STDERR_FILENO, 23, flags); + return (oclo_check(desc, "duplicated", fd, errno)); +} + +static bool +oclo_dup3fd(const char *desc, int flags) +{ + int fd = fcntl(STDERR_FILENO, F_DUP3FD | (flags << F_DUP3FD_SHIFT), 23); + return (oclo_check(desc, "duplicated", fd, errno)); +} + + +static bool +oclo_pipe2(const char *desc, int flags) +{ + int fds[2], ret; + + ret = pipe2(fds, flags); + return (oclo_check(desc, "piped", ret, errno)); +} + +#if 0 +static bool +oclo_socket(const char *desc, int type) +{ + int fd = socket(PF_UNIX, SOCK_STREAM | type, 0); + return (oclo_check(desc, "created", fd, errno)); +} +#endif + +static bool +oclo_accept(const char *desc, int flags) +{ + int sock, fd, e; + struct sockaddr_in in; + + sock = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (sock < 0) { + warn("TEST FAILED: %s: failed to create listen socket", desc); + return (false); + } + + (void) memset(&in, 0, sizeof (in)); + in.sin_family = AF_INET; + in.sin_port = 0; + in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(sock, (struct sockaddr *)&in, sizeof (in)) != 0) { + warn("TEST FAILED: %s: failed to bind socket", desc); + (void) close(sock); + return (false); + } + + if (listen(sock, 5) < 0) { + warn("TEST FAILED: %s: failed to listen on socket", desc); + (void) close(sock); + return (false); + } + + + fd = accept4(sock, NULL, NULL, flags); + e = errno; + (void) close(sock); + return (oclo_check(desc, "accepted", fd, e)); +} + +int +main(void) +{ + int ret = EXIT_SUCCESS; + + closefrom(STDERR_FILENO + 1); + + if (!oclo_dup3("dup3(): O_RDWR", O_RDWR)) { + ret = EXIT_FAILURE; + } + + if (!oclo_dup3("dup3(): O_NONBLOCK|O_CLOXEC", O_NONBLOCK | O_CLOEXEC)) { + ret = EXIT_FAILURE; + } + + if (!oclo_dup3("dup3(): O_CLOFORK|O_WRONLY", O_CLOFORK | O_WRONLY)) { + ret = EXIT_FAILURE; + } + + if (!oclo_dup3fd("fcntl(FDUP3FD): 0x7777", 0x7777)) { + ret = EXIT_FAILURE; + } + + if (!oclo_dup3fd("fcntl(FDUP3FD): FD_CLOEXEC|FD_CLOFORK + 1", + (FD_CLOEXEC | FD_CLOFORK) + 1)) { + ret = EXIT_FAILURE; + } + + if (!oclo_dup3fd("fcntl(FDUP3FD): INT_MAX", INT_MAX)) { + ret = EXIT_FAILURE; + } + + + if (!oclo_pipe2("pipe2(): O_RDWR", O_RDWR)) { + ret = EXIT_FAILURE; + } + + if (!oclo_pipe2("pipe2(): O_SYNC|O_CLOXEC", O_SYNC | O_CLOEXEC)) { + ret = EXIT_FAILURE; + } + + if (!oclo_pipe2("pipe2(): O_CLOFORK|O_WRONLY", O_CLOFORK | O_WRONLY)) { + ret = EXIT_FAILURE; + } + + if (!oclo_pipe2("pipe2(): INT32_MAX", INT32_MAX)) { + ret = EXIT_FAILURE; + } + +#if 0 /* These tests are known to fail on FreeBSD */ + if (!oclo_socket("socket(): INT32_MAX", INT32_MAX)) { + ret = EXIT_FAILURE; + } + + if (!oclo_socket("socket(): 3 << 25", 3 << 25)) { + ret = EXIT_FAILURE; + } +#endif + + if (!oclo_accept("accept4(): INT32_MAX", INT32_MAX)) { + ret = EXIT_FAILURE; + } + + if (!oclo_accept("accept4(): 3 << 25", 3 << 25)) { + ret = EXIT_FAILURE; + } + + if (ret == EXIT_SUCCESS) { + (void) printf("All tests completed successfully\n"); + } + + return (ret); +} diff --git a/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/ocloexec_verify.c b/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/ocloexec_verify.c new file mode 100644 index 00000000000000..1f8aa8f7936280 --- /dev/null +++ b/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/ocloexec_verify.c @@ -0,0 +1,139 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2024 Oxide Computer Company + */ + +/* + * Verify that our file descriptors starting after stderr are correct based upon + * the series of passed in arguments from the 'oclo' program. Arguments are + * passed as a string that represents the flags that were originally verified + * pre-fork/exec via fcntl(F_GETFD). In addition, anything that was originally + * closed because it had FD_CLOFORK set was reopened with the same flags so we + * can verify that things with only FD_CLOFORK survive exec. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define strerrorname_np(e) (sys_errlist[e]) + +static int +getmaxfd(void) +{ + struct kinfo_file *files; + int i, cnt, max; + + if ((files = kinfo_getfile(getpid(), &cnt)) == NULL) + err(1, "kinfo_getfile"); + + max = -1; + for (i = 0; i < cnt; i++) + if (files[i].kf_fd > max) + max = files[i].kf_fd; + + free(files); + return (max); +} + +static bool +verify_flags(int fd, int exp_flags) +{ + bool fail = (exp_flags & FD_CLOEXEC) != 0; + int flags = fcntl(fd, F_GETFD, NULL); + + if (flags < 0) { + int e = errno; + + if (fail) { + if (e == EBADF) { + (void) printf("TEST PASSED: post-exec fd %d: " + "flags 0x%x: correctly closed\n", fd, + exp_flags); + return (true); + } + + + warn("TEST FAILED: post-fork fd %d: expected fcntl to " + "fail with EBADF, but found %s", fd, + strerrorname_np(e)); + return (false); + } + + warnx("TEST FAILED: post-fork fd %d: fcntl(F_GETFD) " + "unexpectedly failed with %s, expected flags %d", fd, + strerrorname_np(e), exp_flags); + return (false); + } + + if (fail) { + warnx("TEST FAILED: post-fork fd %d: received flags %d, but " + "expected to fail based on flags %d", fd, flags, exp_flags); + return (false); + } + + if (flags != exp_flags) { + warnx("TEST FAILED: post-exec fd %d: discovered flags 0x%x do " + "not match expected flags 0x%x", fd, flags, exp_flags); + return (false); + } + + (void) printf("TEST PASSED: post-exec fd %d: flags 0x%x: successfully " + "matched\n", fd, exp_flags); + return (true); +} + +int +main(int argc, char *argv[]) +{ + int maxfd; + int ret = EXIT_SUCCESS; + + /* + * We should have one argument for each fd we found, ignoring stdin, + * stdout, and stderr. argc will also have an additional entry for our + * program name, which we want to skip. Note, the last fd may not exist + * because it was marked for close, hence the use of '>' below. + */ + maxfd = getmaxfd(); + if (maxfd - 3 > argc - 1) { + errx(EXIT_FAILURE, "TEST FAILED: found more fds %d than " + "arguments %d", maxfd - 3, argc - 1); + } + + for (int i = 1; i < argc; i++) { + char *endptr; + int targ_fd = i + STDERR_FILENO; + errno = 0; + long long val = strtoll(argv[i], &endptr, 0); + + if (errno != 0 || *endptr != '\0' || + (val < 0 || val > (FD_CLOEXEC | FD_CLOFORK))) { + errx(EXIT_FAILURE, "TEST FAILED: failed to parse " + "argument %d: %s", i, argv[i]); + } + + if (!verify_flags(targ_fd, (int)val)) + ret = EXIT_FAILURE; + } + + return (ret); +} diff --git a/lib/libc/gen/dup3.3 b/lib/libc/gen/dup3.3 index f2798930797b0d..338a9ae74c6496 100644 --- a/lib/libc/gen/dup3.3 +++ b/lib/libc/gen/dup3.3 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd August 16, 2013 +.Dd May 17, 2025 .Dt DUP3 3 .Os .Sh NAME @@ -47,6 +47,11 @@ The close-on-exec flag on the new file descriptor is determined by the bit in .Fa flags . .Pp +The close-on-fork flag on the new file descriptor is determined by the +.Dv O_CLOFORK +bit in +.Fa flags . +.Pp If .Fa oldd \*(Ne @@ -91,7 +96,9 @@ argument. The .Fa flags argument has bits set other than -.Dv O_CLOEXEC . +.Dv O_CLOEXEC +or +.Dv O_CLOFORK . .El .Sh SEE ALSO .Xr accept 2 , @@ -112,3 +119,7 @@ The .Fn dup3 function appeared in .Fx 10.0 . +The +.Dv O_CLOFORK +flag appeared in +.Fx 15.0 . diff --git a/lib/libc/gen/dup3.c b/lib/libc/gen/dup3.c index fca1e99fb47b80..1401c1f5b60721 100644 --- a/lib/libc/gen/dup3.c +++ b/lib/libc/gen/dup3.c @@ -39,21 +39,22 @@ int __dup3(int, int, int); int __dup3(int oldfd, int newfd, int flags) { - int how; + int fdflags; if (oldfd == newfd) { errno = EINVAL; return (-1); } - if (flags & ~O_CLOEXEC) { + if ((flags & ~(O_CLOEXEC | O_CLOFORK)) != 0) { errno = EINVAL; return (-1); } - how = (flags & O_CLOEXEC) ? F_DUP2FD_CLOEXEC : F_DUP2FD; + fdflags = ((flags & O_CLOEXEC) != 0 ? FD_CLOEXEC : 0) | + ((flags & O_CLOFORK) != 0 ? FD_CLOFORK : 0); - return (_fcntl(oldfd, how, newfd)); + return (_fcntl(oldfd, F_DUP3FD | (fdflags << F_DUP3FD_SHIFT), newfd)); } __weak_reference(__dup3, dup3); diff --git a/lib/libopenbsd/Makefile b/lib/libopenbsd/Makefile index 675ed476c51d2e..dca1c08b0aed4a 100644 --- a/lib/libopenbsd/Makefile +++ b/lib/libopenbsd/Makefile @@ -2,7 +2,8 @@ PACKAGE=lib${LIB} LIB= openbsd SRCS= imsg-buffer.c \ imsg.c \ - ohash.c + ohash.c \ + recallocarray.c .if !defined(BOOTSTRAPPING) # Skip getdtablecount.c when bootstrapping since it doesn't compile for Linux # and is not used by any of the bootstrap tools diff --git a/lib/libopenbsd/recallocarray.c b/lib/libopenbsd/recallocarray.c new file mode 100644 index 00000000000000..11e1fda744c709 --- /dev/null +++ b/lib/libopenbsd/recallocarray.c @@ -0,0 +1,82 @@ +/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */ +/* + * Copyright (c) 2008, 2017 Otto Moerbeek + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void *recallocarray(void *, size_t, size_t, size_t); + +void * +recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) +{ + size_t oldsize, newsize; + void *newptr; + + if (ptr == NULL) + return calloc(newnmemb, size); + + if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + newnmemb > 0 && SIZE_MAX / newnmemb < size) { + errno = ENOMEM; + return NULL; + } + newsize = newnmemb * size; + + if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { + errno = EINVAL; + return NULL; + } + oldsize = oldnmemb * size; + + /* + * Don't bother too much if we're shrinking just a bit, + * we do not shrink for series of small steps, oh well. + */ + if (newsize <= oldsize) { + size_t d = oldsize - newsize; + + if (d < oldsize / 2 && d < (size_t)getpagesize()) { + memset((char *)ptr + newsize, 0, d); + return ptr; + } + } + + newptr = malloc(newsize); + if (newptr == NULL) + return NULL; + + if (newsize > oldsize) { + memcpy(newptr, ptr, oldsize); + memset((char *)newptr + oldsize, 0, newsize - oldsize); + } else + memcpy(newptr, ptr, newsize); + + explicit_bzero(ptr, oldsize); + free(ptr); + + return newptr; +} diff --git a/lib/libsys/accept.2 b/lib/libsys/accept.2 index 53926b3153d22b..2da2af066a5b1d 100644 --- a/lib/libsys/accept.2 +++ b/lib/libsys/accept.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd October 9, 2014 +.Dd May 17, 2025 .Dt ACCEPT 2 .Os .Sh NAME @@ -85,6 +85,13 @@ and the close-on-exec flag on the new file descriptor can be set via the flag in the .Fa flags argument. +Similarly, the +.Dv O_CLOFORK +property can be set via the +.Dv SOCK_CLOFORK +flag in the +.Fa flags +argument. .Pp If no pending connections are present on the queue, and the original socket @@ -234,3 +241,8 @@ The .Fn accept4 system call appeared in .Fx 10.0 . +.Pp +The +.Dv SOCK_CLOFORK +flag appeared in +.Fx 15.0 . diff --git a/lib/libsys/closefrom.2 b/lib/libsys/closefrom.2 index aaa4c55607ac96..ac74c9e9cc0b72 100644 --- a/lib/libsys/closefrom.2 +++ b/lib/libsys/closefrom.2 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd March 3, 2022 +.Dd May 17, 2025 .Dt CLOSEFROM 2 .Os .Sh NAME @@ -60,6 +60,10 @@ Supported .It Dv CLOSE_RANGE_CLOEXEC Set the close-on-exec flag on descriptors in the range instead of closing them. .El +.Bl -tag -width ".Dv CLOSE_RANGE_CLOFORK" +.It Dv CLOSE_RANGE_CLOFORK +Set the close-on-fork flag on descriptors in the range instead of closing them. +.El .Sh RETURN VALUES Upon successful completion, .Fn close_range @@ -90,3 +94,8 @@ The .Fn closefrom function first appeared in .Fx 8.0 . +.Pp +The +.Dv CLOSE_RANGE_CLOFORK +flag appeared in +.Fx 15.0 . diff --git a/lib/libsys/fcntl.2 b/lib/libsys/fcntl.2 index b5d4abe35aeb96..1e5da6957e858c 100644 --- a/lib/libsys/fcntl.2 +++ b/lib/libsys/fcntl.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd December 7, 2021 +.Dd May 17, 2025 .Dt FCNTL 2 .Os .Sh NAME @@ -80,6 +80,13 @@ associated with the new file descriptor is cleared, so the file descriptor is to remain open across .Xr execve 2 system calls. +.It +The fork-on-exec flag +.Dv FD_CLOFORK +associated with the new file descriptor is cleared, so the file descriptor is +to remain open across +.Xr fork 2 +system calls. .El .It Dv F_DUPFD_CLOEXEC Like @@ -90,6 +97,15 @@ flag associated with the new file descriptor is set, so the file descriptor is closed when .Xr execve 2 system call executes. +.It Dv F_DUPFD_CLOFORK +Like +.Dv F_DUPFD , +but the +.Dv FD_CLOFORK +flag associated with the new file descriptor is set, so the file descriptor +is closed when +.Xr fork 2 +system call executes. .It Dv F_DUP2FD It is functionally equivalent to .Bd -literal -offset indent @@ -112,11 +128,13 @@ Use .Fn dup2 instead of .Dv F_DUP2FD . +.It Dv F_DUP3FD +Used to implement the +.Fn dup3 +call. Do not use it. .It Dv F_GETFD -Get the close-on-exec flag associated with the file descriptor +Get the close-on-exec and close-on-fork flags associated with the file descriptor .Fa fd -as -.Dv FD_CLOEXEC . If the returned value ANDed with .Dv FD_CLOEXEC is 0, @@ -125,16 +143,19 @@ the file will remain open across otherwise the file will be closed upon execution of .Fn exec .Fa ( arg -is ignored). +is ignored). The same happens with +.Dv FD_CLOFORK . .It Dv F_SETFD -Set the close-on-exec flag associated with +Set the close-on-exec and close-on-fork flags associated with .Fa fd to .Fa arg , where .Fa arg -is either 0 or -.Dv FD_CLOEXEC , +is either 0, +.Dv FD_CLOEXEC +or +.Dv FD_CLOFORK as described above. .It Dv F_GETFL Get descriptor status flags, as described below @@ -762,8 +783,10 @@ for the reasons as stated in .Sh STANDARDS The .Dv F_DUP2FD -constant is non portable. -It is provided for compatibility with AIX and Solaris. +and +.Dv F_DUP3FD +constants are not portable. +They are provided for compatibility with AIX and Solaris. .Pp Per .St -susv4 , @@ -788,3 +811,10 @@ The .Dv F_DUP2FD constant first appeared in .Fx 7.1 . +.Pp +The +.Dv F_DUPFD_CLOFORK +and +.Dv F_DUP3FD +flags appeared in +.Fx 15.0 . diff --git a/lib/libsys/fork.2 b/lib/libsys/fork.2 index 7d548a42890d81..1db2a6115e8fab 100644 --- a/lib/libsys/fork.2 +++ b/lib/libsys/fork.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd August 5, 2021 +.Dd May 17, 2024 .Dt FORK 2 .Os .Sh NAME @@ -68,6 +68,15 @@ by the parent. This descriptor copying is also used by the shell to establish standard input and output for newly created processes as well as to set up pipes. +Any file descriptors that were marked with the close-on-fork flag, +.Dv FD_CLOFORK +(see +.Fn fcntl 2 +and +.Dv O_CLOFORK +in +.Fn open 2 ) +, will not be present in the child process, but remain open in the parent. .It The child process' resource utilizations are set to 0; see diff --git a/lib/libsys/open.2 b/lib/libsys/open.2 index 84c4f02fce8a21..9f24ccc1480d11 100644 --- a/lib/libsys/open.2 +++ b/lib/libsys/open.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd April 3, 2025 +.Dd May 17, 2025 .Dt OPEN 2 .Os .Sh NAME @@ -195,6 +195,18 @@ error if file is not a directory .It Dv O_CLOEXEC automatically close file on .Xr execve 2 +.It Dv O_CLOFORK +If set, the file descriptor returned will be closed in any child processses created with the +.Fn fork 2 +family of functions. +The file descriptor will remain open in the parent. +This sets the +.Dv FD_CLOFORK +flag on the file descriptor. +If not performed at open time, this can later be set with the +.Dv F_SETFD +.Fn fcntl 2 +command. .It Dv O_VERIFY verify the contents of the file with .Xr mac_veriexec 4 @@ -846,6 +858,9 @@ function was introduced in appeared in 13.0. .Dv O_NAMEDATTR appeared in 15.0. +.Dv O_CLOFORK +appeared in +.Fx 15.0 . .Sh BUGS The .Fa mode diff --git a/lib/libsys/pipe.2 b/lib/libsys/pipe.2 index 9531c971739504..37d6eba420de7d 100644 --- a/lib/libsys/pipe.2 +++ b/lib/libsys/pipe.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd December 1, 2017 +.Dd May 17, 2025 .Dt PIPE 2 .Os .Sh NAME @@ -64,6 +64,8 @@ list, defined in .Bl -tag -width ".Dv O_NONBLOCK" .It Dv O_CLOEXEC Set the close-on-exec flag for the new file descriptors. +.It Dv O_CLOFORK +Set the close-on-fork flag for the new file descriptors. .It Dv O_NONBLOCK Set the non-blocking flag for the ends of the pipe. .El @@ -173,3 +175,8 @@ function became a wrapper around .Fn pipe2 in .Fx 11.0 . +.Pp +The +.Dv O_CLOFORK +flag appeared in +.Fx 15.0 . diff --git a/lib/libsys/recv.2 b/lib/libsys/recv.2 index f3ee60b7566380..b78cd70b8a1da9 100644 --- a/lib/libsys/recv.2 +++ b/lib/libsys/recv.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 30, 2022 +.Dd May 17, 2025 .Dt RECV 2 .Os .Sh NAME @@ -164,6 +164,7 @@ one or more of the values: .It Dv MSG_WAITALL Ta wait for full request or error .It Dv MSG_DONTWAIT Ta do not block .It Dv MSG_CMSG_CLOEXEC Ta set received fds close-on-exec +.It Dv MSG_CMSG_CLOFORK Ta set received fds close-on-fork .It Dv MSG_WAITFORONE Ta do not block after receiving the first message (only for .Fn recvmmsg diff --git a/lib/libsys/socket.2 b/lib/libsys/socket.2 index a383cbcc4d8000..b211611c6354aa 100644 --- a/lib/libsys/socket.2 +++ b/lib/libsys/socket.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd January 15, 2023 +.Dd May 17, 2025 .Dt SOCKET 2 .Os .Sh NAME @@ -121,6 +121,7 @@ argument: .Pp .Bd -literal -offset indent -compact SOCK_CLOEXEC Set close-on-exec on the new descriptor, +SOCK_CLOFORK Set close-on-fork on the new descriptor, SOCK_NONBLOCK Set non-blocking mode on the new socket .Ed .Pp @@ -331,7 +332,10 @@ argument of .Fn socket . The .Dv SOCK_CLOEXEC -flag is expected to conform to the next revision of the +and +.Dv SOCK_CLOFORK +flags are expected to conform to +.St -p1003.1-2024 . .Tn POSIX standard. The @@ -347,3 +351,8 @@ The .Fn socket system call appeared in .Bx 4.2 . +.Pp +The +.Dv SOCK_CLOFORK +flag appeared in +.Fx 15.0 . diff --git a/lib/libsys/socketpair.2 b/lib/libsys/socketpair.2 index 5874a0791f4ddd..60dec74f9cc2ba 100644 --- a/lib/libsys/socketpair.2 +++ b/lib/libsys/socketpair.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 10, 2018 +.Dd May 17, 2025 .Dt SOCKETPAIR 2 .Os .Sh NAME @@ -56,7 +56,8 @@ and The two sockets are indistinguishable. .Pp The -.Dv SOCK_CLOEXEC +.Dv SOCK_CLOEXEC , +.Dv SOCK_CLOFORK and .Dv SOCK_NONBLOCK flags in the diff --git a/lib/libsysdecode/flags.c b/lib/libsysdecode/flags.c index 32829d35dbe001..0d1cc2569e4d55 100644 --- a/lib/libsysdecode/flags.c +++ b/lib/libsysdecode/flags.c @@ -196,7 +196,7 @@ sysdecode_vmprot(FILE *fp, int type, int *rem) } static struct name_table sockflags[] = { - X(SOCK_CLOEXEC) X(SOCK_NONBLOCK) XEND + X(SOCK_CLOEXEC) X(SOCK_CLOFORK) X(SOCK_NONBLOCK) XEND }; bool @@ -206,16 +206,17 @@ sysdecode_socket_type(FILE *fp, int type, int *rem) uintmax_t val; bool printed; - str = lookup_value(socktype, type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)); + str = lookup_value(socktype, + type & ~(SOCK_CLOEXEC | SOCK_CLOFORK | SOCK_NONBLOCK)); if (str != NULL) { fputs(str, fp); *rem = 0; printed = true; } else { - *rem = type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK); + *rem = type & ~(SOCK_CLOEXEC | SOCK_CLOFORK | SOCK_NONBLOCK); printed = false; } - val = type & (SOCK_CLOEXEC | SOCK_NONBLOCK); + val = type & (SOCK_CLOEXEC | SOCK_CLOFORK | SOCK_NONBLOCK); print_mask_part(fp, sockflags, &val, &printed); return (printed); } @@ -556,7 +557,7 @@ sysdecode_nfssvc_flags(int flags) } static struct name_table pipe2flags[] = { - X(O_CLOEXEC) X(O_NONBLOCK) XEND + X(O_CLOEXEC) X(O_CLOFORK) X(O_NONBLOCK) XEND }; bool @@ -866,7 +867,7 @@ sysdecode_fcntl_cmd(int cmd) } static struct name_table fcntl_fd_arg[] = { - X(FD_CLOEXEC) X(0) XEND + X(FD_CLOEXEC) X(FD_CLOFORK) X(0) XEND }; bool diff --git a/lib/libsysdecode/sysdecode_fcntl_arg.3 b/lib/libsysdecode/sysdecode_fcntl_arg.3 index ee3a030a79e442..d5648ce0adc392 100644 --- a/lib/libsysdecode/sysdecode_fcntl_arg.3 +++ b/lib/libsysdecode/sysdecode_fcntl_arg.3 @@ -54,7 +54,8 @@ are determined by .It Sy Command Ta Fa arg Sy Type Ta Sy Output Format .It .It Dv F_SETFD Ta Vt int Ta -.Dq FD_CLOEXEC +.Dq FD_CLOEXEC , +.Dq FD_CLOFORK or the value of .Fa arg in the indicated diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c index e70f2d24836573..e3e9714e7cd2d9 100644 --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -492,6 +492,7 @@ kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg) int error, flg, kif_sz, seals, tmp, got_set, got_cleared; uint64_t bsize; off_t foffset; + int flags; error = 0; flg = F_POSIX; @@ -511,6 +512,11 @@ kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg) error = kern_dup(td, FDDUP_FCNTL, FDDUP_FLAG_CLOEXEC, fd, tmp); break; + case F_DUPFD_CLOFORK: + tmp = arg; + error = kern_dup(td, FDDUP_FCNTL, FDDUP_FLAG_CLOFORK, fd, tmp); + break; + case F_DUP2FD: tmp = arg; error = kern_dup(td, FDDUP_FIXED, 0, fd, tmp); @@ -527,7 +533,8 @@ kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg) fde = fdeget_noref(fdp, fd); if (fde != NULL) { td->td_retval[0] = - (fde->fde_flags & UF_EXCLOSE) ? FD_CLOEXEC : 0; + ((fde->fde_flags & UF_EXCLOSE) != 0 ? FD_CLOEXEC : 0) | + ((fde->fde_flags & UF_FOCLOSE) != 0 ? FD_CLOFORK : 0); error = 0; } FILEDESC_SUNLOCK(fdp); @@ -538,8 +545,10 @@ kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg) FILEDESC_XLOCK(fdp); fde = fdeget_noref(fdp, fd); if (fde != NULL) { - fde->fde_flags = (fde->fde_flags & ~UF_EXCLOSE) | - (arg & FD_CLOEXEC ? UF_EXCLOSE : 0); + fde->fde_flags = + (fde->fde_flags & ~(UF_EXCLOSE | UF_FOCLOSE)) | + ((arg & FD_CLOEXEC) != 0 ? UF_EXCLOSE : 0) | + ((arg & FD_CLOFORK) != 0 ? UF_FOCLOSE : 0); error = 0; } FILEDESC_XUNLOCK(fdp); @@ -909,7 +918,20 @@ kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg) break; default: - error = EINVAL; + if ((cmd & ((1u << F_DUP3FD_SHIFT) - 1)) != F_DUP3FD) { + error = EINVAL; + break; + } + /* Handle F_DUP3FD */ + flags = (cmd >> F_DUP3FD_SHIFT); + if ((flags & ~(FD_CLOEXEC | FD_CLOFORK)) != 0) { + error = EINVAL; + break; + } + tmp = arg; + error = kern_dup(td, FDDUP_FIXED, + ((flags & FD_CLOEXEC) != 0 ? FDDUP_FLAG_CLOEXEC : 0) | + ((flags & FD_CLOFORK) != 0 ? FDDUP_FLAG_CLOFORK : 0), fd, tmp); break; } return (error); @@ -939,7 +961,7 @@ kern_dup(struct thread *td, u_int mode, int flags, int old, int new) fdp = p->p_fd; oioctls = NULL; - MPASS((flags & ~(FDDUP_FLAG_CLOEXEC)) == 0); + MPASS((flags & ~(FDDUP_FLAG_CLOEXEC | FDDUP_FLAG_CLOFORK)) == 0); MPASS(mode < FDDUP_LASTMODE); AUDIT_ARG_FD(old); @@ -964,8 +986,10 @@ kern_dup(struct thread *td, u_int mode, int flags, int old, int new) goto unlock; if (mode == FDDUP_FIXED && old == new) { td->td_retval[0] = new; - if (flags & FDDUP_FLAG_CLOEXEC) + if ((flags & FDDUP_FLAG_CLOEXEC) != 0) fdp->fd_ofiles[new].fde_flags |= UF_EXCLOSE; + if ((flags & FDDUP_FLAG_CLOFORK) != 0) + fdp->fd_ofiles[new].fde_flags |= UF_FOCLOSE; error = 0; goto unlock; } @@ -1040,10 +1064,9 @@ kern_dup(struct thread *td, u_int mode, int flags, int old, int new) fde_copy(oldfde, newfde); filecaps_copy_finish(&oldfde->fde_caps, &newfde->fde_caps, nioctls); - if ((flags & FDDUP_FLAG_CLOEXEC) != 0) - newfde->fde_flags = oldfde->fde_flags | UF_EXCLOSE; - else - newfde->fde_flags = oldfde->fde_flags & ~UF_EXCLOSE; + newfde->fde_flags = (oldfde->fde_flags & ~(UF_EXCLOSE | UF_FOCLOSE)) | + ((flags & FDDUP_FLAG_CLOEXEC) != 0 ? UF_EXCLOSE : 0) | + ((flags & FDDUP_FLAG_CLOFORK) != 0 ? UF_FOCLOSE : 0); #ifdef CAPABILITIES seqc_write_end(&newfde->fde_seqc); #endif @@ -1409,13 +1432,15 @@ kern_close(struct thread *td, int fd) } static int -close_range_cloexec(struct thread *td, u_int lowfd, u_int highfd) +close_range_flags(struct thread *td, u_int lowfd, u_int highfd, int flags) { struct filedesc *fdp; struct fdescenttbl *fdt; struct filedescent *fde; - int fd; + int fd, fde_flags; + fde_flags = ((flags & CLOSE_RANGE_CLOEXEC) != 0 ? UF_EXCLOSE : 0) | + ((flags & CLOSE_RANGE_CLOFORK) != 0 ? UF_FOCLOSE : 0); fdp = td->td_proc->p_fd; FILEDESC_XLOCK(fdp); fdt = atomic_load_ptr(&fdp->fd_files); @@ -1427,7 +1452,7 @@ close_range_cloexec(struct thread *td, u_int lowfd, u_int highfd) for (; fd <= highfd; fd++) { fde = &fdt->fdt_ofiles[fd]; if (fde->fde_file != NULL) - fde->fde_flags |= UF_EXCLOSE; + fde->fde_flags |= fde_flags; } out_locked: FILEDESC_XUNLOCK(fdp); @@ -1485,8 +1510,8 @@ kern_close_range(struct thread *td, int flags, u_int lowfd, u_int highfd) return (EINVAL); } - if ((flags & CLOSE_RANGE_CLOEXEC) != 0) - return (close_range_cloexec(td, lowfd, highfd)); + if ((flags & (CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_CLOFORK)) != 0) + return (close_range_flags(td, lowfd, highfd, flags)); return (close_range_impl(td, lowfd, highfd)); } @@ -1506,7 +1531,7 @@ sys_close_range(struct thread *td, struct close_range_args *uap) AUDIT_ARG_CMD(uap->highfd); AUDIT_ARG_FFLAGS(uap->flags); - if ((uap->flags & ~(CLOSE_RANGE_CLOEXEC)) != 0) + if ((uap->flags & ~(CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_CLOFORK)) != 0) return (EINVAL); return (kern_close_range(td, uap->flags, uap->lowfd, uap->highfd)); } @@ -2164,7 +2189,8 @@ _finstall(struct filedesc *fdp, struct file *fp, int fd, int flags, seqc_write_begin(&fde->fde_seqc); #endif fde->fde_file = fp; - fde->fde_flags = (flags & O_CLOEXEC) != 0 ? UF_EXCLOSE : 0; + fde->fde_flags = ((flags & O_CLOEXEC) != 0 ? UF_EXCLOSE : 0) | + ((flags & O_CLOFORK) != 0 ? UF_FOCLOSE : 0); if (fcaps != NULL) filecaps_move(fcaps, &fde->fde_caps); else @@ -2424,6 +2450,7 @@ fdcopy(struct filedesc *fdp) newfdp->fd_freefile = fdp->fd_freefile; FILEDESC_FOREACH_FDE(fdp, i, ofde) { if ((ofde->fde_file->f_ops->fo_flags & DFLAG_PASSABLE) == 0 || + (ofde->fde_flags & UF_FOCLOSE) != 0 || !fhold(ofde->fde_file)) { if (newfdp->fd_freefile == fdp->fd_freefile) newfdp->fd_freefile = i; @@ -2721,6 +2748,12 @@ fdcloseexec(struct thread *td) fdfree(fdp, i); (void) closefp(fdp, i, fp, td, false, false); FILEDESC_UNLOCK_ASSERT(fdp); + } else if (fde->fde_flags & UF_FOCLOSE) { + /* + * https://austingroupbugs.net/view.php?id=1851 + * FD_CLOFORK should not be preserved across exec + */ + fde->fde_flags &= ~UF_FOCLOSE; } } } diff --git a/sys/kern/sys_pipe.c b/sys/kern/sys_pipe.c index 9340779918a2c5..ed651da96b14c9 100644 --- a/sys/kern/sys_pipe.c +++ b/sys/kern/sys_pipe.c @@ -548,7 +548,7 @@ sys_pipe2(struct thread *td, struct pipe2_args *uap) { int error, fildes[2]; - if (uap->flags & ~(O_CLOEXEC | O_NONBLOCK)) + if ((uap->flags & ~(O_CLOEXEC | O_CLOFORK | O_NONBLOCK)) != 0) return (EINVAL); error = kern_pipe(td, fildes, uap->flags, NULL, NULL); if (error) diff --git a/sys/kern/uipc_syscalls.c b/sys/kern/uipc_syscalls.c index 0bbf13936bf99f..93af049df2e21b 100644 --- a/sys/kern/uipc_syscalls.c +++ b/sys/kern/uipc_syscalls.c @@ -151,6 +151,10 @@ kern_socket(struct thread *td, int domain, int type, int protocol) type &= ~SOCK_CLOEXEC; oflag |= O_CLOEXEC; } + if ((type & SOCK_CLOFORK) != 0) { + type &= ~SOCK_CLOFORK; + oflag |= O_CLOFORK; + } if ((type & SOCK_NONBLOCK) != 0) { type &= ~SOCK_NONBLOCK; fflag |= FNONBLOCK; @@ -352,7 +356,8 @@ kern_accept4(struct thread *td, int s, struct sockaddr *sa, int flags, goto done; #endif error = falloc_caps(td, &nfp, &fd, - (flags & SOCK_CLOEXEC) ? O_CLOEXEC : 0, &fcaps); + ((flags & SOCK_CLOEXEC) != 0 ? O_CLOEXEC : 0) | + ((flags & SOCK_CLOFORK) != 0 ? O_CLOFORK : 0), &fcaps); if (error != 0) goto done; SOCK_LOCK(head); @@ -435,7 +440,7 @@ int sys_accept4(struct thread *td, struct accept4_args *uap) { - if (uap->flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) + if ((uap->flags & ~(SOCK_CLOEXEC | SOCK_CLOFORK | SOCK_NONBLOCK)) != 0) return (EINVAL); return (accept1(td, uap->s, uap->name, uap->anamelen, uap->flags)); @@ -557,6 +562,10 @@ kern_socketpair(struct thread *td, int domain, int type, int protocol, type &= ~SOCK_CLOEXEC; oflag |= O_CLOEXEC; } + if ((type & SOCK_CLOFORK) != 0) { + type &= ~SOCK_CLOFORK; + oflag |= O_CLOFORK; + } if ((type & SOCK_NONBLOCK) != 0) { type &= ~SOCK_NONBLOCK; fflag |= FNONBLOCK; diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index 95d857b2625d93..d5d7c1b5addd4a 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -3494,7 +3494,8 @@ unp_externalize(struct mbuf *control, struct mbuf **controlp, int flags) } for (i = 0; i < newfds; i++, fdp++) { _finstall(fdesc, fdep[i]->fde_file, *fdp, - (flags & MSG_CMSG_CLOEXEC) != 0 ? O_CLOEXEC : 0, + ((flags & MSG_CMSG_CLOEXEC) != 0 ? O_CLOEXEC : 0) | + ((flags & MSG_CMSG_CLOFORK) != 0 ? O_CLOFORK : 0), &fdep[i]->fde_caps); unp_externalize_fp(fdep[i]->fde_file); } diff --git a/sys/sys/fcntl.h b/sys/sys/fcntl.h index 08aa26fbce02be..b1cf522c075e72 100644 --- a/sys/sys/fcntl.h +++ b/sys/sys/fcntl.h @@ -143,6 +143,10 @@ typedef __pid_t pid_t; #define O_NAMEDATTR 0x04000000 /* NFSv4 named attributes */ #endif +#if __POSIX_VISIBLE >= 202405 +#define O_CLOFORK 0x08000000 +#endif + /* * XXX missing O_RSYNC. */ @@ -269,6 +273,16 @@ typedef __pid_t pid_t; #define F_GET_SEALS 20 #define F_ISUNIONSTACK 21 /* Kludge for libc, don't use it. */ #define F_KINFO 22 /* Return kinfo_file for this fd */ +#endif /* __BSD_VISIBLE */ + +#if __POSIX_VISIBLE >= 202405 +#define F_DUPFD_CLOFORK 23 /* Like F_DUPFD, but FD_CLOFORK is set */ +#endif + +#if __BSD_VISIBLE +#define F_DUP3FD 24 /* Used with dup3() */ + +#define F_DUP3FD_SHIFT 16 /* Shift used for F_DUP3FD */ /* Seals (F_ADD_SEALS, F_GET_SEALS). */ #define F_SEAL_SEAL 0x0001 /* Prevent adding sealings */ @@ -279,6 +293,9 @@ typedef __pid_t pid_t; /* file descriptor flags (F_GETFD, F_SETFD) */ #define FD_CLOEXEC 1 /* close-on-exec flag */ +#if __POSIX_VISIBLE >= 202405 +#define FD_CLOFORK 2 /* close-on-fork flag */ +#endif /* record locking flags (F_GETLK, F_SETLK, F_SETLKW) */ #define F_RDLCK 1 /* shared or read lock */ diff --git a/sys/sys/filedesc.h b/sys/sys/filedesc.h index 602d236ff85322..df1dcca8f9fe3e 100644 --- a/sys/sys/filedesc.h +++ b/sys/sys/filedesc.h @@ -148,6 +148,7 @@ struct filedesc_to_leader { * Per-process open flags. */ #define UF_EXCLOSE 0x01 /* auto-close on exec */ +#define UF_FOCLOSE 0x02 /* auto-close on fork */ #ifdef _KERNEL @@ -220,6 +221,7 @@ enum { /* Flags for kern_dup(). */ #define FDDUP_FLAG_CLOEXEC 0x1 /* Atomically set UF_EXCLOSE. */ +#define FDDUP_FLAG_CLOFORK 0x2 /* Atomically set UF_FOCLOSE. */ /* For backward compatibility. */ #define falloc(td, resultfp, resultfd, flags) \ diff --git a/sys/sys/socket.h b/sys/sys/socket.h index 5e7c554c34cf96..cdd4fa3b4b89a0 100644 --- a/sys/sys/socket.h +++ b/sys/sys/socket.h @@ -111,10 +111,11 @@ typedef __uintptr_t uintptr_t; */ #define SOCK_CLOEXEC 0x10000000 #define SOCK_NONBLOCK 0x20000000 +#define SOCK_CLOFORK 0x40000000 #ifdef _KERNEL /* * Flags for accept1(), kern_accept4() and solisten_dequeue, in addition - * to SOCK_CLOEXEC and SOCK_NONBLOCK. + * to SOCK_CLOEXEC, SOCK_CLOFORK and SOCK_NONBLOCK. */ #define ACCEPT4_INHERIT 0x1 #define ACCEPT4_COMPAT 0x2 @@ -478,6 +479,9 @@ struct msghdr { #define MSG_MORETOCOME 0x00100000 /* additional data pending */ #define MSG_TLSAPPDATA 0x00200000 /* do not soreceive() alert rec. (TLS) */ #endif +#if __BSD_VISIBLE +#define MSG_CMSG_CLOFORK 0x00400000 /* make received fds close-on-fork */ +#endif /* * Header for ancillary data objects in msg_control buffer. diff --git a/sys/sys/unistd.h b/sys/sys/unistd.h index f5caea2e3919d0..7f10b78f14fc06 100644 --- a/sys/sys/unistd.h +++ b/sys/sys/unistd.h @@ -210,6 +210,7 @@ * close_range() options. */ #define CLOSE_RANGE_CLOEXEC (1<<2) +#define CLOSE_RANGE_CLOFORK (1<<3) #endif /* __BSD_VISIBLE */ diff --git a/tests/Makefile b/tests/Makefile index e8dd7793f1697f..451d55498a261a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,3 +1,5 @@ +.include + PACKAGE= tests TESTSDIR= ${TESTSBASE} @@ -11,6 +13,9 @@ SUBDIR+= examples SUBDIR+= include SUBDIR+= sys SUBDIR+= atf_python +.if ${MK_CDDL} != "no" +SUBDIR+= oclo +.endif SUBDIR_PARALLEL= diff --git a/tests/oclo/Makefile b/tests/oclo/Makefile new file mode 100644 index 00000000000000..350c9f857c8531 --- /dev/null +++ b/tests/oclo/Makefile @@ -0,0 +1,11 @@ +.PATH: ${SRCTOP}/cddl/contrib/opensolaris/tests/os-tests/tests/oclo + +TESTSDIR= ${TESTSBASE}/cddl/oclo + +PLAIN_TESTS_C= oclo oclo_errors ocloexec_verify + +SRCS.oclo= oclo.c +LIBADD.oclo+= openbsd +LIBADD.ocloexec_verify+= util + +.include diff --git a/tests/sys/file/closefrom_test.c b/tests/sys/file/closefrom_test.c index e30c5eb3d59160..7dccf858c7728c 100644 --- a/tests/sys/file/closefrom_test.c +++ b/tests/sys/file/closefrom_test.c @@ -144,7 +144,7 @@ main(void) pid_t pid; int fd, flags, i, start; - printf("1..21\n"); + printf("1..22\n"); /* We'd better start up with fd's 0, 1, and 2 open. */ start = devnull(); @@ -356,5 +356,38 @@ main(void) fail_err("close_range"); ok("close_range(..., CLOSE_RANGE_CLOEXEC)"); + /* test CLOSE_RANGE_CLOFORK */ + for (i = 0; i < 8; i++) + (void)devnull(); + fd = highest_fd(); + start = fd - 8; + if (close_range(start + 1, start + 4, CLOSE_RANGE_CLOFORK) < 0) + fail_err("close_range(..., CLOSE_RANGE_CLOFORK)"); + flags = fcntl(start, F_GETFD); + if (flags < 0) + fail_err("fcntl(.., F_GETFD)"); + if ((flags & FD_CLOFORK) != 0) + fail("close_range", "CLOSE_RANGE_CLOFORK set close-on-exec " + "when it should not have on fd %d", start); + for (i = start + 1; i <= start + 4; i++) { + flags = fcntl(i, F_GETFD); + if (flags < 0) + fail_err("fcntl(.., F_GETFD)"); + if ((flags & FD_CLOFORK) == 0) + fail("close_range", "CLOSE_RANGE_CLOFORK did not set " + "close-on-exec on fd %d", i); + } + for (; i < start + 8; i++) { + flags = fcntl(i, F_GETFD); + if (flags < 0) + fail_err("fcntl(.., F_GETFD)"); + if ((flags & FD_CLOFORK) != 0) + fail("close_range", "CLOSE_RANGE_CLOFORK set close-on-exec " + "when it should not have on fd %d", i); + } + if (close_range(start, start + 8, 0) < 0) + fail_err("close_range"); + ok("close_range(..., CLOSE_RANGE_CLOFORK)"); + return (0); } diff --git a/tests/sys/file/dup_test.c b/tests/sys/file/dup_test.c index b024e72d0d1afe..455115eda8c8c3 100644 --- a/tests/sys/file/dup_test.c +++ b/tests/sys/file/dup_test.c @@ -46,6 +46,8 @@ * Test #31: check if dup3(0) fails if oldfd == newfd. * Test #32: check if dup3(O_CLOEXEC) to a fd > current maximum number of * open files limit work. + * Tests #33-43 : Same as #18-26, 30 & 32 with O_CLOFORK instead of O_CLOEXEC, + * except F_DUP2FD_CLOEXEC. */ #include @@ -82,7 +84,7 @@ main(int __unused argc, char __unused *argv[]) orgfd = getafile(); - printf("1..32\n"); + printf("1..43\n"); /* If dup(2) ever work? */ if ((fd1 = dup(orgfd)) < 0) @@ -380,5 +382,99 @@ main(int __unused argc, char __unused *argv[]) printf("ok %d - dup3(O_CLOEXEC) didn't bypass NOFILE limit\n", test); + /* Does fcntl(F_DUPFD_CLOFORK) work? */ + if ((fd2 = fcntl(fd1, F_DUPFD_CLOFORK, 10)) < 0) + err(1, "fcntl(F_DUPFD_CLOFORK)"); + if (fd2 < 10) + printf("not ok %d - fcntl(F_DUPFD_CLOFORK) returned wrong fd %d\n", + ++test, fd2); + else + printf("ok %d - fcntl(F_DUPFD_CLOFORK) works\n", ++test); + + /* Was close-on-fork cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) != FD_CLOFORK) + printf( + "not ok %d - fcntl(F_DUPFD_CLOFORK) didn't set close-on-fork\n", + test); + else + printf("ok %d - fcntl(F_DUPFD_CLOFORK) set close-on-fork\n", + test); + + /* Does dup3(O_CLOFORK) ever work? */ + if ((fd2 = dup3(fd1, fd1 + 1, O_CLOFORK)) < 0) + err(1, "dup3(O_CLOFORK)"); + printf("ok %d - dup3(O_CLOFORK) works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1 + 1) + printf( + "no ok %d - dup3(O_CLOFORK) didn't give us the right fd\n", + test); + else + printf("ok %d - dup3(O_CLOFORK) returned a correct fd\n", + test); + + /* Was close-on-fork set? */ + ++test; + if (fcntl(fd2, F_GETFD) != FD_CLOFORK) + printf( + "not ok %d - dup3(O_CLOFORK) didn't set close-on-fork\n", + test); + else + printf("ok %d - dup3(O_CLOFORK) set close-on-fork\n", + test); + + /* Does dup3(0) ever work? */ + if ((fd2 = dup3(fd1, fd1 + 1, 0)) < 0) + err(1, "dup3(0)"); + printf("ok %d - dup3(0) works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1 + 1) + printf( + "no ok %d - dup3(0) didn't give us the right fd\n", + test); + else + printf("ok %d - dup3(0) returned a correct fd\n", + test); + + /* Was close-on-fork cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) != 0) + printf( + "not ok %d - dup3(0) didn't clear close-on-fork\n", + test); + else + printf("ok %d - dup3(0) cleared close-on-fork\n", + test); + + /* dup3() does not allow duplicating to the same fd */ + ++test; + if (dup3(fd1, fd1, O_CLOFORK) != -1) + printf( + "not ok %d - dup3(fd1, fd1, O_CLOFORK) succeeded\n", test); + else + printf("ok %d - dup3(fd1, fd1, O_CLOFORK) failed\n", test); + + ++test; + if (dup3(fd1, fd1, 0) != -1) + printf( + "not ok %d - dup3(fd1, fd1, 0) succeeded\n", test); + else + printf("ok %d - dup3(fd1, fd1, 0) failed\n", test); + + ++test; + if (getrlimit(RLIMIT_NOFILE, &rlp) < 0) + err(1, "getrlimit"); + if ((fd2 = dup3(fd1, rlp.rlim_cur + 1, O_CLOFORK)) >= 0) + printf("not ok %d - dup3(O_CLOFORK) bypassed NOFILE limit\n", + test); + else + printf("ok %d - dup3(O_CLOFORK) didn't bypass NOFILE limit\n", + test); + return (0); } diff --git a/tests/sys/kern/unix_passfd_test.c b/tests/sys/kern/unix_passfd_test.c index 8c6a976a0e1260..a7aa6bff601477 100644 --- a/tests/sys/kern/unix_passfd_test.c +++ b/tests/sys/kern/unix_passfd_test.c @@ -375,6 +375,30 @@ ATF_TC_BODY(simple_send_fd_msg_cmsg_cloexec, tc) closesocketpair(fd); } +/* + * Like simple_send_fd but also sets MSG_CMSG_CLOFORK and checks that the + * received file descriptor has the FD_CLOFORK flag set. + */ +ATF_TC_WITHOUT_HEAD(simple_send_fd_msg_cmsg_clofork); +ATF_TC_BODY(simple_send_fd_msg_cmsg_clofork, tc) +{ + struct stat getfd_stat, putfd_stat; + int fd[2], getfd, putfd; + + domainsocketpair(fd); + tempfile(&putfd); + dofstat(putfd, &putfd_stat); + sendfd(fd[0], putfd); + recvfd(fd[1], &getfd, MSG_CMSG_CLOFORK); + dofstat(getfd, &getfd_stat); + samefile(&putfd_stat, &getfd_stat); + ATF_REQUIRE_EQ_MSG(fcntl(getfd, F_GETFD) & FD_CLOFORK, FD_CLOFORK, + "FD_CLOFORK not set on the received file descriptor"); + close(putfd); + close(getfd); + closesocketpair(fd); +} + /* * Same as simple_send_fd, only close the file reference after sending, so that * the only reference is the descriptor in the UNIX domain socket buffer. @@ -1037,6 +1061,7 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, simple_send_fd); ATF_TP_ADD_TC(tp, simple_send_fd_msg_cmsg_cloexec); + ATF_TP_ADD_TC(tp, simple_send_fd_msg_cmsg_clofork); ATF_TP_ADD_TC(tp, send_and_close); ATF_TP_ADD_TC(tp, send_and_cancel); ATF_TP_ADD_TC(tp, send_and_shutdown);