aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2021-04-15 13:40:57 +0000
committerMark Johnston <markj@FreeBSD.org>2021-04-15 13:40:57 +0000
commit3a248c84419d4f7d6617d30744ab56d0b456fe03 (patch)
tree84d22e7c8b1274e6a335934c8ee85b777064c9eb
parente3d675958539eee899d42438f5b46a26f3c64902 (diff)
downloadsrc-3a248c84419d4f7d6617d30744ab56d0b456fe03.tar.gz
src-3a248c84419d4f7d6617d30744ab56d0b456fe03.zip
Add some regression tests for O_PATH and AT_EMPTY_PATH
Reviewed by: kib MFC after: 2 weeks Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D29719
-rw-r--r--tests/sys/file/Makefile1
-rw-r--r--tests/sys/file/path_test.c778
2 files changed, 779 insertions, 0 deletions
diff --git a/tests/sys/file/Makefile b/tests/sys/file/Makefile
index ed2d2c4fbe2e..46a6a9544c62 100644
--- a/tests/sys/file/Makefile
+++ b/tests/sys/file/Makefile
@@ -4,6 +4,7 @@ TESTSDIR= ${TESTSBASE}/sys/file
BINDIR= ${TESTSDIR}
+ATF_TESTS_C+= path_test
TAP_TESTS_C+= closefrom_test
TAP_TESTS_C+= dup_test
TAP_TESTS_C+= fcntlflags_test
diff --git a/tests/sys/file/path_test.c b/tests/sys/file/path_test.c
new file mode 100644
index 000000000000..a8354f88b091
--- /dev/null
+++ b/tests/sys/file/path_test.c
@@ -0,0 +1,778 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 The FreeBSD Foundation
+ *
+ * This software was developed by Mark Johnston under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Basic regression tests for handling of O_PATH descriptors.
+ */
+
+#include <sys/param.h>
+#include <sys/capsicum.h>
+#include <sys/event.h>
+#include <sys/ioctl.h>
+#include <sys/memrange.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <aio.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define FMT_ERR(s) s ": %s", strerror(errno)
+
+#define CHECKED_CLOSE(fd) \
+ ATF_REQUIRE_MSG(close(fd) == 0, FMT_ERR("close"))
+
+/* Create a temporary regular file containing some data. */
+static void
+mktfile(char path[PATH_MAX], const char *template)
+{
+ char buf[BUFSIZ];
+ int fd;
+
+ snprintf(path, PATH_MAX, "%s", template);
+ fd = mkstemp(path);
+ ATF_REQUIRE_MSG(fd >= 0, FMT_ERR("mkstemp"));
+ memset(buf, 0, sizeof(buf));
+ ATF_REQUIRE_MSG(write(fd, buf, sizeof(buf)) == sizeof(buf),
+ FMT_ERR("write"));
+ CHECKED_CLOSE(fd);
+}
+
+/* Make a temporary directory. */
+static void
+mktdir(char path[PATH_MAX], const char *template)
+{
+ snprintf(path, PATH_MAX, "%s", template);
+ ATF_REQUIRE_MSG(mkdtemp(path) == path, FMT_ERR("mkdtemp"));
+}
+
+/* Wait for a child process to exit with status 0. */
+static void
+waitchild(pid_t child, int exstatus)
+{
+ int error, status;
+
+ error = waitpid(child, &status, 0);
+ ATF_REQUIRE_MSG(error != -1, FMT_ERR("waitpid"));
+ ATF_REQUIRE_MSG(WIFEXITED(status), "child exited abnormally, status %d",
+ status);
+ ATF_REQUIRE_MSG(WEXITSTATUS(status) == exstatus,
+ "child exit status is %d, expected %d",
+ WEXITSTATUS(status), exstatus);
+}
+
+ATF_TC_WITHOUT_HEAD(path_access);
+ATF_TC_BODY(path_access, tc)
+{
+ char path[PATH_MAX];
+ struct stat sb;
+ struct timespec ts[2];
+ struct timeval tv[2];
+ int pathfd;
+
+ mktfile(path, "path_access.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ ATF_REQUIRE_ERRNO(EBADF, fchmod(pathfd, 0666) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, fchown(pathfd, getuid(), getgid()) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, fchflags(pathfd, UF_NODUMP) == -1);
+ memset(tv, 0, sizeof(tv));
+ ATF_REQUIRE_ERRNO(EBADF, futimes(pathfd, tv) == -1);
+ memset(ts, 0, sizeof(ts));
+ ATF_REQUIRE_ERRNO(EBADF, futimens(pathfd, ts) == -1);
+
+ /* fpathconf(2) and fstat(2) are permitted. */
+ ATF_REQUIRE_MSG(fstat(pathfd, &sb) == 0, FMT_ERR("fstat"));
+ ATF_REQUIRE_MSG(fpathconf(pathfd, _PC_LINK_MAX) != -1,
+ FMT_ERR("fpathconf"));
+
+ CHECKED_CLOSE(pathfd);
+}
+
+/* Basic tests to verify that AIO operations fail. */
+ATF_TC_WITHOUT_HEAD(path_aio);
+ATF_TC_BODY(path_aio, tc)
+{
+ struct aiocb aio;
+ char buf[BUFSIZ], path[PATH_MAX];
+ int pathfd;
+
+ mktfile(path, "path_aio.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ memset(&aio, 0, sizeof(aio));
+ aio.aio_buf = buf;
+ aio.aio_nbytes = sizeof(buf);
+ aio.aio_fildes = pathfd;
+ aio.aio_offset = 0;
+
+ ATF_REQUIRE_ERRNO(EBADF, aio_read(&aio) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, aio_write(&aio) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, aio_fsync(O_SYNC, &aio) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, aio_fsync(O_DSYNC, &aio) == -1);
+
+ CHECKED_CLOSE(pathfd);
+}
+
+/* Basic tests to verify that Capsicum restrictions apply to path fds. */
+ATF_TC_WITHOUT_HEAD(path_capsicum);
+ATF_TC_BODY(path_capsicum, tc)
+{
+ char path[PATH_MAX];
+ cap_rights_t rights;
+ int truefd;
+ pid_t child;
+
+ mktfile(path, "path_capsicum.XXXXXX");
+
+ /* Make sure that filesystem namespace restrictions apply to O_PATH. */
+ child = fork();
+ ATF_REQUIRE_MSG(child != -1, FMT_ERR("fork"));
+ if (child == 0) {
+ if (cap_enter() != 0)
+ _exit(1);
+ if (open(path, O_PATH) >= 0)
+ _exit(2);
+ if (errno != ECAPMODE)
+ _exit(3);
+ if (open("/usr/bin/true", O_PATH | O_EXEC) >= 0)
+ _exit(4);
+ if (errno != ECAPMODE)
+ _exit(5);
+ _exit(0);
+ }
+ waitchild(child, 0);
+
+ /* Make sure that CAP_FEXECVE is required. */
+ child = fork();
+ ATF_REQUIRE_MSG(child != -1, FMT_ERR("fork"));
+ if (child == 0) {
+ truefd = open("/usr/bin/true", O_PATH | O_EXEC);
+ if (truefd < 0)
+ _exit(1);
+ cap_rights_init(&rights);
+ if (cap_rights_limit(truefd, &rights) != 0)
+ _exit(2);
+ (void)fexecve(truefd,
+ (char * const[]){__DECONST(char *, "/usr/bin/true"), NULL},
+ NULL);
+ if (errno != ENOTCAPABLE)
+ _exit(3);
+ _exit(4);
+ }
+ waitchild(child, 4);
+}
+
+/* Verify operations on directory path descriptors. */
+ATF_TC_WITHOUT_HEAD(path_directory);
+ATF_TC_BODY(path_directory, tc)
+{
+ struct dirent de;
+ struct stat sb;
+ char path[PATH_MAX];
+ int fd, pathfd;
+
+ mktdir(path, "path_directory.XXXXXX");
+
+ pathfd = open(path, O_PATH | O_DIRECTORY);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ /* Should not be possible to list directory entries. */
+ ATF_REQUIRE_ERRNO(EBADF,
+ getdirentries(pathfd, (char *)&de, sizeof(de), NULL) == -1);
+
+ /* It should be possible to create files under pathfd. */
+ fd = openat(pathfd, "test", O_RDWR | O_CREAT, 0600);
+ ATF_REQUIRE_MSG(fd >= 0, FMT_ERR("open"));
+ ATF_REQUIRE_MSG(fstatat(pathfd, "test", &sb, 0) == 0,
+ FMT_ERR("fstatat"));
+ CHECKED_CLOSE(fd);
+
+ /* ... but doing so requires write access. */
+ if (geteuid() != 0) {
+ ATF_REQUIRE_ERRNO(EBADF, fchmod(pathfd, 0500) == -1);
+ ATF_REQUIRE_MSG(chmod(path, 0500) == 0, FMT_ERR("chmod"));
+ ATF_REQUIRE_ERRNO(EACCES,
+ openat(pathfd, "test2", O_RDWR | O_CREAT, 0600) < 0);
+ }
+
+ /* fchdir(2) is permitted. */
+ ATF_REQUIRE_MSG(fchdir(pathfd) == 0, FMT_ERR("fchdir"));
+
+ CHECKED_CLOSE(pathfd);
+}
+
+/* Verify access permission checking for a directory path fd. */
+ATF_TC_WITH_CLEANUP(path_directory_not_root);
+ATF_TC_HEAD(path_directory_not_root, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(path_directory_not_root, tc)
+{
+ char path[PATH_MAX];
+ int pathfd;
+
+ mktdir(path, "path_directory.XXXXXX");
+
+ pathfd = open(path, O_PATH | O_DIRECTORY);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ ATF_REQUIRE_ERRNO(EBADF, fchmod(pathfd, 0500) == -1);
+ ATF_REQUIRE_MSG(chmod(path, 0500) == 0, FMT_ERR("chmod"));
+ ATF_REQUIRE_ERRNO(EACCES,
+ openat(pathfd, "test2", O_RDWR | O_CREAT, 0600) < 0);
+
+ CHECKED_CLOSE(pathfd);
+}
+ATF_TC_CLEANUP(path_directory_not_root, tc)
+{
+}
+
+/* Validate system calls that handle AT_EMPTY_PATH. */
+ATF_TC_WITHOUT_HEAD(path_empty);
+ATF_TC_BODY(path_empty, tc)
+{
+ char path[PATH_MAX];
+ struct timespec ts[2];
+ struct stat sb;
+ int pathfd;
+
+ mktfile(path, "path_empty.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ /* Various *at operations should work on path fds. */
+ ATF_REQUIRE_MSG(faccessat(pathfd, "", F_OK, AT_EMPTY_PATH) == 0,
+ FMT_ERR("faccessat"));
+ ATF_REQUIRE_MSG(chflagsat(pathfd, "", UF_NODUMP, AT_EMPTY_PATH) == 0,
+ FMT_ERR("chflagsat"));
+ ATF_REQUIRE_MSG(fchmodat(pathfd, "", 0600, AT_EMPTY_PATH) == 0,
+ FMT_ERR("fchmodat"));
+ ATF_REQUIRE_MSG(fchownat(pathfd, "", getuid(), getgid(),
+ AT_EMPTY_PATH) == 0, FMT_ERR("fchownat"));
+ ATF_REQUIRE_MSG(fstatat(pathfd, "", &sb, AT_EMPTY_PATH) == 0,
+ FMT_ERR("fstatat"));
+ ATF_REQUIRE_MSG(sb.st_size == BUFSIZ,
+ "unexpected size %ju", (uintmax_t)sb.st_size);
+ memset(ts, 0, sizeof(ts));
+ ATF_REQUIRE_MSG(utimensat(pathfd, "", ts, AT_EMPTY_PATH) == 0,
+ FMT_ERR("utimensat"));
+
+ CHECKED_CLOSE(pathfd);
+}
+
+/* Verify that various operations on a path fd have access checks. */
+ATF_TC_WITH_CLEANUP(path_empty_not_root);
+ATF_TC_HEAD(path_empty_not_root, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(path_empty_not_root, tc)
+{
+ int pathfd;
+
+ pathfd = open("/dev/null", O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ ATF_REQUIRE_ERRNO(EPERM,
+ chflagsat(pathfd, "", UF_NODUMP, AT_EMPTY_PATH) == -1);
+ ATF_REQUIRE_ERRNO(EPERM,
+ fchownat(pathfd, "", getuid(), getgid(), AT_EMPTY_PATH) == -1);
+ ATF_REQUIRE_ERRNO(EPERM,
+ fchmodat(pathfd, "", 0600, AT_EMPTY_PATH) == -1);
+ ATF_REQUIRE_ERRNO(EPERM,
+ linkat(pathfd, "", AT_FDCWD, "test", AT_EMPTY_PATH) == -1);
+
+ CHECKED_CLOSE(pathfd);
+}
+ATF_TC_CLEANUP(path_empty_not_root, tc)
+{
+}
+
+/* Test linkat(2) with AT_EMPTY_PATH, which requires privileges. */
+ATF_TC_WITH_CLEANUP(path_empty_root);
+ATF_TC_HEAD(path_empty_root, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(path_empty_root, tc)
+{
+ char path[PATH_MAX];
+ struct stat sb, sb2;
+ int pathfd;
+
+ mktfile(path, "path_empty_root.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+ ATF_REQUIRE_MSG(fstatat(pathfd, "", &sb, AT_EMPTY_PATH) == 0,
+ FMT_ERR("fstatat"));
+
+ ATF_REQUIRE_MSG(linkat(pathfd, "", AT_FDCWD, "test", AT_EMPTY_PATH) ==
+ 0, FMT_ERR("linkat"));
+ ATF_REQUIRE_MSG(fstatat(AT_FDCWD, "test", &sb2, 0) == 0,
+ FMT_ERR("fstatat"));
+ ATF_REQUIRE_MSG(sb.st_dev == sb2.st_dev, "st_dev mismatch");
+ ATF_REQUIRE_MSG(sb.st_ino == sb2.st_ino, "st_ino mismatch");
+
+ CHECKED_CLOSE(pathfd);
+
+}
+ATF_TC_CLEANUP(path_empty_root, tc)
+{
+}
+
+/* poll(2) never returns an event for path fds, but kevent(2) does. */
+ATF_TC_WITHOUT_HEAD(path_event);
+ATF_TC_BODY(path_event, tc)
+{
+ char buf[BUFSIZ], path[PATH_MAX];
+ struct kevent ev;
+ struct pollfd pollfd;
+ int kq, pathfd;
+
+ mktfile(path, "path_event.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ /* poll(2) should return POLLNVAL. */
+ pollfd.fd = pathfd;
+ pollfd.events = POLLIN;
+ pollfd.revents = 0;
+ ATF_REQUIRE_MSG(poll(&pollfd, 1, 0) == 1, FMT_ERR("poll"));
+ ATF_REQUIRE_MSG(pollfd.revents == POLLNVAL, "unexpected revents %x",
+ pollfd.revents);
+ pollfd.events = POLLOUT;
+ pollfd.revents = 0;
+ ATF_REQUIRE_MSG(poll(&pollfd, 1, 0) == 1, FMT_ERR("poll"));
+ ATF_REQUIRE_MSG(pollfd.revents == POLLNVAL, "unexpected revents %x",
+ pollfd.revents);
+
+ /* Try to get a EVFILT_READ event through a path fd. */
+ kq = kqueue();
+ ATF_REQUIRE_MSG(kq >= 0, FMT_ERR("kqueue"));
+ EV_SET(&ev, pathfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
+ ATF_REQUIRE_MSG(kevent(kq, &ev, 1, NULL, 0, NULL) == 0,
+ FMT_ERR("kevent"));
+ ATF_REQUIRE_MSG(kevent(kq, NULL, 0, &ev, 1, NULL) == 1,
+ FMT_ERR("kevent"));
+ ATF_REQUIRE_MSG((ev.flags & EV_ERROR) == 0, "EV_ERROR is set");
+ ATF_REQUIRE_MSG(ev.data == sizeof(buf),
+ "data is %jd", (intmax_t)ev.data);
+ EV_SET(&ev, pathfd, EVFILT_READ, EV_DELETE, 0, 0, 0);
+ ATF_REQUIRE_MSG(kevent(kq, &ev, 1, NULL, 0, NULL) == 0,
+ FMT_ERR("kevent"));
+
+ /* Try to get a EVFILT_VNODE/NOTE_LINK event through a path fd. */
+ EV_SET(&ev, pathfd, EVFILT_VNODE, EV_ADD | EV_ENABLE, NOTE_LINK, 0, 0);
+ ATF_REQUIRE_MSG(kevent(kq, &ev, 1, NULL, 0, NULL) == 0,
+ FMT_ERR("kevent"));
+ ATF_REQUIRE_MSG(funlinkat(AT_FDCWD, path, pathfd, 0) == 0,
+ FMT_ERR("funlinkat"));
+ ATF_REQUIRE_MSG(kevent(kq, NULL, 0, &ev, 1, NULL) == 1,
+ FMT_ERR("kevent"));
+ EV_SET(&ev, pathfd, EVFILT_VNODE, EV_DELETE, 0, 0, 0);
+ ATF_REQUIRE_MSG(kevent(kq, &ev, 1, NULL, 0, NULL) == 0,
+ FMT_ERR("kevent"));
+
+ CHECKED_CLOSE(kq);
+ CHECKED_CLOSE(pathfd);
+}
+
+/* Check various fcntl(2) operations on a path desriptor. */
+ATF_TC_WITHOUT_HEAD(path_fcntl);
+ATF_TC_BODY(path_fcntl, tc)
+{
+ char path[PATH_MAX];
+ int flags, pathfd, pathfd2;
+
+ mktfile(path, "path_fcntl.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ /* O_PATH should appear in the fd flags. */
+ flags = fcntl(pathfd, F_GETFL);
+ ATF_REQUIRE_MSG(flags != -1, FMT_ERR("fcntl"));
+ ATF_REQUIRE_MSG((flags & O_PATH) != 0, "O_PATH not set");
+
+ ATF_REQUIRE_ERRNO(EBADF,
+ fcntl(pathfd, F_SETFL, flags & ~O_PATH));
+ ATF_REQUIRE_ERRNO(EBADF,
+ fcntl(pathfd, F_SETFL, flags | O_APPEND));
+
+ /* A dup'ed O_PATH fd had better have O_PATH set too. */
+ pathfd2 = fcntl(pathfd, F_DUPFD, 0);
+ ATF_REQUIRE_MSG(pathfd2 >= 0, FMT_ERR("fcntl"));
+ flags = fcntl(pathfd2, F_GETFL);
+ ATF_REQUIRE_MSG(flags != -1, FMT_ERR("fcntl"));
+ ATF_REQUIRE_MSG((flags & O_PATH) != 0, "O_PATH not set");
+ CHECKED_CLOSE(pathfd2);
+
+ /* Double check with dup(2). */
+ pathfd2 = dup(pathfd);
+ ATF_REQUIRE_MSG(pathfd2 >= 0, FMT_ERR("dup"));
+ flags = fcntl(pathfd2, F_GETFL);
+ ATF_REQUIRE_MSG(flags != -1, FMT_ERR("fcntl"));
+ ATF_REQUIRE_MSG((flags & O_PATH) != 0, "O_PATH not set");
+ CHECKED_CLOSE(pathfd2);
+
+ /* It should be possible to set O_CLOEXEC. */
+ ATF_REQUIRE_MSG(fcntl(pathfd, F_SETFD, FD_CLOEXEC) == 0,
+ FMT_ERR("fcntl"));
+ ATF_REQUIRE_MSG(fcntl(pathfd, F_GETFD) == FD_CLOEXEC,
+ FMT_ERR("fcntl"));
+
+ CHECKED_CLOSE(pathfd);
+}
+
+/* Verify that we can execute a file opened with O_PATH. */
+ATF_TC_WITHOUT_HEAD(path_fexecve);
+ATF_TC_BODY(path_fexecve, tc)
+{
+ char path[PATH_MAX];
+ pid_t child;
+ int fd, pathfd;
+
+ child = fork();
+ ATF_REQUIRE_MSG(child != -1, FMT_ERR("fork"));
+ if (child == 0) {
+ pathfd = open("/usr/bin/true", O_PATH | O_EXEC);
+ if (pathfd < 0)
+ _exit(1);
+ fexecve(pathfd,
+ (char * const[]){__DECONST(char *, "/usr/bin/true"), NULL},
+ NULL);
+ _exit(2);
+ }
+ waitchild(child, 0);
+
+ /*
+ * Also verify that access permissions are checked when opening with
+ * O_PATH.
+ */
+ snprintf(path, sizeof(path), "path_fexecve.XXXXXX");
+ ATF_REQUIRE_MSG(mktemp(path) == path, FMT_ERR("mktemp"));
+
+ fd = open(path, O_CREAT | O_RDONLY, 0600);
+ ATF_REQUIRE_MSG(fd >= 0, FMT_ERR("open"));
+
+ pathfd = open(path, O_PATH | O_EXEC);
+ ATF_REQUIRE_ERRNO(EACCES, pathfd < 0);
+}
+
+/* Files may be unlinked using a path fd. */
+ATF_TC_WITHOUT_HEAD(path_funlinkat);
+ATF_TC_BODY(path_funlinkat, tc)
+{
+ char path[PATH_MAX];
+ struct stat sb;
+ int pathfd;
+
+ mktfile(path, "path_rights.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ ATF_REQUIRE_MSG(funlinkat(AT_FDCWD, path, pathfd, 0) == 0,
+ FMT_ERR("funlinkat"));
+ ATF_REQUIRE_ERRNO(ENOENT, stat(path, &sb) == -1);
+
+ CHECKED_CLOSE(pathfd);
+}
+
+/* Verify that various I/O operations fail on an O_PATH descriptor. */
+ATF_TC_WITHOUT_HEAD(path_io);
+ATF_TC_BODY(path_io, tc)
+{
+ char path[PATH_MAX], path2[PATH_MAX];
+ char buf[BUFSIZ];
+ struct iovec iov;
+ int error, fd, pathfd, sd[2];
+
+ /* It shouldn't be possible to create new files with O_PATH. */
+ snprintf(path, sizeof(path), "path_io.XXXXXX");
+ ATF_REQUIRE_MSG(mktemp(path) == path, FMT_ERR("mktemp"));
+ ATF_REQUIRE_ERRNO(ENOENT, open(path, O_PATH | O_CREAT, 0600) < 0);
+
+ /* Create a non-empty file for use in the rest of the tests. */
+ mktfile(path, "path_io.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ /* Make sure that basic I/O operations aren't possible. */
+ iov.iov_base = path;
+ iov.iov_len = strlen(path);
+ ATF_REQUIRE_ERRNO(EBADF,
+ write(pathfd, iov.iov_base, iov.iov_len) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ pwrite(pathfd, iov.iov_base, iov.iov_len, 0) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ writev(pathfd, &iov, 1) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ pwritev(pathfd, &iov, 1, 0) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ read(pathfd, path, 1) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ pread(pathfd, path, 1, 0) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ readv(pathfd, &iov, 1) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ preadv(pathfd, &iov, 1, 0) == -1);
+
+ /* copy_file_range() should not be permitted. */
+ mktfile(path2, "path_io.XXXXXX");
+ fd = open(path2, O_RDWR);
+ ATF_REQUIRE_ERRNO(EBADF,
+ copy_file_range(fd, NULL, pathfd, NULL, sizeof(buf), 0) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ copy_file_range(pathfd, NULL, fd, NULL, sizeof(buf), 0) == -1);
+ CHECKED_CLOSE(fd);
+
+ /* sendfile() should not be permitted. */
+ ATF_REQUIRE_MSG(socketpair(PF_LOCAL, SOCK_STREAM, 0, sd) == 0,
+ FMT_ERR("socketpair"));
+ ATF_REQUIRE_ERRNO(EBADF,
+ sendfile(pathfd, sd[0], 0, 0, NULL, NULL, 0));
+ CHECKED_CLOSE(sd[0]);
+ CHECKED_CLOSE(sd[1]);
+
+ /* No seeking. */
+ ATF_REQUIRE_ERRNO(ESPIPE,
+ lseek(pathfd, 0, SEEK_SET) == -1);
+
+ /* No operations on the file extent. */
+ ATF_REQUIRE_ERRNO(EINVAL,
+ ftruncate(pathfd, 0) == -1);
+ error = posix_fallocate(pathfd, 0, sizeof(buf) * 2);
+ ATF_REQUIRE_MSG(error == ESPIPE, "posix_fallocate() returned %d", error);
+ error = posix_fadvise(pathfd, 0, sizeof(buf), POSIX_FADV_NORMAL);
+ ATF_REQUIRE_MSG(error == ESPIPE, "posix_fadvise() returned %d", error);
+
+ /* mmap() is not allowed. */
+ ATF_REQUIRE_ERRNO(ENODEV,
+ mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, pathfd, 0) ==
+ MAP_FAILED);
+ ATF_REQUIRE_ERRNO(ENODEV,
+ mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_SHARED, pathfd, 0) ==
+ MAP_FAILED);
+ ATF_REQUIRE_ERRNO(ENODEV,
+ mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE, pathfd, 0) ==
+ MAP_FAILED);
+
+ /* No fsync() or fdatasync(). */
+ ATF_REQUIRE_ERRNO(EBADF, fsync(pathfd) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, fdatasync(pathfd) == -1);
+
+ CHECKED_CLOSE(pathfd);
+}
+
+/* ioctl(2) is not permitted on path fds. */
+ATF_TC_WITHOUT_HEAD(path_ioctl);
+ATF_TC_BODY(path_ioctl, tc)
+{
+ char path[PATH_MAX];
+ struct mem_extract me;
+ int pathfd, val;
+
+ mktfile(path, "path_ioctl.XXXXXX");
+
+ /* Standard file descriptor ioctls should fail. */
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ val = 0;
+ ATF_REQUIRE_ERRNO(EBADF, ioctl(pathfd, FIONBIO, &val) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, ioctl(pathfd, FIONREAD, &val) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, ioctl(pathfd, FIONWRITE, &val) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, ioctl(pathfd, FIONSPACE, &val) == -1);
+
+ CHECKED_CLOSE(pathfd);
+
+ /* Device ioctls should fail. */
+ pathfd = open("/dev/mem", O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ me.me_vaddr = (uintptr_t)&me;
+ ATF_REQUIRE_ERRNO(EBADF, ioctl(pathfd, MEM_EXTRACT_PADDR, &me) == -1);
+
+ CHECKED_CLOSE(pathfd);
+}
+
+ATF_TC_WITHOUT_HEAD(path_lock);
+ATF_TC_BODY(path_lock, tc)
+{
+ char buf[BUFSIZ], path[PATH_MAX];
+ struct flock flk;
+ int fd, pathfd;
+
+ snprintf(path, sizeof(path), "path_rights.XXXXXX");
+ fd = mkostemp(path, O_SHLOCK);
+ ATF_REQUIRE_MSG(fd >= 0, FMT_ERR("mkostemp"));
+ memset(buf, 0, sizeof(buf));
+ ATF_REQUIRE_MSG(write(fd, buf, sizeof(buf)) == sizeof(buf),
+ FMT_ERR("write()"));
+
+ /* Verify that O_EXLOCK is ignored when combined with O_PATH. */
+ pathfd = open(path, O_PATH | O_EXLOCK);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ CHECKED_CLOSE(fd);
+
+ /* flock(2) is prohibited. */
+ ATF_REQUIRE_ERRNO(EOPNOTSUPP, flock(pathfd, LOCK_SH) == -1);
+ ATF_REQUIRE_ERRNO(EOPNOTSUPP, flock(pathfd, LOCK_EX) == -1);
+ ATF_REQUIRE_ERRNO(EOPNOTSUPP, flock(pathfd, LOCK_SH | LOCK_NB) == -1);
+ ATF_REQUIRE_ERRNO(EOPNOTSUPP, flock(pathfd, LOCK_EX | LOCK_NB) == -1);
+ ATF_REQUIRE_ERRNO(EOPNOTSUPP, flock(pathfd, LOCK_UN) == -1);
+
+ /* fcntl(2) file locks are prohibited. */
+ memset(&flk, 0, sizeof(flk));
+ flk.l_whence = SEEK_CUR;
+ ATF_REQUIRE_ERRNO(EBADF, fcntl(pathfd, F_GETLK, &flk) == -1);
+ flk.l_type = F_RDLCK;
+ ATF_REQUIRE_ERRNO(EBADF, fcntl(pathfd, F_SETLK, &flk) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, fcntl(pathfd, F_SETLKW, &flk) == -1);
+ flk.l_type = F_WRLCK;
+ ATF_REQUIRE_ERRNO(EBADF, fcntl(pathfd, F_SETLK, &flk) == -1);
+ ATF_REQUIRE_ERRNO(EBADF, fcntl(pathfd, F_SETLKW, &flk) == -1);
+
+ CHECKED_CLOSE(pathfd);
+}
+
+/* Verify that we can send an O_PATH descriptor over a unix socket. */
+ATF_TC_WITHOUT_HEAD(path_rights);
+ATF_TC_BODY(path_rights, tc)
+{
+ char path[PATH_MAX];
+ struct cmsghdr *cmsg;
+ struct msghdr msg;
+ struct iovec iov;
+ int flags, pathfd, pathfd_copy, sd[2];
+ char c;
+
+ ATF_REQUIRE_MSG(socketpair(PF_LOCAL, SOCK_STREAM, 0, sd) == 0,
+ FMT_ERR("socketpair"));
+
+ mktfile(path, "path_rights.XXXXXX");
+
+ pathfd = open(path, O_PATH);
+ ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open"));
+
+ /* Package up the O_PATH and send it over the socket pair. */
+ cmsg = malloc(CMSG_SPACE(sizeof(pathfd)));
+ ATF_REQUIRE_MSG(cmsg != NULL, FMT_ERR("malloc"));
+
+ cmsg->cmsg_len = CMSG_LEN(sizeof(pathfd));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ *(int *)(void *)CMSG_DATA(cmsg) = pathfd;
+
+ c = 0;
+ iov.iov_base = &c;
+ iov.iov_len = 1;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg;
+ msg.msg_controllen = CMSG_SPACE(sizeof(pathfd));
+
+ ATF_REQUIRE_MSG(sendmsg(sd[0], &msg, 0) == sizeof(c),
+ FMT_ERR("sendmsg"));
+
+ /* Grab the pathfd copy from the other end of the pair. */
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg;
+ msg.msg_controllen = CMSG_SPACE(sizeof(pathfd));
+
+ ATF_REQUIRE_MSG(recvmsg(sd[1], &msg, 0) == 1,
+ FMT_ERR("recvmsg"));
+ pathfd_copy = *(int *)(void *)CMSG_DATA(cmsg);
+ ATF_REQUIRE_MSG(pathfd_copy != pathfd,
+ "pathfd and pathfd_copy are equal");
+
+ /* Verify that the copy has O_PATH properties. */
+ flags = fcntl(pathfd_copy, F_GETFL);
+ ATF_REQUIRE_MSG(flags != -1, FMT_ERR("fcntl"));
+ ATF_REQUIRE_MSG((flags & O_PATH) != 0, "O_PATH is not set");
+ ATF_REQUIRE_ERRNO(EBADF,
+ read(pathfd_copy, &c, 1) == -1);
+ ATF_REQUIRE_ERRNO(EBADF,
+ write(pathfd_copy, &c, 1) == -1);
+
+ CHECKED_CLOSE(pathfd);
+ CHECKED_CLOSE(pathfd_copy);
+ CHECKED_CLOSE(sd[0]);
+ CHECKED_CLOSE(sd[1]);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, path_access);
+ ATF_TP_ADD_TC(tp, path_aio);
+ ATF_TP_ADD_TC(tp, path_capsicum);
+ ATF_TP_ADD_TC(tp, path_directory);
+ ATF_TP_ADD_TC(tp, path_directory_not_root);
+ ATF_TP_ADD_TC(tp, path_empty);
+ ATF_TP_ADD_TC(tp, path_empty_not_root);
+ ATF_TP_ADD_TC(tp, path_empty_root);
+ ATF_TP_ADD_TC(tp, path_event);
+ ATF_TP_ADD_TC(tp, path_fcntl);
+ ATF_TP_ADD_TC(tp, path_fexecve);
+ ATF_TP_ADD_TC(tp, path_funlinkat);
+ ATF_TP_ADD_TC(tp, path_io);
+ ATF_TP_ADD_TC(tp, path_ioctl);
+ ATF_TP_ADD_TC(tp, path_lock);
+ ATF_TP_ADD_TC(tp, path_rights);
+
+ return (atf_no_error());
+}