--- sys/kern/vfs_syscalls.c.orig +++ sys/kern/vfs_syscalls.c @@ -1976,16 +1976,17 @@ static int kern_funlinkat_ex(struct thread *td, int dfd, const char *path, int fd, - int flag, enum uio_seg pathseg, ino_t oldinum) + int flags, enum uio_seg pathseg, ino_t oldinum) { - if ((flag & ~(AT_REMOVEDIR | AT_RESOLVE_BENEATH)) != 0) + if ((flags & ~(AT_REMOVEDIR | AT_RESOLVE_BENEATH)) != 0) return (EINVAL); - if ((flag & AT_REMOVEDIR) != 0) - return (kern_frmdirat(td, dfd, path, fd, UIO_USERSPACE, 0)); + if ((flags & AT_REMOVEDIR) != 0) + return (kern_frmdirat(td, dfd, path, fd, pathseg, + flags & ~AT_REMOVEDIR)); - return (kern_funlinkat(td, dfd, path, fd, UIO_USERSPACE, 0, 0)); + return (kern_funlinkat(td, dfd, path, fd, pathseg, flags, 0)); } #ifndef _SYS_SYSPROTO_H_ --- tests/sys/kern/Makefile.orig +++ tests/sys/kern/Makefile @@ -39,6 +39,7 @@ ATF_TESTS_C+= ptrace_test TEST_METADATA.ptrace_test+= timeout="15" ATF_TESTS_C+= reaper +ATF_TESTS_C+= resolve_beneath_test ATF_TESTS_C+= sched_affinity ATF_TESTS_C+= shutdown_dgram ATF_TESTS_C+= sigaltstack --- /dev/null +++ tests/sys/kern/resolve_beneath_test.c @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2026 The FreeBSD Foundation + * + * This software was written by Mark Johnston under sponsorship from + * the FreeBSD Foundation. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include +#include +#include + +#include + +/* + * Verify that AT_RESOLVE_BENEATH is respected by various system calls. + */ + +static int +setup(void) +{ + int fd, tfd; + + ATF_REQUIRE_EQ(0, mkdir("base", 0755)); + ATF_REQUIRE_EQ(0, mkdir("outside", 0755)); + ATF_REQUIRE((tfd = open("outside/target", O_CREAT | O_WRONLY, 0644)) >= + 0); + ATF_REQUIRE(close(tfd) == 0); + ATF_REQUIRE_EQ(0, mkdir("outside/dir", 0755)); + ATF_REQUIRE((tfd = open("base/file", O_CREAT | O_WRONLY, 0644)) >= 0); + ATF_REQUIRE(close(tfd) == 0); + ATF_REQUIRE_EQ(0, mkdir("base/subdir", 0755)); + ATF_REQUIRE((fd = open("base", O_DIRECTORY | O_RDONLY)) >= 0); + return (fd); +} + +ATF_TC_WITHOUT_HEAD(faccessat_beneath); +ATF_TC_BODY(faccessat_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + faccessat(fd, "file", F_OK, AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + faccessat(fd, "../outside/target", F_OK, + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(chflagsat_beneath); +ATF_TC_BODY(chflagsat_beneath, tc) +{ + int fd, ret; + + fd = setup(); + ret = chflagsat(fd, "file", UF_NODUMP, AT_RESOLVE_BENEATH); + if (ret != 0) + ATF_REQUIRE_EQ(EOPNOTSUPP, errno); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + chflagsat(fd, "../outside/target", UF_NODUMP, + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(fchmodat_beneath); +ATF_TC_BODY(fchmodat_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + fchmodat(fd, "file", 0644, AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + fchmodat(fd, "../outside/target", 0644, + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(fchownat_beneath); +ATF_TC_BODY(fchownat_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + fchownat(fd, "file", -1, -1, AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + fchownat(fd, "../outside/target", 0, 0, + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(utimensat_beneath); +ATF_TC_BODY(utimensat_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + utimensat(fd, "file", NULL, AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + utimensat(fd, "../outside/target", NULL, + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(linkat_beneath); +ATF_TC_BODY(linkat_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + linkat(fd, "file", fd, "hardlink", AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + linkat(fd, "../outside/target", fd, "hardlink2", + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(unlinkat_beneath); +ATF_TC_BODY(unlinkat_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + unlinkat(fd, "file", AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + unlinkat(fd, "../outside/target", + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(unlinkat_rmdir_beneath); +ATF_TC_BODY(unlinkat_rmdir_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + unlinkat(fd, "subdir", + AT_REMOVEDIR | AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + unlinkat(fd, "../outside/dir", + AT_REMOVEDIR | AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE_EQ(0, access("outside/dir", F_OK)); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(funlinkat_beneath); +ATF_TC_BODY(funlinkat_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + funlinkat(fd, "file", FD_NONE, AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + funlinkat(fd, "../outside/target", FD_NONE, + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC_WITHOUT_HEAD(funlinkat_rmdir_beneath); +ATF_TC_BODY(funlinkat_rmdir_beneath, tc) +{ + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, mkdir("base/subdir2", 0755)); + ATF_REQUIRE_EQ(0, + funlinkat(fd, "subdir2", FD_NONE, + AT_REMOVEDIR | AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + funlinkat(fd, "../outside/dir", FD_NONE, + AT_REMOVEDIR | AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE_EQ(0, access("outside/dir", F_OK)); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TC(getfhat_beneath); +ATF_TC_HEAD(getfhat_beneath, tc) +{ + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(getfhat_beneath, tc) +{ + fhandle_t fh; + int fd; + + fd = setup(); + ATF_REQUIRE_EQ(0, + getfhat(fd, "file", &fh, AT_RESOLVE_BENEATH)); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + getfhat(fd, "../outside/target", &fh, + AT_RESOLVE_BENEATH) == -1); + ATF_REQUIRE(close(fd) == 0); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, faccessat_beneath); + ATF_TP_ADD_TC(tp, chflagsat_beneath); + ATF_TP_ADD_TC(tp, fchmodat_beneath); + ATF_TP_ADD_TC(tp, fchownat_beneath); + ATF_TP_ADD_TC(tp, utimensat_beneath); + ATF_TP_ADD_TC(tp, linkat_beneath); + ATF_TP_ADD_TC(tp, unlinkat_beneath); + ATF_TP_ADD_TC(tp, unlinkat_rmdir_beneath); + ATF_TP_ADD_TC(tp, funlinkat_beneath); + ATF_TP_ADD_TC(tp, funlinkat_rmdir_beneath); + ATF_TP_ADD_TC(tp, getfhat_beneath); + return (atf_no_error()); +}