diff options
48 files changed, 2420 insertions, 99 deletions
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 000000000000..8e6f7c726f24 --- /dev/null +++ b/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo.c @@ -0,0 +1,1341 @@ +/* + * 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 2025 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. Note + * that O_CLOFORK is cleared after exec. The original specification in POSIX has + * it being retained; however, this issue was raised after the spec was + * published as folks went to implement it and we have ended up following along + * with the divergence of other implementations. + */ + +#include <sys/param.h> +#include <sys/sysctl.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +void *recallocarray(void *, size_t, size_t, size_t); + +#define strerrorname_np(e) (sys_errlist[e]) + +/* + * 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%zx, 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 that the file descriptor makes it to our child verifier. + * Importantly, with the changes that cause O_CLOFORK to be cleared on exec, we + * should not see any file descriptors with the flag in the child. This allows + * us to confirm that this is what we expect. + * + * In addition, this 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, 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++) { + 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 (size_t i = 0; i < nitems(oclo_create); i++) { + oclo_create[i].clo_func(&oclo_create[i]); + } + + pid_t child = fork(); + 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 000000000000..05b0c1a0839b --- /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 <netinet/in.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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 000000000000..e33c61f03d54 --- /dev/null +++ b/cddl/contrib/opensolaris/tests/os-tests/tests/oclo/ocloexec_verify.c @@ -0,0 +1,154 @@ +/* + * 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 2025 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. This + * allows us to verify that the combinations worked and that FD_CLOFORK was + * properly cleared. + */ + +#include <sys/types.h> +#include <sys/user.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libutil.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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); +} + +/* + * Our flags may have FD_CLOFORK set in them (anything with FD_CLOEXEC Should + * not exist by definition). FD_CLOFORK is supposed to be cleared on exec. We + * still indicate which file descriptors FD_CLOFORK so we can check where it + * wasn't cleared. + */ +static bool +verify_flags(int fd, int exp_flags) +{ + bool fail = (exp_flags & FD_CLOEXEC) != 0; + int flags = fcntl(fd, F_GETFD, NULL); + bool clofork = (exp_flags & FD_CLOFORK) != 0; + exp_flags &= ~FD_CLOFORK; + + 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 (clofork && (flags & FD_CLOFORK) != 0) { + warnx("TEST FAILED: post-fork fd %d (flags %d) retained " + "FD_CLOFORK, but it should have been cleared", fd, 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/lib80211/regdomain.xml b/lib/lib80211/regdomain.xml index 9116e54c31cf..16b74445f429 100644 --- a/lib/lib80211/regdomain.xml +++ b/lib/lib80211/regdomain.xml @@ -494,6 +494,10 @@ <flags>IEEE80211_CHAN_PASSIVE</flags> <flags>IEEE80211_CHAN_DFS</flags> </band> + <band> + <freqband ref="A20_5745_5865"/> + <maxpower>13</maxpower> + </band> </netband> <netband mode="11ng"> <band> @@ -548,6 +552,14 @@ <flags>IEEE80211_CHAN_PASSIVE</flags> <flags>IEEE80211_CHAN_DFS</flags> </band> + <band> + <freqband ref="NA20_5745_5865"/> + <maxpower>13</maxpower> + </band> + <band> + <freqband ref="NA40_5745_5845"/> + <maxpower>13</maxpower> + </band> </netband> <netband mode="11ac"> <!-- 5150-5250/80, 200 mW, indoor --> @@ -645,7 +657,7 @@ <flags>IEEE80211_CHAN_DFS</flags> </band> <band> - <freqband ref="AC2_5745_5805_40"/> + <freqband ref="AC2_5745_5845_40"/> <maxpower>13</maxpower> <flags>IEEE80211_CHAN_HT40</flags> <flags>IEEE80211_CHAN_VHT40</flags> @@ -658,13 +670,6 @@ <flags>IEEE80211_CHAN_VHT80</flags> <flags>IEEE80211_CHAN_DFS</flags> </band> - <band> - <freqband ref="AC2_5745_5885_160"/> - <maxpower>13</maxpower> - <flags>IEEE80211_CHAN_HT40</flags> - <flags>IEEE80211_CHAN_VHT160</flags> - <flags>IEEE80211_CHAN_DFS</flags> - </band> </netband> </rd> @@ -2304,6 +2309,29 @@ <chanwidth>20</chanwidth> <chansep>20</chansep> <flags>IEEE80211_CHAN_A</flags> </freqband> +<freqband id="A20_5745_5865"> + <freqstart>5745</freqstart> + <freqend>5865</freqend> + <chanwidth>20</chanwidth> + <chansep>20</chansep> + <flags>IEEE80211_CHAN_A</flags> +</freqband> +<freqband id="NA20_5745_5865"> + <freqstart>5745</freqstart> + <freqend>5865</freqend> + <chanwidth>20</chanwidth> + <chansep>20</chansep> + <flags>IEEE80211_CHAN_A</flags> + <flags>IEEE80211_CHAN_HT20</flags> +</freqband> +<freqband id="NA40_5745_5845"> + <freqstart>5745</freqstart> + <freqend>5845</freqend> + <chanwidth>40</chanwidth> + <chansep>20</chansep> + <flags>IEEE80211_CHAN_A</flags> + <flags>IEEE80211_CHAN_HT40</flags> +</freqband> <freqband id="F1_5660_5700"> <freqstart>5660</freqstart> <freqend>5700</freqend> <chanwidth>20</chanwidth> <chansep>20</chansep> diff --git a/lib/libc/gen/dup3.3 b/lib/libc/gen/dup3.3 index f2798930797b..338a9ae74c64 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 fca1e99fb47b..1401c1f5b607 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 675ed476c51d..dca1c08b0aed 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 000000000000..11e1fda744c7 --- /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 <otto@drijf.net> + * + * 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 <errno.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +/* + * 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 53926b3153d2..2da2af066a5b 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 aaa4c55607ac..1885a6fdeaa8 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 @@ -59,6 +59,8 @@ Supported .Bl -tag -width ".Dv CLOSE_RANGE_CLOEXEC" .It Dv CLOSE_RANGE_CLOEXEC Set the close-on-exec flag on descriptors in the range instead of closing them. +.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, @@ -90,3 +92,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/execve.2 b/lib/libsys/execve.2 index 5a35980e9555..dc85b9321e48 100644 --- a/lib/libsys/execve.2 +++ b/lib/libsys/execve.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd January 26, 2022 +.Dd July 02, 2025 .Dt EXECVE 2 .Os .Sh NAME @@ -127,7 +127,10 @@ flag is set (see and .Xr fcntl 2 ) . Descriptors that remain open are unaffected by -.Fn execve . +.Fn execve , +except those with the close-on-fork flag +.Dv FD_CLOFORK +which is cleared from all file descriptors. If any of the standard descriptors (0, 1, and/or 2) are closed at the time .Fn execve diff --git a/lib/libsys/fcntl.2 b/lib/libsys/fcntl.2 index 604de43e5e8c..3cf8adc29f88 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 June 5, 2025 +.Dd June 24, 2025 .Dt FCNTL 2 .Os .Sh NAME @@ -81,6 +81,13 @@ 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. +.It The .Dv FD_RESOLVE_BENEATH flag, described below, will be set if it was set on the original @@ -95,6 +102,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 @@ -117,6 +133,11 @@ 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 flags associated with the file descriptor .Fa fd . @@ -128,6 +149,10 @@ The file will be closed upon execution of .Fa ( arg is ignored). Otherwise, the file descriptor will remain open. +.It Dv FD_CLOFORK +The file will be closed upon execution of the +.Fn fork +family of system calls. .It Dv FD_RESOLVE_BENEATH All path name lookups relative to that file descriptor will behave as if the lookup had @@ -153,7 +178,8 @@ descriptor to also have the flag set. Set flags associated with .Fa fd . The available flags are -.Dv FD_CLOEXEC +.Dv FD_CLOEXEC , +.Dv FD_CLOFORK and .Dv FD_RESOLVE_BENEATH . The @@ -785,8 +811,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 , @@ -811,3 +839,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 7d548a42890d..e59b208a9ff5 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,16 @@ 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 +.Po see +.Fn fcntl 2 +and +.Dv O_CLOFORK +in +.Fn open 2 +.Pc , +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 84c4f02fce8a..a0e905a8f375 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,9 @@ error if file is not a directory .It Dv O_CLOEXEC automatically close file on .Xr execve 2 +.It Dv O_CLOFORK +automatically close file on any child process created with +.Fn fork 2 .It Dv O_VERIFY verify the contents of the file with .Xr mac_veriexec 4 @@ -360,6 +363,27 @@ may be used to set .Dv FD_CLOEXEC flag for the newly returned file descriptor. .Pp +.Dv O_CLOFORK +may be used to set +.Dv FD_CLOFORK +flag for the newly returned file descriptor. +The file will be closed on any child process created with +.Fn fork 2 , +.Fn vfork 2 +or +.Fn rfork 2 +with the +.Dv RFFDG +flag, remaining open in the parent. +Both the +.Dv O_CLOEXEC +and +.Dv O_CLOFORK +flags can be modified with the +.Dv F_SETFD +.Fn fcntl 2 +command. +.Pp .Dv O_VERIFY may be used to indicate to the kernel that the contents of the file should be verified before allowing the open to proceed. @@ -846,6 +870,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 9531c9717395..37d6eba420de 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 f3ee60b75663..b78cd70b8a1d 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 a383cbcc4d80..b211611c6354 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 5874a0791f4d..60dec74f9cc2 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 dc09c5747968..f8e26e6a9dae 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); } @@ -563,7 +564,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 @@ -873,7 +874,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 ee3a030a79e4..d5648ce0adc3 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/sbin/routed/routed.8 b/sbin/routed/routed.8 index 8cf12d7b60e1..334c828b943e 100644 --- a/sbin/routed/routed.8 +++ b/sbin/routed/routed.8 @@ -27,13 +27,20 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd June 27, 2022 +.Dd May 20, 2025 .Dt ROUTED 8 .Os .Sh NAME .Nm routed , .Nm rdisc .Nd network RIP and router discovery routing daemon +.Sh DEPRECATION NOTICE +The +.Nm routed +and +.Nm rdisc +utilities are deprecated and will be removed in +.Fx 16.0 . .Sh SYNOPSIS .Nm .Op Fl isqdghmpAtv diff --git a/sbin/routed/rtquery/rtquery.8 b/sbin/routed/rtquery/rtquery.8 index de5e1fc7cf96..ff46a3414dcf 100644 --- a/sbin/routed/rtquery/rtquery.8 +++ b/sbin/routed/rtquery/rtquery.8 @@ -1,11 +1,16 @@ .\" $Revision: 2.27 $ .\" -.Dd June 1, 1996 +.Dd May 20, 2025 .Dt RTQUERY 8 .Os .Sh NAME .Nm rtquery .Nd query routing daemons for their routing tables +.Sh DEPRECATION NOTICE +The +.Nm +utility is deprecated and will be removed in +.Fx 16.0 . .Sh SYNOPSIS .Nm .Op Fl np1 diff --git a/sys/amd64/amd64/efirt_machdep.c b/sys/amd64/amd64/efirt_machdep.c index f70e235a0150..fe5d60c978dd 100644 --- a/sys/amd64/amd64/efirt_machdep.c +++ b/sys/amd64/amd64/efirt_machdep.c @@ -63,8 +63,6 @@ 1u << EFI_MD_TYPE_FIRMWARE \ ) -uint32_t efi_map_regs; - static pml5_entry_t *efi_pml5; static pml4_entry_t *efi_pml4; static vm_object_t obj_1t1_pt; diff --git a/sys/amd64/amd64/machdep.c b/sys/amd64/amd64/machdep.c index 1e8f9b22bd19..f46462b39fa3 100644 --- a/sys/amd64/amd64/machdep.c +++ b/sys/amd64/amd64/machdep.c @@ -188,6 +188,12 @@ struct init_ops init_ops = { */ vm_paddr_t efi_systbl_phys; +/* + * Bitmap of extra EFI memory region types that should be preserved and mapped + * during runtime services calls. + */ +uint32_t efi_map_regs; + /* Intel ICH registers */ #define ICH_PMBASE 0x400 #define ICH_SMI_EN ICH_PMBASE + 0x30 diff --git a/sys/amd64/amd64/pmap.c b/sys/amd64/amd64/pmap.c index cae5436a1ff2..9c985df13ddf 100644 --- a/sys/amd64/amd64/pmap.c +++ b/sys/amd64/amd64/pmap.c @@ -9991,7 +9991,7 @@ pmap_demote_DMAP(vm_paddr_t base, vm_size_t len, bool invalidate) pd_entry_t *pde; vm_offset_t va; vm_page_t m, mpte; - bool changed; + bool changed, rv __diagused; if (len == 0) return; @@ -10021,8 +10021,8 @@ pmap_demote_DMAP(vm_paddr_t base, vm_size_t len, bool invalidate) if ((*pdpe & X86_PG_V) == 0) panic("pmap_demote_DMAP: invalid PDPE"); if ((*pdpe & PG_PS) != 0) { - if (!pmap_demote_pdpe(kernel_pmap, pdpe, va, m)) - panic("pmap_demote_DMAP: PDPE failed"); + rv = pmap_demote_pdpe(kernel_pmap, pdpe, va, m); + KASSERT(rv, ("pmap_demote_DMAP: PDPE failed")); changed = true; m = NULL; } @@ -10033,9 +10033,9 @@ pmap_demote_DMAP(vm_paddr_t base, vm_size_t len, bool invalidate) if ((*pde & PG_PS) != 0) { mpte->pindex = pmap_pde_pindex(va); pmap_pt_page_count_adj(kernel_pmap, 1); - if (!pmap_demote_pde_mpte(kernel_pmap, pde, va, - NULL, mpte)) - panic("pmap_demote_DMAP: PDE failed"); + rv = pmap_demote_pde_mpte(kernel_pmap, pde, va, + NULL, mpte); + KASSERT(rv, ("pmap_demote_DMAP: PDE failed")); changed = true; mpte = NULL; } diff --git a/sys/compat/freebsd32/freebsd32_sysent.c b/sys/compat/freebsd32/freebsd32_sysent.c index 3718a1b0c8ee..ef0aff8bf852 100644 --- a/sys/compat/freebsd32/freebsd32_sysent.c +++ b/sys/compat/freebsd32/freebsd32_sysent.c @@ -659,7 +659,7 @@ struct sysent freebsd32_sysent[] = { { .sy_narg = AS(getrlimitusage_args), .sy_call = (sy_call_t *)sys_getrlimitusage, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 589 = getrlimitusage */ { .sy_narg = AS(fchroot_args), .sy_call = (sy_call_t *)sys_fchroot, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 590 = fchroot */ { .sy_narg = AS(freebsd32_setcred_args), .sy_call = (sy_call_t *)freebsd32_setcred, .sy_auevent = AUE_SETCRED, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 591 = freebsd32_setcred */ - { .sy_narg = AS(exterrctl_args), .sy_call = (sy_call_t *)sys_exterrctl, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 592 = exterrctl */ + { .sy_narg = AS(exterrctl_args), .sy_call = (sy_call_t *)sys_exterrctl, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 592 = exterrctl */ { .sy_narg = AS(inotify_add_watch_at_args), .sy_call = (sy_call_t *)sys_inotify_add_watch_at, .sy_auevent = AUE_INOTIFY, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 593 = inotify_add_watch_at */ { .sy_narg = AS(inotify_rm_watch_args), .sy_call = (sy_call_t *)sys_inotify_rm_watch, .sy_auevent = AUE_INOTIFY, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 594 = inotify_rm_watch */ }; diff --git a/sys/fs/nfs/nfs_commonsubs.c b/sys/fs/nfs/nfs_commonsubs.c index f46b0d282861..4c498e96a3c0 100644 --- a/sys/fs/nfs/nfs_commonsubs.c +++ b/sys/fs/nfs/nfs_commonsubs.c @@ -630,6 +630,10 @@ nfscl_fillsattr(struct nfsrv_descript *nd, struct vattr *vap, NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_OWNERGROUP); if ((flags & NFSSATTR_FULL) && vap->va_size != VNOVAL) NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_SIZE); + if ((flags & NFSSATTR_FULL) && vap->va_flags != VNOVAL) { + NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_HIDDEN); + NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_SYSTEM); + } if (vap->va_atime.tv_sec != VNOVAL) NFSSETBIT_ATTRBIT(&attrbits, NFSATTRBIT_TIMEACCESSSET); if (vap->va_mtime.tv_sec != VNOVAL) @@ -1314,6 +1318,7 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp, u_int32_t freenum = 0, tuint; u_int64_t uquad = 0, thyp, thyp2; uint16_t tui16; + long has_pathconf; #ifdef QUOTA struct dqblk dqb; uid_t savuid; @@ -1421,6 +1426,16 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp, NFSCLRBIT_ATTRBIT(&checkattrbits, NFSATTRBIT_ACL); NFSCLRBIT_ATTRBIT(&checkattrbits, NFSATTRBIT_ACLSUPPORT); } + /* Some filesystems do not support uf_hidden */ + if (vp == NULL || VOP_PATHCONF(vp, + _PC_HAS_HIDDENSYSTEM, &has_pathconf) != 0) + has_pathconf = 0; + if (has_pathconf == 0) { + NFSCLRBIT_ATTRBIT(&checkattrbits, + NFSATTRBIT_HIDDEN); + NFSCLRBIT_ATTRBIT(&checkattrbits, + NFSATTRBIT_SYSTEM); + } if (!NFSEQUAL_ATTRBIT(&retattrbits, &checkattrbits) || retnotsup) *retcmpp = NFSERR_NOTSAME; @@ -1521,15 +1536,13 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp, NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED); if (compare) { if (!(*retcmpp)) { - long has_named_attr; - if (vp == NULL || VOP_PATHCONF(vp, - _PC_HAS_NAMEDATTR, &has_named_attr) + _PC_HAS_NAMEDATTR, &has_pathconf) != 0) - has_named_attr = 0; - if ((has_named_attr != 0 && + has_pathconf = 0; + if ((has_pathconf != 0 && *tl != newnfs_true) || - (has_named_attr == 0 && + (has_pathconf == 0 && *tl != newnfs_false)) *retcmpp = NFSERR_NOTSAME; } @@ -1792,9 +1805,17 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp, free(cp2, M_NFSSTRING); break; case NFSATTRBIT_HIDDEN: - NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED); - if (compare && !(*retcmpp)) - *retcmpp = NFSERR_ATTRNOTSUPP; + NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED); + if (compare) { + if (!(*retcmpp) && ((*tl == newnfs_true && + (nap->na_flags & UF_HIDDEN) == 0) || + (*tl == newnfs_false && + (nap->na_flags & UF_HIDDEN) != 0))) + *retcmpp = NFSERR_NOTSAME; + } else if (nap != NULL) { + if (*tl == newnfs_true) + nap->na_flags |= UF_HIDDEN; + } attrsum += NFSX_UNSIGNED; break; case NFSATTRBIT_HOMOGENEOUS: @@ -2166,9 +2187,17 @@ nfsv4_loadattr(struct nfsrv_descript *nd, vnode_t vp, attrsum += NFSX_HYPER; break; case NFSATTRBIT_SYSTEM: - NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED); - if (compare && !(*retcmpp)) - *retcmpp = NFSERR_ATTRNOTSUPP; + NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED); + if (compare) { + if (!(*retcmpp) && ((*tl == newnfs_true && + (nap->na_flags & UF_SYSTEM) == 0) || + (*tl == newnfs_false && + (nap->na_flags & UF_SYSTEM) != 0))) + *retcmpp = NFSERR_NOTSAME; + } else if (nap != NULL) { + if (*tl == newnfs_true) + nap->na_flags |= UF_SYSTEM; + } attrsum += NFSX_UNSIGNED; break; case NFSATTRBIT_TIMEACCESS: @@ -2634,7 +2663,7 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp, size_t atsiz; bool xattrsupp; short irflag; - long has_named_attr; + long has_pathconf; #ifdef QUOTA struct dqblk dqb; uid_t savuid; @@ -2751,6 +2780,14 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp, NFSCLRBIT_ATTRBIT(&attrbits,NFSATTRBIT_ACLSUPPORT); NFSCLRBIT_ATTRBIT(&attrbits,NFSATTRBIT_ACL); } + if (cred == NULL || p == NULL || vp == NULL || + VOP_PATHCONF(vp, _PC_HAS_HIDDENSYSTEM, + &has_pathconf) != 0) + has_pathconf = 0; + if (has_pathconf == 0) { + NFSCLRBIT_ATTRBIT(&attrbits, NFSATTRBIT_HIDDEN); + NFSCLRBIT_ATTRBIT(&attrbits, NFSATTRBIT_SYSTEM); + } retnum += nfsrv_putattrbit(nd, &attrbits); break; case NFSATTRBIT_TYPE: @@ -2791,10 +2828,10 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp, break; case NFSATTRBIT_NAMEDATTR: NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); - if (VOP_PATHCONF(vp, _PC_HAS_NAMEDATTR, &has_named_attr) - != 0) - has_named_attr = 0; - if (has_named_attr != 0) + if (VOP_PATHCONF(vp, _PC_HAS_NAMEDATTR, + &has_pathconf) != 0) + has_pathconf = 0; + if (has_pathconf != 0) *tl = newnfs_true; else *tl = newnfs_false; @@ -2899,6 +2936,14 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp, *tl = 0; retnum += 2 * NFSX_UNSIGNED; break; + case NFSATTRBIT_HIDDEN: + NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED); + if ((vap->va_flags & UF_HIDDEN) != 0) + *tl = newnfs_true; + else + *tl = newnfs_false; + retnum += NFSX_UNSIGNED; + break; case NFSATTRBIT_HOMOGENEOUS: NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); if (fsinf.fs_properties & NFSV3FSINFO_HOMOGENEOUS) @@ -3088,6 +3133,14 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp, txdr_hyper(vap->va_bytes, tl); retnum += NFSX_HYPER; break; + case NFSATTRBIT_SYSTEM: + NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED); + if ((vap->va_flags & UF_SYSTEM) != 0) + *tl = newnfs_true; + else + *tl = newnfs_false; + retnum += NFSX_UNSIGNED; + break; case NFSATTRBIT_TIMEACCESS: NFSM_BUILD(tl, u_int32_t *, NFSX_V4TIME); txdr_nfsv4time(&vap->va_atime, tl); diff --git a/sys/fs/nfs/nfsproto.h b/sys/fs/nfs/nfsproto.h index eff53e1a384e..cb5a80e8df73 100644 --- a/sys/fs/nfs/nfsproto.h +++ b/sys/fs/nfs/nfsproto.h @@ -1142,6 +1142,7 @@ struct nfsv3_sattr { NFSATTRBM_FILESFREE | \ NFSATTRBM_FILESTOTAL | \ NFSATTRBM_FSLOCATIONS | \ + NFSATTRBM_HIDDEN | \ NFSATTRBM_HOMOGENEOUS | \ NFSATTRBM_MAXFILESIZE | \ NFSATTRBM_MAXLINK | \ @@ -1163,6 +1164,7 @@ struct nfsv3_sattr { NFSATTRBM_SPACEFREE | \ NFSATTRBM_SPACETOTAL | \ NFSATTRBM_SPACEUSED | \ + NFSATTRBM_SYSTEM | \ NFSATTRBM_TIMEACCESS | \ NFSATTRBM_TIMECREATE | \ NFSATTRBM_TIMEDELTA | \ @@ -1210,11 +1212,13 @@ struct nfsv3_sattr { */ #define NFSATTRBIT_SETABLE0 \ (NFSATTRBM_SIZE | \ + NFSATTRBM_HIDDEN | \ NFSATTRBM_ACL) #define NFSATTRBIT_SETABLE1 \ (NFSATTRBM_MODE | \ NFSATTRBM_OWNER | \ NFSATTRBM_OWNERGROUP | \ + NFSATTRBM_SYSTEM | \ NFSATTRBM_TIMECREATE | \ NFSATTRBM_TIMEACCESSSET | \ NFSATTRBM_TIMEMODIFYSET) @@ -1254,6 +1258,7 @@ struct nfsv3_sattr { NFSATTRBM_SIZE | \ NFSATTRBM_FSID | \ NFSATTRBM_FILEID | \ + NFSATTRBM_HIDDEN | \ NFSATTRBM_MAXREAD) /* @@ -1266,6 +1271,7 @@ struct nfsv3_sattr { NFSATTRBM_OWNERGROUP | \ NFSATTRBM_RAWDEV | \ NFSATTRBM_SPACEUSED | \ + NFSATTRBM_SYSTEM | \ NFSATTRBM_TIMEACCESS | \ NFSATTRBM_TIMECREATE | \ NFSATTRBM_TIMEMETADATA | \ @@ -1288,6 +1294,7 @@ struct nfsv3_sattr { NFSATTRBM_SIZE | \ NFSATTRBM_FSID | \ NFSATTRBM_FILEID | \ + NFSATTRBM_HIDDEN | \ NFSATTRBM_MAXREAD) /* @@ -1298,6 +1305,7 @@ struct nfsv3_sattr { NFSATTRBM_NUMLINKS | \ NFSATTRBM_RAWDEV | \ NFSATTRBM_SPACEUSED | \ + NFSATTRBM_SYSTEM | \ NFSATTRBM_TIMEACCESS | \ NFSATTRBM_TIMECREATE | \ NFSATTRBM_TIMEMETADATA | \ diff --git a/sys/fs/nfsclient/nfs_clrpcops.c b/sys/fs/nfsclient/nfs_clrpcops.c index c07da6f9275f..e0e66baca44d 100644 --- a/sys/fs/nfsclient/nfs_clrpcops.c +++ b/sys/fs/nfsclient/nfs_clrpcops.c @@ -4158,6 +4158,13 @@ nfsrpc_readdirplus(vnode_t vp, struct uio *uiop, nfsuint64 *cookiep, if (!NFSISSET_ATTRBIT(&dnp->n_vattr.na_suppattr, NFSATTRBIT_TIMECREATE)) NFSCLRBIT_ATTRBIT(&attrbits, NFSATTRBIT_TIMECREATE); + if (!NFSISSET_ATTRBIT(&dnp->n_vattr.na_suppattr, + NFSATTRBIT_HIDDEN) || + !NFSISSET_ATTRBIT(&dnp->n_vattr.na_suppattr, + NFSATTRBIT_SYSTEM)) { + NFSCLRBIT_ATTRBIT(&attrbits, NFSATTRBIT_HIDDEN); + NFSCLRBIT_ATTRBIT(&attrbits, NFSATTRBIT_SYSTEM); + } } /* diff --git a/sys/fs/nfsclient/nfs_clvnops.c b/sys/fs/nfsclient/nfs_clvnops.c index 0049d7edca33..fbfcdafaa06b 100644 --- a/sys/fs/nfsclient/nfs_clvnops.c +++ b/sys/fs/nfsclient/nfs_clvnops.c @@ -1074,15 +1074,23 @@ nfs_setattr(struct vop_setattr_args *ap) int error = 0; u_quad_t tsize; struct timespec ts; + struct nfsmount *nmp; #ifndef nolint tsize = (u_quad_t)0; #endif /* - * Setting of flags and marking of atimes are not supported. + * Only setting of UF_HIDDEN and UF_SYSTEM are supported and + * only for NFSv4 servers that support them. */ - if (vap->va_flags != VNOVAL) + nmp = VFSTONFS(vp->v_mount); + if (vap->va_flags != VNOVAL && (!NFSHASNFSV4(nmp) || + (vap->va_flags & ~(UF_HIDDEN | UF_SYSTEM)) != 0 || + ((vap->va_flags & UF_HIDDEN) != 0 && + !NFSISSET_ATTRBIT(&np->n_vattr.na_suppattr, NFSATTRBIT_HIDDEN)) || + ((vap->va_flags & UF_SYSTEM) != 0 && + !NFSISSET_ATTRBIT(&np->n_vattr.na_suppattr, NFSATTRBIT_SYSTEM)))) return (EOPNOTSUPP); /* @@ -1092,7 +1100,8 @@ nfs_setattr(struct vop_setattr_args *ap) vap->va_gid != (gid_t)VNOVAL || vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL || vap->va_birthtime.tv_sec != VNOVAL || - vap->va_mode != (mode_t)VNOVAL) && + vap->va_mode != (mode_t)VNOVAL || + vap->va_flags != (u_long)VNOVAL) && (vp->v_mount->mnt_flag & MNT_RDONLY)) return (EROFS); if (vap->va_size != VNOVAL) { @@ -4754,6 +4763,15 @@ nfs_pathconf(struct vop_pathconf_args *ap) else *ap->a_retval = 0; break; + case _PC_HAS_HIDDENSYSTEM: + if (NFS_ISV4(vp) && NFSISSET_ATTRBIT(&np->n_vattr.na_suppattr, + NFSATTRBIT_HIDDEN) && + NFSISSET_ATTRBIT(&np->n_vattr.na_suppattr, + NFSATTRBIT_SYSTEM)) + *ap->a_retval = 1; + else + *ap->a_retval = 0; + break; default: error = vop_stdpathconf(ap); diff --git a/sys/fs/nfsserver/nfs_nfsdport.c b/sys/fs/nfsserver/nfs_nfsdport.c index 3bf54d82b959..a81f1492ef95 100644 --- a/sys/fs/nfsserver/nfs_nfsdport.c +++ b/sys/fs/nfsserver/nfs_nfsdport.c @@ -449,6 +449,7 @@ nfsvno_getattr(struct vnode *vp, struct nfsvattr *nvap, } nvap->na_bsdflags = 0; + nvap->na_flags = 0; error = VOP_GETATTR(vp, &nvap->na_vattr, nd->nd_cred); if (lockedit != 0) NFSVOPUNLOCK(vp); @@ -3127,6 +3128,9 @@ nfsv4_sattr(struct nfsrv_descript *nd, vnode_t vp, struct nfsvattr *nvap, bitpos = NFSATTRBIT_MAX; } else { bitpos = 0; + if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_HIDDEN) || + NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_SYSTEM)) + nvap->na_flags = 0; } moderet = 0; for (; bitpos < NFSATTRBIT_MAX; bitpos++) { @@ -3163,9 +3167,11 @@ nfsv4_sattr(struct nfsrv_descript *nd, vnode_t vp, struct nfsvattr *nvap, attrsum += NFSX_UNSIGNED; break; case NFSATTRBIT_HIDDEN: - NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED); - if (!nd->nd_repstat) - nd->nd_repstat = NFSERR_ATTRNOTSUPP; + NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED); + if (nd->nd_repstat == 0) { + if (*tl == newnfs_true) + nvap->na_flags |= UF_HIDDEN; + } attrsum += NFSX_UNSIGNED; break; case NFSATTRBIT_MIMETYPE: @@ -3240,9 +3246,11 @@ nfsv4_sattr(struct nfsrv_descript *nd, vnode_t vp, struct nfsvattr *nvap, attrsum += (NFSX_UNSIGNED + NFSM_RNDUP(j)); break; case NFSATTRBIT_SYSTEM: - NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED); - if (!nd->nd_repstat) - nd->nd_repstat = NFSERR_ATTRNOTSUPP; + NFSM_DISSECT(tl, uint32_t *, NFSX_UNSIGNED); + if (nd->nd_repstat == 0) { + if (*tl == newnfs_true) + nvap->na_flags |= UF_SYSTEM; + } attrsum += NFSX_UNSIGNED; break; case NFSATTRBIT_TIMEACCESSSET: diff --git a/sys/fs/nfsserver/nfs_nfsdserv.c b/sys/fs/nfsserver/nfs_nfsdserv.c index 4e15d55eb312..f7564ade401b 100644 --- a/sys/fs/nfsserver/nfs_nfsdserv.c +++ b/sys/fs/nfsserver/nfs_nfsdserv.c @@ -403,8 +403,10 @@ nfsrvd_setattr(struct nfsrv_descript *nd, __unused int isdgram, if (error) goto nfsmout; - /* For NFSv4, only va_uid is used from nva2. */ + /* For NFSv4, only va_uid and va_flags is used from nva2. */ NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_OWNER); + NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_HIDDEN); + NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_SYSTEM); preat_ret = nfsvno_getattr(vp, &nva2, nd, p, 1, &retbits); if (!nd->nd_repstat) nd->nd_repstat = preat_ret; @@ -463,6 +465,9 @@ nfsrvd_setattr(struct nfsrv_descript *nd, __unused int isdgram, &nva, &attrbits, exp, p); if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4)) { + u_long oldflags; + + oldflags = nva2.na_flags; /* * For V4, try setting the attributes in sets, so that the * reply bitmap will be correct for an error case. @@ -532,6 +537,32 @@ nfsrvd_setattr(struct nfsrv_descript *nd, __unused int isdgram, NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_MODESETMASKED); } } + if (!nd->nd_repstat && + (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_HIDDEN) || + NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_SYSTEM))) { + if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_HIDDEN)) { + if ((nva.na_flags & UF_HIDDEN) != 0) + oldflags |= UF_HIDDEN; + else + oldflags &= ~UF_HIDDEN; + } + if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_SYSTEM)) { + if ((nva.na_flags & UF_SYSTEM) != 0) + oldflags |= UF_SYSTEM; + else + oldflags &= ~UF_SYSTEM; + } + NFSVNO_ATTRINIT(&nva2); + NFSVNO_SETATTRVAL(&nva2, flags, oldflags); + nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p, + exp); + if (!nd->nd_repstat) { + if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_HIDDEN)) + NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_HIDDEN); + if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_SYSTEM)) + NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_SYSTEM); + } + } #ifdef NFS4_ACL_EXTATTR_NAME if (!nd->nd_repstat && aclp->acl_cnt > 0 && diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c index 34e71a0665ed..91792430d24c 100644 --- a/sys/kern/init_sysent.c +++ b/sys/kern/init_sysent.c @@ -658,7 +658,7 @@ struct sysent sysent[] = { { .sy_narg = AS(getrlimitusage_args), .sy_call = (sy_call_t *)sys_getrlimitusage, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 589 = getrlimitusage */ { .sy_narg = AS(fchroot_args), .sy_call = (sy_call_t *)sys_fchroot, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 590 = fchroot */ { .sy_narg = AS(setcred_args), .sy_call = (sy_call_t *)sys_setcred, .sy_auevent = AUE_SETCRED, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 591 = setcred */ - { .sy_narg = AS(exterrctl_args), .sy_call = (sy_call_t *)sys_exterrctl, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 592 = exterrctl */ + { .sy_narg = AS(exterrctl_args), .sy_call = (sy_call_t *)sys_exterrctl, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 592 = exterrctl */ { .sy_narg = AS(inotify_add_watch_at_args), .sy_call = (sy_call_t *)sys_inotify_add_watch_at, .sy_auevent = AUE_INOTIFY, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 593 = inotify_add_watch_at */ { .sy_narg = AS(inotify_rm_watch_args), .sy_call = (sy_call_t *)sys_inotify_rm_watch, .sy_auevent = AUE_INOTIFY, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 594 = inotify_rm_watch */ }; diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c index ac4b6ac3f457..406236fc2723 100644 --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -38,9 +38,11 @@ #include "opt_ddb.h" #include "opt_ktrace.h" +#define EXTERR_CATEGORY EXTERR_CAT_FILEDESC #include <sys/systm.h> #include <sys/capsicum.h> #include <sys/conf.h> +#include <sys/exterrvar.h> #include <sys/fcntl.h> #include <sys/file.h> #include <sys/filedesc.h> @@ -492,6 +494,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 +514,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); @@ -528,6 +536,7 @@ kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg) if (fde != NULL) { td->td_retval[0] = ((fde->fde_flags & UF_EXCLOSE) ? FD_CLOEXEC : 0) | + ((fde->fde_flags & UF_FOCLOSE) ? FD_CLOFORK : 0) | ((fde->fde_flags & UF_RESOLVE_BENEATH) ? FD_RESOLVE_BENEATH : 0); error = 0; @@ -545,6 +554,7 @@ kern_fcntl(struct thread *td, int fd, int cmd, intptr_t arg) */ fde->fde_flags = (fde->fde_flags & ~UF_EXCLOSE) | ((arg & FD_CLOEXEC) != 0 ? UF_EXCLOSE : 0) | + ((arg & FD_CLOFORK) != 0 ? UF_FOCLOSE : 0) | ((arg & FD_RESOLVE_BENEATH) != 0 ? UF_RESOLVE_BENEATH : 0); error = 0; @@ -916,7 +926,17 @@ revert_f_setfl: break; default: - error = EINVAL; + if ((cmd & ((1u << F_DUP3FD_SHIFT) - 1)) != F_DUP3FD) + return (EXTERROR(EINVAL, "invalid fcntl cmd")); + /* Handle F_DUP3FD */ + flags = (cmd >> F_DUP3FD_SHIFT); + if ((flags & ~(FD_CLOEXEC | FD_CLOFORK)) != 0) + return (EXTERROR(EINVAL, "invalid flags for F_DUP3FD")); + 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); @@ -946,7 +966,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); @@ -971,8 +991,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; } @@ -1047,10 +1069,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 @@ -1416,13 +1437,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); @@ -1434,7 +1457,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); @@ -1492,8 +1515,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)); } @@ -1513,7 +1536,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)); } @@ -2172,6 +2195,7 @@ _finstall(struct filedesc *fdp, struct file *fp, int fd, int flags, #endif fde->fde_file = fp; fde->fde_flags = ((flags & O_CLOEXEC) != 0 ? UF_EXCLOSE : 0) | + ((flags & O_CLOFORK) != 0 ? UF_FOCLOSE : 0) | ((flags & O_RESOLVE_BENEATH) != 0 ? UF_RESOLVE_BENEATH : 0); if (fcaps != NULL) filecaps_move(fcaps, &fde->fde_caps); @@ -2432,6 +2456,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; @@ -2729,6 +2754,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 9340779918a2..ed651da96b14 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 ad8485028987..133724ac76c5 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 72bd0246db11..0056dac65c7d 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -3463,7 +3463,8 @@ unp_externalize(struct mbuf *control, struct mbuf **controlp, int flags) UNP_LINK_UNLOCK_ASSERT(); - fdflags = (flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0; + fdflags = ((flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0) | + ((flags & MSG_CMSG_CLOFORK) ? O_CLOFORK : 0); error = 0; if (controlp != NULL) /* controlp == NULL => free control messages */ diff --git a/sys/sys/fcntl.h b/sys/sys/fcntl.h index dd9fccf5cf38..18d3928e91c7 100644 --- a/sys/sys/fcntl.h +++ b/sys/sys/fcntl.h @@ -144,6 +144,10 @@ typedef __pid_t pid_t; #define O_XATTR O_NAMEDATTR /* Solaris compatibility */ #endif +#if __POSIX_VISIBLE >= 202405 +#define O_CLOFORK 0x08000000 +#endif + /* * !!! DANGER !!! * @@ -280,6 +284,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 */ @@ -292,6 +306,9 @@ typedef __pid_t pid_t; #define FD_CLOEXEC 1 /* close-on-exec flag */ #define FD_RESOLVE_BENEATH 2 /* all lookups relative to fd have O_RESOLVE_BENEATH semantics */ +#if __POSIX_VISIBLE >= 202405 +#define FD_CLOFORK 4 /* 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 55969b2ff4b3..0a388c90de26 100644 --- a/sys/sys/filedesc.h +++ b/sys/sys/filedesc.h @@ -149,6 +149,7 @@ struct filedesc_to_leader { */ #define UF_EXCLOSE 0x01 /* auto-close on exec */ #define UF_RESOLVE_BENEATH 0x02 /* lookups must be beneath this dir */ +#define UF_FOCLOSE 0x04 /* auto-close on fork */ #ifdef _KERNEL @@ -221,6 +222,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 5e7c554c34cf..cdd4fa3b4b89 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 c12343e5d0fd..c291c1dc2b95 100644 --- a/sys/sys/unistd.h +++ b/sys/sys/unistd.h @@ -211,6 +211,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 e8dd7793f169..451d55498a26 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,3 +1,5 @@ +.include <src.opts.mk> + 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 000000000000..350c9f857c85 --- /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 <bsd.test.mk> diff --git a/tests/sys/file/closefrom_test.c b/tests/sys/file/closefrom_test.c index e30c5eb3d591..7dccf858c772 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 b024e72d0d1a..455115eda8c8 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 <sys/types.h> @@ -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 95271c04a16b..7dc4541ad402 100644 --- a/tests/sys/kern/unix_passfd_test.c +++ b/tests/sys/kern/unix_passfd_test.c @@ -380,6 +380,30 @@ ATF_TC_BODY(simple_send_fd_msg_cmsg_cloexec, tc) } /* + * 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. */ @@ -1170,6 +1194,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); diff --git a/usr.sbin/rip6query/rip6query.8 b/usr.sbin/rip6query/rip6query.8 index 856a59138bc1..92e49f5ade58 100644 --- a/usr.sbin/rip6query/rip6query.8 +++ b/usr.sbin/rip6query/rip6query.8 @@ -29,13 +29,19 @@ .\" .\" $Id: rip6query.8,v 1.2 2000/01/19 06:24:55 itojun Exp $ .\" -.Dd October 7, 1999 +.Dd May 20, 2025 .Dt RIP6QUERY 8 .Os .Sh NAME .Nm rip6query .Nd RIPng debugging tool .\" +.Sh DEPRECATION NOTICE +The +.Nm +utility is deprecated and will be removed in +.Fx 16.0 . +.\" .Sh SYNOPSIS .Nm .Op Fl I Ar interface diff --git a/usr.sbin/route6d/route6d.8 b/usr.sbin/route6d/route6d.8 index 3a7bc8721923..e9ad3266ba26 100644 --- a/usr.sbin/route6d/route6d.8 +++ b/usr.sbin/route6d/route6d.8 @@ -14,12 +14,17 @@ .\" LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR .\" A PARTICULAR PURPOSE. .\" -.Dd November 18, 2012 +.Dd May 20, 2025 .Dt ROUTE6D 8 .Os .Sh NAME .Nm route6d .Nd RIP6 Routing Daemon +.Sh DEPRECATION NOTICE +The +.Nm +utility is deprecated and will be removed in +.Fx 16.0 . .Sh SYNOPSIS .Nm .Op Fl adDhlnqsS |