From owner-dev-commits-src-main@freebsd.org Thu Apr 15 13:41:56 2021 Return-Path: Delivered-To: dev-commits-src-main@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 46D4D5F052C; Thu, 15 Apr 2021 13:41:56 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4FLgVr1bLGz3tyx; Thu, 15 Apr 2021 13:41:56 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 2991F12783; Thu, 15 Apr 2021 13:41:56 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 13FDfuu0086050; Thu, 15 Apr 2021 13:41:56 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 13FDfupC086049; Thu, 15 Apr 2021 13:41:56 GMT (envelope-from git) Date: Thu, 15 Apr 2021 13:41:56 GMT Message-Id: <202104151341.13FDfupC086049@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Mark Johnston Subject: git: 3a248c84419d - main - Add some regression tests for O_PATH and AT_EMPTY_PATH MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 3a248c84419d4f7d6617d30744ab56d0b456fe03 Auto-Submitted: auto-generated X-BeenThere: dev-commits-src-main@freebsd.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Commit messages for the main branch of the src repository List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 15 Apr 2021 13:41:56 -0000 The branch main has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=3a248c84419d4f7d6617d30744ab56d0b456fe03 commit 3a248c84419d4f7d6617d30744ab56d0b456fe03 Author: Mark Johnston AuthorDate: 2021-04-15 13:40:57 +0000 Commit: Mark Johnston CommitDate: 2021-04-15 13:40:57 +0000 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 --- tests/sys/file/Makefile | 1 + tests/sys/file/path_test.c | 778 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 779 insertions(+) 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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()); +}