git: f56b66f1260a - stable/14 - tests: Add a regression test for commit 7587f6d4840f8

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Mon, 30 Jun 2025 14:21:29 UTC
The branch stable/14 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=f56b66f1260a33e49c65bbc05213ec8267978a93

commit f56b66f1260a33e49c65bbc05213ec8267978a93
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2025-05-28 15:28:36 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2025-06-30 14:19:30 +0000

    tests: Add a regression test for commit 7587f6d4840f8
    
    Reviewed by:    kib
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D50533
    
    (cherry picked from commit a5dac34f6e98c47bd7cb1946e39cc45432e167a8)
---
 tests/sys/kern/Makefile           |   2 +
 tests/sys/kern/jail_lookup_root.c | 171 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 173 insertions(+)

diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
index 46977bedef98..89c993a39dc7 100644
--- a/tests/sys/kern/Makefile
+++ b/tests/sys/kern/Makefile
@@ -19,6 +19,7 @@ ATF_TESTS_C+=	kern_descrip_test
 # One test modifies the maxfiles limit, which can cause spurious test failures.
 TEST_METADATA.kern_descrip_test+= is_exclusive="true"
 ATF_TESTS_C+=	fdgrowtable_test
+ATF_TESTS_C+=	jail_lookup_root
 ATF_TESTS_C+=	kill_zombie
 .if ${MK_OPENSSL} != "no"
 ATF_TESTS_C+=	ktls_test
@@ -71,6 +72,7 @@ PROGS+=		coredump_phnum_helper
 PROGS+=		pdeathsig_helper
 PROGS+=		sendfile_helper
 
+LIBADD.jail_lookup_root+=		jail util
 CFLAGS.sys_getrandom+=			-I${SRCTOP}/sys/contrib/zstd/lib
 LIBADD.sys_getrandom+=			zstd
 LIBADD.sys_getrandom+=			c
diff --git a/tests/sys/kern/jail_lookup_root.c b/tests/sys/kern/jail_lookup_root.c
new file mode 100644
index 000000000000..609f86ac4c66
--- /dev/null
+++ b/tests/sys/kern/jail_lookup_root.c
@@ -0,0 +1,171 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Mark Johnston <markj@FreeBSD.org>
+ */
+
+#include <sys/param.h>
+#include <sys/jail.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jail.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+static void
+build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val,
+    size_t len)
+{
+	int i;
+
+	if (*iovlen < 0)
+		return;
+	i = *iovlen;
+	*iov = realloc(*iov, sizeof **iov * (i + 2));
+	if (*iov == NULL) {
+		*iovlen = -1;
+		return;
+	}
+	(*iov)[i].iov_base = strdup(name);
+	(*iov)[i].iov_len = strlen(name) + 1;
+	i++;
+	(*iov)[i].iov_base = val;
+	if (len == (size_t)-1) {
+		if (val != NULL)
+			len = strlen(val) + 1;
+		else
+			len = 0;
+	}
+	(*iov)[i].iov_len = (int)len;
+	*iovlen = ++i;
+}
+
+static void
+free_iovec(struct iovec **iov, int *iovlen)
+{
+	int i;
+
+	for (i = 0; i < *iovlen; i += 2)
+		free((*iov)[i].iov_base);
+	free(*iov);
+}
+
+static void
+mkdir_checked(const char *dir, mode_t mode)
+{
+	int error;
+
+	error = mkdir(dir, mode);
+	ATF_REQUIRE_MSG(error == 0 || errno == EEXIST,
+	    "mkdir %s: %s", dir, strerror(errno));
+}
+
+static void __unused
+mount_nullfs(const char *dir, const char *target)
+{
+	struct iovec *iov;
+	char errmsg[1024];
+	int error, iovlen;
+
+	iov = NULL;
+	iovlen = 0;
+
+	build_iovec(&iov, &iovlen, __DECONST(char *, "fstype"),
+	    __DECONST(char *, "nullfs"), (size_t)-1);
+	build_iovec(&iov, &iovlen, __DECONST(char *, "fspath"),
+	    __DECONST(char *, target), (size_t)-1);
+	build_iovec(&iov, &iovlen, __DECONST(char *, "from"),
+	    __DECONST(char *, dir), (size_t)-1);
+	build_iovec(&iov, &iovlen, __DECONST(char *, "errmsg"),
+	    errmsg, sizeof(errmsg));
+
+	errmsg[0] = '\0';
+	error = nmount(iov, iovlen, 0);
+	ATF_REQUIRE_MSG(error == 0, "nmount: %s",
+	    errmsg[0] != '\0' ? errmsg : strerror(errno));
+
+	free_iovec(&iov, &iovlen);
+}
+
+ATF_TC_WITH_CLEANUP(jail_root);
+ATF_TC_HEAD(jail_root, tc)
+{
+	atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(jail_root, tc)
+{
+	int error, fd, jid;
+
+	mkdir_checked("./root", 0755);
+	mkdir_checked("./root/a", 0755);
+	mkdir_checked("./root/b", 0755);
+	mkdir_checked("./root/a/c", 0755);
+
+	jid = jail_setv(JAIL_CREATE | JAIL_ATTACH,
+	    "name", "nullfs_jail_root_test",
+	    "allow.mount", "true",
+	    "allow.mount.nullfs", "true",
+	    "enforce_statfs", "1",
+	    "path", "./root",
+	    "persist", NULL,
+	    NULL);
+	ATF_REQUIRE_MSG(jid >= 0, "jail_setv: %s", jail_errmsg);
+
+	mount_nullfs("/a", "/b");
+
+	error = chdir("/b/c");
+	ATF_REQUIRE(error == 0);
+
+	error = rename("/a/c", "/c");
+	ATF_REQUIRE(error == 0);
+
+	/* Descending to the jail root should be ok. */
+	error = chdir("..");
+	ATF_REQUIRE(error == 0);
+
+	/* Going beyond the root will trigger an error. */
+	error = chdir("..");
+	ATF_REQUIRE_ERRNO(ENOENT, error != 0);
+	fd = open("..", O_RDONLY | O_DIRECTORY);
+	ATF_REQUIRE_ERRNO(ENOENT, fd < 0);
+}
+ATF_TC_CLEANUP(jail_root, tc)
+{
+	struct statfs fs;
+	fsid_t fsid;
+	int error, jid;
+
+	error = statfs("./root/b", &fs);
+	if (error != 0)
+		err(1, "statfs ./b");
+	fsid = fs.f_fsid;
+	error = statfs("./root", &fs);
+	if (error != 0)
+		err(1, "statfs ./root");
+	if (fsid.val[0] != fs.f_fsid.val[0] ||
+	    fsid.val[1] != fs.f_fsid.val[1]) {
+		error = unmount("./root/b", 0);
+		if (error != 0)
+			err(1, "unmount ./root/b");
+	}
+
+	jid = jail_getid("nullfs_jail_root_test");
+	if (jid >= 0) {
+		error = jail_remove(jid);
+		if (error != 0)
+			err(1, "jail_remove");
+	}
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	ATF_TP_ADD_TC(tp, jail_root);
+	return (atf_no_error());
+}