Changeset View
Standalone View
tests/sys/file/path_test.c
- This file was added.
/*- | |||||
* 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/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")); | |||||
} | |||||
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")); | |||||
kib: Canonical way, AFAIU, is to not specify O_RDONLY. O_PATH is the access mode on its own. When… | |||||
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); | |||||
} | |||||
/* 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); | |||||
} | |||||
/* 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); | |||||
/* Try to verify that we can't operate on privileged files. */ | |||||
if (geteuid() != 0) { | |||||
pathfd = open("/dev/null", O_PATH); | |||||
ATF_REQUIRE_MSG(pathfd >= 0, FMT_ERR("open")); | |||||
ATF_REQUIRE_ERRNO(EPERM, | |||||
fchownat(pathfd, "", getuid(), getgid(), AT_EMPTY_PATH) == | |||||
-1); | |||||
ATF_REQUIRE_ERRNO(EPERM, | |||||
linkat(pathfd, "", AT_FDCWD, "test", AT_EMPTY_PATH) == -1); | |||||
CHECKED_CLOSE(pathfd); | |||||
} | |||||
} | |||||
/* 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"); | |||||
} | |||||
Not Done Inline ActionsIt would be most interesting to do something in reverse, namely, check that AT_EMPTY_PATH verifies access permissions when non-root user tries to e.g. linkat(AT_EMPTY_PATH) to file he does not own. In other words, check that AT_EMPTY_PATH does not create a security hole. But I have no idea how to do it with atf. kib: It would be most interesting to do something in reverse, namely, check that AT_EMPTY_PATH… | |||||
Done Inline ActionsI tried to do this in the test above, with the geteuid() == 0 check. It is not ideal, nothing ensures that the test is ever run as a non-root user. markj: I tried to do this in the test above, with the `geteuid() == 0` check. It is not ideal, nothing… | |||||
Not Done Inline ActionsThere is some mechanism in ATF triggered by atf_tc_set_md_var(tc, "require.user", "unprivileged");. See for instance contrib/netbsd-tests/lib/libc/sys/t_access.c access_access But I have no idea about details. kib: There is some mechanism in ATF triggered by `atf_tc_set_md_var(tc, "require.user"… | |||||
Done Inline ActionsThanks, I didn't know about it. Apparently it causes kyua to run the test without privileges if invoked as root. markj: Thanks, I didn't know about it. Apparently it causes kyua to run the test without privileges if… | |||||
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 error, fd, pathfd, status; | |||||
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); | |||||
} | |||||
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) == 0, "child exit status is %d", | |||||
WEXITSTATUS(status)); | |||||
/* | |||||
* 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_directory); | |||||
ATF_TP_ADD_TC(tp, path_empty); | |||||
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()); | |||||
} |
Canonical way, AFAIU, is to not specify O_RDONLY. O_PATH is the access mode on its own. When requesting O_PATH | O_EXEC, we requesting two modes.
On the other hand, O_RDONLY is zero so it is fine either way for testing the implementation.