diff --git a/regress/sys/kern/oclo/Makefile b/regress/sys/kern/oclo/Makefile new file mode 100644 index 000000000000..64a7645c5c22 --- /dev/null +++ b/regress/sys/kern/oclo/Makefile @@ -0,0 +1,8 @@ +PROGS= oclo oclo_errors ocloexec_verify + +LDADD_ocloexec_verify= -lkvm +MAN= + +WARNINGS= yes + +.include diff --git a/regress/sys/kern/oclo/oclo.c b/regress/sys/kern/oclo/oclo.c new file mode 100644 index 000000000000..6f12b866685c --- /dev/null +++ b/regress/sys/kern/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 + +#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 POSIX fork() + */ +static pid_t +forkx(int flags) +{ + struct sigaction oldact, ign; + pid_t pid; + + if ((flags & ~(FORK_NOSIGCHLD | FORK_WAITPID)) != 0) { + errno = EINVAL; + return (-1); + } + + memset(&ign, 0, sizeof(ign)); + if (flags & FORK_NOSIGCHLD) + ign.sa_handler = SIG_IGN; + sigemptyset(&ign.sa_mask); + + sigaction(SIGCHLD, NULL, &oldact); + sigaction(SIGCHLD, &ign, NULL); + + pid = fork(); + + sigaction(SIGCHLD, &oldact, NULL); + + return pid; +} + +/* + * 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; +#ifdef F_DUP2FD + case F_DUP2FD: +#endif +#ifdef F_DUP2FD_CLOEXEC + case F_DUP2FD_CLOEXEC: +#endif +#ifdef F_DUP2FD_CLOFORK + case F_DUP2FD_CLOFORK: +#endif +#if defined(F_DUP2FD) || defined(F_DUP2FD_CLOEXEC) || defined(F_DUP2FD_CLOFORK) + dup = fcntl(fd, cmd, fd + 1); + break; +#endif +#ifdef F_DUP3FD + case F_DUP3FD: + dup = fcntl(fd, cmd | (targ_flags << F_DUP3FD_SHIFT), fd + 1); + break; +#endif + 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); +} + +#ifdef F_DUP2FD +static void +oclo_fdup2fd(const clo_create_t *c) +{ + oclo_fdup_common(c, 0, F_DUP2FD); +} +#endif + +#ifdef F_DUP2FD_CLOFORK +static void +oclo_fdup2fd_fork(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOFORK, F_DUP2FD_CLOFORK); +} +#endif + +#ifdef F_DUP2FD_CLOEXEC +static void +oclo_fdup2fd_exec(const clo_create_t *c) +{ + oclo_fdup_common(c, FD_CLOEXEC, F_DUP2FD_CLOEXEC); +} +#endif + +#ifdef F_DUP3FD +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); +} +#endif + +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); + } + + /* + * XXX + * We have to add 4 here to avoid this error message: + * found insufficient message control length: expected at least 0x18, found 0x14 + */ + if (msg.msg_controllen + 4 < 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 +}, { +#ifdef F_DUP2FD + .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 +}, { + .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 +#ifdef F_DUP2FD_CLOEXEC + .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 +}, { +#endif +#ifdef F_DUP3FD + .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 +}, { +#endif + .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) +{ + char dir[PATH_MAX], file[PATH_MAX]; + char **argv; + + /* + * XXX + * There's no way to get the full pathname to an executable in OpenBSD + * so use the cwd here. + */ + if (getcwd(dir, sizeof(dir)) == NULL) + err(1, "getcwd"); + + if (snprintf(file, sizeof (file), "%s/%s", 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++) { + if (asprintf(&argv[i + 1], "0x%x", oclo_rtdata[i].crt_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 (long unsigned int 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/regress/sys/kern/oclo/oclo_errors.c b/regress/sys/kern/oclo/oclo_errors.c new file mode 100644 index 000000000000..5d47f6493e29 --- /dev/null +++ b/regress/sys/kern/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)); +} + +#ifdef F_DUP3FD +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)); +} +#endif + +static bool +oclo_pipe2(const char *desc, int flags) +{ + int fds[2], ret; + + ret = pipe2(fds, flags); + return (oclo_check(desc, "piped", ret, errno)); +} + +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)); +} + +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; + } + +#ifdef F_DUP3FD + 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; + } +#endif + + 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 (!oclo_socket("socket(): INT32_MAX", INT32_MAX)) { + ret = EXIT_FAILURE; + } + +#if 0 /* XXX */ + 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/regress/sys/kern/oclo/ocloexec_verify.c b/regress/sys/kern/oclo/ocloexec_verify.c new file mode 100644 index 000000000000..f0a5fe1b852b --- /dev/null +++ b/regress/sys/kern/oclo/ocloexec_verify.c @@ -0,0 +1,146 @@ +/* + * 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 +#include + +#define strerrorname_np(e) (sys_errlist[e]) + +static int +getmaxfd(void) +{ + char errbuf[_POSIX2_LINE_MAX]; + struct kinfo_file *kf; + kvm_t *kd; + int i, cnt, max; + + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + if (kd == NULL) + errx(1, "%s", errbuf); + + kf = kvm_getfiles(kd, KERN_FILE_BYPID, getpid(), sizeof(*kf), &cnt); + if (kf == NULL) + errx(1, "kvm_getfiles: %s", kvm_geterr(kd)); + + max = -1; + for (i = 0; i < cnt; i++) + if (kf[i].fd_fd > max) + max = kf[i].fd_fd; + + 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/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c index 3e57566b8207..550b1c3ae9ef 100644 --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -80,6 +80,7 @@ int dodup3(struct proc *, int, int, int, register_t *); #define DUPF_CLOEXEC 0x01 #define DUPF_DUP2 0x02 +#define DUPF_CLOFORK 0x04 struct pool file_pool; struct pool fdesc_pool; @@ -336,7 +337,7 @@ sys_dup3(struct proc *p, void *v, register_t *retval) if (SCARG(uap, from) == SCARG(uap, to)) return (EINVAL); - if (SCARG(uap, flags) & ~O_CLOEXEC) + if (SCARG(uap, flags) & ~(O_CLOEXEC | O_CLOFORK)) return (EINVAL); return (dodup3(p, SCARG(uap, from), SCARG(uap, to), SCARG(uap, flags), retval)); @@ -388,6 +389,8 @@ dodup3(struct proc *p, int old, int new, int flags, register_t *retval) dupflags = DUPF_DUP2; if (flags & O_CLOEXEC) dupflags |= DUPF_CLOEXEC; + if (flags & O_CLOFORK) + dupflags |= DUPF_CLOFORK; /* No need for FRELE(), finishdup() uses current ref. */ return (finishdup(p, fp, old, new, retval, dupflags)); @@ -423,6 +426,7 @@ sys_fcntl(struct proc *p, void *v, register_t *retval) case F_DUPFD: case F_DUPFD_CLOEXEC: + case F_DUPFD_CLOFORK: newmin = (long)SCARG(uap, arg); if ((u_int)newmin >= lim_cur(RLIMIT_NOFILE) || (u_int)newmin >= atomic_load_int(&maxfiles)) { @@ -444,6 +448,8 @@ sys_fcntl(struct proc *p, void *v, register_t *retval) if (SCARG(uap, cmd) == F_DUPFD_CLOEXEC) dupflags |= DUPF_CLOEXEC; + else if (SCARG(uap, cmd) == F_DUPFD_CLOFORK) + dupflags |= DUPF_CLOFORK; /* No need for FRELE(), finishdup() uses current ref. */ error = finishdup(p, fp, fd, i, retval, dupflags); @@ -452,16 +458,17 @@ sys_fcntl(struct proc *p, void *v, register_t *retval) case F_GETFD: fdplock(fdp); - *retval = fdp->fd_ofileflags[fd] & UF_EXCLOSE ? 1 : 0; + *retval = + ((fdp->fd_ofileflags[fd] & UF_EXCLOSE) ? FD_CLOEXEC : 0) | + ((fdp->fd_ofileflags[fd] & UF_FOCLOSE) ? FD_CLOFORK : 0); fdpunlock(fdp); break; case F_SETFD: fdplock(fdp); - if ((long)SCARG(uap, arg) & 1) - fdp->fd_ofileflags[fd] |= UF_EXCLOSE; - else - fdp->fd_ofileflags[fd] &= ~UF_EXCLOSE; + fdp->fd_ofileflags[fd] = + (((long)SCARG(uap, arg) & FD_CLOEXEC) ? UF_EXCLOSE : 0) | + (((long)SCARG(uap, arg) & FD_CLOFORK) ? UF_FOCLOSE : 0); fdpunlock(fdp); break; @@ -667,9 +674,12 @@ finishdup(struct proc *p, struct file *fp, int old, int new, fdp->fd_ofiles[new] = fp; mtx_leave(&fdp->fd_fplock); - fdp->fd_ofileflags[new] = fdp->fd_ofileflags[old] & ~UF_EXCLOSE; + fdp->fd_ofileflags[new] = + fdp->fd_ofileflags[old] & ~(UF_EXCLOSE | UF_FOCLOSE); if (dupflags & DUPF_CLOEXEC) fdp->fd_ofileflags[new] |= UF_EXCLOSE; + if (dupflags & DUPF_CLOFORK) + fdp->fd_ofileflags[new] |= UF_FOCLOSE; *retval = new; if (oldfp != NULL) { @@ -711,7 +721,7 @@ fdinsert(struct filedesc *fdp, int fd, int flags, struct file *fp) fdp->fd_ofiles[fd] = fp; mtx_leave(&fdp->fd_fplock); - fdp->fd_ofileflags[fd] |= (flags & UF_EXCLOSE); + fdp->fd_ofileflags[fd] |= (flags & (UF_EXCLOSE | UF_FOCLOSE)); } void @@ -1150,6 +1160,7 @@ fdcopy(struct process *pr) * their internal consistency, so close them here. */ if (fp->f_count >= FDUP_MAX_COUNT || + (fdp->fd_ofileflags[i] & UF_FOCLOSE) != 0 || fp->f_type == DTYPE_KQUEUE) { if (i < newfdp->fd_freefile) newfdp->fd_freefile = i; @@ -1407,8 +1418,9 @@ dupfdopen(struct proc *p, int indx, int mode) fdp->fd_ofiles[indx] = wfp; mtx_leave(&fdp->fd_fplock); - fdp->fd_ofileflags[indx] = (fdp->fd_ofileflags[indx] & UF_EXCLOSE) | - (fdp->fd_ofileflags[dupfd] & ~UF_EXCLOSE); + fdp->fd_ofileflags[indx] = + (fdp->fd_ofileflags[indx] & (UF_EXCLOSE | UF_FOCLOSE)) | + (fdp->fd_ofileflags[dupfd] & ~(UF_EXCLOSE | UF_FOCLOSE)); return (0); } diff --git a/sys/kern/sys_pipe.c b/sys/kern/sys_pipe.c index 12254a052dab..d278647d382e 100644 --- a/sys/kern/sys_pipe.c +++ b/sys/kern/sys_pipe.c @@ -162,7 +162,7 @@ sys_pipe2(struct proc *p, void *v, register_t *retval) syscallarg(int) flags; } */ *uap = v; - if (SCARG(uap, flags) & ~(O_CLOEXEC | FNONBLOCK)) + if (SCARG(uap, flags) & ~(O_CLOEXEC | O_CLOFORK | FNONBLOCK)) return (EINVAL); return (dopipe(p, SCARG(uap, fdp), SCARG(uap, flags))); @@ -175,9 +175,10 @@ dopipe(struct proc *p, int *ufds, int flags) struct file *rf, *wf; struct pipe_pair *pp; struct pipe *rpipe, *wpipe = NULL; - int fds[2], cloexec, error; + int fds[2], fdflags, error; - cloexec = (flags & O_CLOEXEC) ? UF_EXCLOSE : 0; + fdflags = ((flags & O_CLOEXEC) ? UF_EXCLOSE : 0) | + ((flags & O_CLOFORK) ? UF_FOCLOSE : 0); pp = pipe_pair_create(); if (pp == NULL) @@ -203,8 +204,8 @@ dopipe(struct proc *p, int *ufds, int flags) wf->f_data = wpipe; wf->f_ops = &pipeops; - fdinsert(fdp, fds[0], cloexec, rf); - fdinsert(fdp, fds[1], cloexec, wf); + fdinsert(fdp, fds[0], fdflags, rf); + fdinsert(fdp, fds[1], fdflags, wf); error = copyout(fds, ufds, sizeof(fds)); if (error == 0) { diff --git a/sys/kern/uipc_syscalls.c b/sys/kern/uipc_syscalls.c index 7a93c571a29a..00833af2705a 100644 --- a/sys/kern/uipc_syscalls.c +++ b/sys/kern/uipc_syscalls.c @@ -81,7 +81,7 @@ sys_socket(struct proc *p, void *v, register_t *retval) struct file *fp; int type = SCARG(uap, type); int domain = SCARG(uap, domain); - int fd, cloexec, nonblock, fflag, error; + int fd, fdflags, nonblock, fflag, error; unsigned int ss = 0; if ((type & SOCK_DNS) && !(domain == AF_INET || domain == AF_INET6)) @@ -93,8 +93,9 @@ sys_socket(struct proc *p, void *v, register_t *retval) if (error) return (error); - type &= ~(SOCK_CLOEXEC | SOCK_NONBLOCK | SOCK_DNS); - cloexec = (SCARG(uap, type) & SOCK_CLOEXEC) ? UF_EXCLOSE : 0; + type &= ~(SOCK_CLOEXEC | SOCK_CLOFORK | SOCK_NONBLOCK | SOCK_DNS); + fdflags = ((SCARG(uap, type) & SOCK_CLOEXEC) ? UF_EXCLOSE : 0) | + ((SCARG(uap, type) & SOCK_CLOFORK) ? UF_FOCLOSE : 0); nonblock = SCARG(uap, type) & SOCK_NONBLOCK; fflag = FREAD | FWRITE | (nonblock ? FNONBLOCK : 0); @@ -113,7 +114,7 @@ sys_socket(struct proc *p, void *v, register_t *retval) fp->f_ops = &socketops; so->so_state |= ss; fp->f_data = so; - fdinsert(fdp, fd, cloexec, fp); + fdinsert(fdp, fd, fdflags, fp); fdpunlock(fdp); FRELE(fp, p); *retval = fd; @@ -240,7 +241,7 @@ sys_accept4(struct proc *p, void *v, register_t *retval) syscallarg(socklen_t *) int flags; } */ *uap = v; - if (SCARG(uap, flags) & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) + if (SCARG(uap, flags) & ~(SOCK_CLOEXEC | SOCK_CLOFORK | SOCK_NONBLOCK)) return (EINVAL); return (doaccept(p, SCARG(uap, s), SCARG(uap, name), @@ -257,9 +258,10 @@ doaccept(struct proc *p, int sock, struct sockaddr *name, socklen_t *anamelen, socklen_t namelen; int error, tmpfd; struct socket *head, *so; - int cloexec, nflag; + int fdflags, nflag; - cloexec = (flags & SOCK_CLOEXEC) ? UF_EXCLOSE : 0; + fdflags = ((flags & SOCK_CLOEXEC) ? UF_EXCLOSE : 0) | + ((flags & SOCK_CLOFORK) ? UF_FOCLOSE : 0); if (name && (error = copyin(anamelen, &namelen, sizeof (namelen)))) return (error); @@ -346,7 +348,7 @@ doaccept(struct proc *p, int sock, struct sockaddr *name, socklen_t *anamelen, } fdplock(fdp); - fdinsert(fdp, tmpfd, cloexec, fp); + fdinsert(fdp, tmpfd, fdflags, fp); fdpunlock(fdp); FRELE(fp, p); *retval = tmpfd; @@ -457,10 +459,11 @@ sys_socketpair(struct proc *p, void *v, register_t *retval) struct filedesc *fdp = p->p_fd; struct file *fp1 = NULL, *fp2 = NULL; struct socket *so1, *so2; - int type, cloexec, nonblock, fflag, error, sv[2]; + int type, fdflags, nonblock, fflag, error, sv[2]; - type = SCARG(uap, type) & ~(SOCK_CLOEXEC | SOCK_NONBLOCK); - cloexec = (SCARG(uap, type) & SOCK_CLOEXEC) ? UF_EXCLOSE : 0; + type = SCARG(uap, type) & ~(SOCK_CLOEXEC | SOCK_CLOFORK | SOCK_NONBLOCK); + fdflags = ((SCARG(uap, type) & SOCK_CLOEXEC) ? UF_EXCLOSE : 0) | + ((SCARG(uap, type) & SOCK_CLOFORK) ? UF_FOCLOSE : 0); nonblock = SCARG(uap, type) & SOCK_NONBLOCK; fflag = FREAD | FWRITE | (nonblock ? FNONBLOCK : 0); @@ -498,8 +501,8 @@ sys_socketpair(struct proc *p, void *v, register_t *retval) fp2->f_data = so2; error = copyout(sv, SCARG(uap, rsv), 2 * sizeof (int)); if (error == 0) { - fdinsert(fdp, sv[0], cloexec, fp1); - fdinsert(fdp, sv[1], cloexec, fp2); + fdinsert(fdp, sv[0], fdflags, fp1); + fdinsert(fdp, sv[1], fdflags, fp2); fdpunlock(fdp); #ifdef KTRACE if (KTRPOINT(p, KTR_STRUCT)) diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index f50a040d1e8a..b025c071f411 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -1146,6 +1146,8 @@ unp_externalize(struct mbuf *rights, socklen_t controllen, int flags) fdp->fd_ofileflags[fds[i]] = (rp->flags & UF_PLEDGED); if (flags & MSG_CMSG_CLOEXEC) fdp->fd_ofileflags[fds[i]] |= UF_EXCLOSE; + if (flags & MSG_CMSG_CLOFORK) + fdp->fd_ofileflags[fds[i]] |= UF_FOCLOSE; rp++; } diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c index 1f5731712a04..32734b2a3f4b 100644 --- a/sys/kern/vfs_syscalls.c +++ b/sys/kern/vfs_syscalls.c @@ -1086,7 +1086,7 @@ doopenat(struct proc *p, int fd, const char *path, int oflags, mode_t mode, struct file *fp; struct vnode *vp; struct vattr vattr; - int flags, cloexec, cmode; + int flags, fdflags, cmode; int type, indx, error, localtrunc = 0; struct flock lf; struct nameidata nd; @@ -1099,7 +1099,8 @@ doopenat(struct proc *p, int fd, const char *path, int oflags, mode_t mode, return (error); } - cloexec = (oflags & O_CLOEXEC) ? UF_EXCLOSE : 0; + fdflags = ((oflags & O_CLOEXEC) ? UF_EXCLOSE : 0) | + ((oflags & O_CLOFORK) ? UF_FOCLOSE : 0); fdplock(fdp); if ((error = falloc(p, &fp, &indx)) != 0) { @@ -1200,7 +1201,7 @@ doopenat(struct proc *p, int fd, const char *path, int oflags, mode_t mode, KERNEL_UNLOCK(); *retval = indx; fdplock(fdp); - fdinsert(fdp, indx, cloexec, fp); + fdinsert(fdp, indx, fdflags, fp); fdpunlock(fdp); FRELE(fp, p); return (error); @@ -1224,7 +1225,7 @@ sys___tmpfd(struct proc *p, void *v, register_t *retval) struct file *fp; struct vnode *vp; int oflags = SCARG(uap, flags); - int flags, cloexec, cmode; + int flags, fdflags, cmode; int indx, error; unsigned int i; struct nameidata nd; @@ -1232,9 +1233,11 @@ sys___tmpfd(struct proc *p, void *v, register_t *retval) static const char *letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"; /* most flags are hardwired */ - oflags = O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | (oflags & O_CLOEXEC); + oflags = O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | + (oflags & (O_CLOEXEC | O_CLOFORK)); - cloexec = (oflags & O_CLOEXEC) ? UF_EXCLOSE : 0; + fdflags = ((oflags & O_CLOEXEC) ? UF_EXCLOSE : 0) | + ((oflags & O_CLOFORK) ? UF_FOCLOSE : 0); fdplock(fdp); if ((error = falloc(p, &fp, &indx)) != 0) { @@ -1270,7 +1273,7 @@ sys___tmpfd(struct proc *p, void *v, register_t *retval) VOP_UNLOCK(vp); *retval = indx; fdplock(fdp); - fdinsert(fdp, indx, cloexec, fp); + fdinsert(fdp, indx, fdflags, fp); fdpunlock(fdp); FRELE(fp, p); @@ -1352,7 +1355,7 @@ sys_fhopen(struct proc *p, void *v, register_t *retval) struct vnode *vp = NULL; struct mount *mp; struct ucred *cred = p->p_ucred; - int flags, cloexec; + int flags, fdflags; int type, indx, error=0; struct flock lf; struct vattr va; @@ -1370,7 +1373,8 @@ sys_fhopen(struct proc *p, void *v, register_t *retval) if ((flags & O_CREAT)) return (EINVAL); - cloexec = (flags & O_CLOEXEC) ? UF_EXCLOSE : 0; + fdflags = ((flags & O_CLOEXEC) ? UF_EXCLOSE : 0) | + ((flags & O_CLOFORK) ? UF_FOCLOSE : 0); fdplock(fdp); if ((error = falloc(p, &fp, &indx)) != 0) { @@ -1456,7 +1460,7 @@ sys_fhopen(struct proc *p, void *v, register_t *retval) VOP_UNLOCK(vp); *retval = indx; fdplock(fdp); - fdinsert(fdp, indx, cloexec, fp); + fdinsert(fdp, indx, fdflags, fp); fdpunlock(fdp); FRELE(fp, p); return (0); diff --git a/sys/sys/fcntl.h b/sys/sys/fcntl.h index e964ea49dde1..bd6e329afa1d 100644 --- a/sys/sys/fcntl.h +++ b/sys/sys/fcntl.h @@ -106,6 +106,7 @@ /* defined by POSIX Issue 7 */ #define O_CLOEXEC 0x10000 /* atomically set FD_CLOEXEC */ #define O_DIRECTORY 0x20000 /* fail if not a directory */ +#define O_CLOFORK 0x40000 /* atomically set FD_CLOFORK */ #ifdef _KERNEL /* @@ -158,9 +159,13 @@ #if __BSD_VISIBLE #define F_ISATTY 11 /* used by isatty(3) */ #endif +#if __POSIX_VISIBLE >= 202405 +#define F_DUPFD_CLOFORK 12 /* duplicate with FD_CLOFORK set */ +#endif /* file descriptor flags (F_GETFD, F_SETFD) */ #define FD_CLOEXEC 1 /* close-on-exec flag */ +#define FD_CLOFORK 2 /* close-on-fork flag */ /* 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 50bc7734a020..38857d98fd78 100644 --- a/sys/sys/filedesc.h +++ b/sys/sys/filedesc.h @@ -115,6 +115,7 @@ struct filedesc0 { */ #define UF_EXCLOSE 0x01 /* auto-close on exec */ #define UF_PLEDGED 0x02 /* open after pledge(2) */ +#define UF_FOCLOSE 0x04 /* auto-close on fork */ /* * Flags on the file descriptor table. diff --git a/sys/sys/socket.h b/sys/sys/socket.h index 4fd50d292746..4e4987ca2553 100644 --- a/sys/sys/socket.h +++ b/sys/sys/socket.h @@ -79,6 +79,7 @@ typedef __sa_family_t sa_family_t; /* sockaddr address family type */ #define SOCK_NONBLOCK_INHERIT 0x2000 /* inherit O_NONBLOCK from listener */ #endif #define SOCK_DNS 0x1000 /* set SS_DNS */ +#define SOCK_CLOFORK 0x0800 /* set FD_CLOFORK */ #endif /* __BSD_VISIBLE */ /* @@ -511,6 +512,7 @@ struct timespec; #define MSG_NOSIGNAL 0x400 /* do not send SIGPIPE */ #define MSG_CMSG_CLOEXEC 0x800 /* set FD_CLOEXEC on received fds */ #define MSG_WAITFORONE 0x1000 /* nonblocking but wait for one msg */ +#define MSG_CMSG_CLOFORK 0x2000 /* set FD_CLOFORK on received fds */ /* * Header for ancillary data objects in msg_control buffer. diff --git a/usr.bin/fstat/fstat.c b/usr.bin/fstat/fstat.c index a74d3a6e916a..acd1ffe26cc2 100644 --- a/usr.bin/fstat/fstat.c +++ b/usr.bin/fstat/fstat.c @@ -482,6 +482,8 @@ vtrans(struct kinfo_file *kf) strlcat(rwep, "w", sizeof rwep); if (kf->fd_ofileflags & UF_EXCLOSE) strlcat(rwep, "e", sizeof rwep); + if (kf->fd_ofileflags & UF_FOCLOSE) + strlcat(rwep, "f", sizeof rwep); if (kf->fd_ofileflags & UF_PLEDGED) strlcat(rwep, "p", sizeof rwep); printf(" %4s", rwep);