git: fe03a78c5d59 - main - tests/jaildesc: Add some more test scenarios

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Fri, 12 Jun 2026 16:00:57 UTC
The branch main has been updated by markj:

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

commit fe03a78c5d5966992c8df482d984bae83dc92b45
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-06-12 14:58:19 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-06-12 14:58:19 +0000

    tests/jaildesc: Add some more test scenarios
    
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D57147
---
 tests/sys/kern/Makefile   |   2 +-
 tests/sys/kern/jaildesc.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 205 insertions(+), 1 deletion(-)

diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
index fb267f1a2782..3b09ee3181d2 100644
--- a/tests/sys/kern/Makefile
+++ b/tests/sys/kern/Makefile
@@ -95,7 +95,7 @@ PROGS+=		sendfile_helper
 LIBADD.aslr+=				util
 LIBADD.copy_file_range+=		md
 LIBADD.jail_lookup_root+=		jail util
-LIBADD.jaildesc+=			pthread
+LIBADD.jaildesc+=			kvm pthread
 LIBADD.ssl_sendfile+=			pthread crypto ssl
 CFLAGS.sys_getrandom+=			-I${SRCTOP}/sys/contrib/zstd/lib
 LIBADD.sys_getrandom+=			zstd
diff --git a/tests/sys/kern/jaildesc.c b/tests/sys/kern/jaildesc.c
index 11d751554887..cfdd1529fdfa 100644
--- a/tests/sys/kern/jaildesc.c
+++ b/tests/sys/kern/jaildesc.c
@@ -6,16 +6,56 @@
 
 #include <sys/param.h>
 #include <sys/jail.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
 #include <sys/uio.h>
+#include <sys/user.h>
 
 #include <atf-c.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <poll.h>
 #include <pthread.h>
 #include <pwd.h>
+#include <signal.h>
 #include <string.h>
 #include <unistd.h>
 
+#include <kvm.h>
+
+/*
+ * Block until a thread in the specified process is sleeping in the specified
+ * wait message.
+ */
+static void
+wait_for_naptime(pid_t pid, const char *wmesg)
+{
+	kvm_t *kd;
+	int count;
+
+	kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, NULL);
+	ATF_REQUIRE(kd != NULL);
+	for (;;) {
+		struct kinfo_proc *kip;
+		int i;
+
+		usleep(1000);
+		kip = kvm_getprocs(kd, KERN_PROC_PID | KERN_PROC_INC_THREAD,
+		    pid, &count);
+		ATF_REQUIRE(kip != NULL);
+		for (i = 0; i < count; i++) {
+			ATF_REQUIRE(kip[i].ki_stat != SZOMB);
+			if (kip[i].ki_stat == SSLEEP &&
+			    strcmp(kip[i].ki_wmesg, wmesg) == 0)
+				break;
+		}
+		if (i < count)
+			break;
+	}
+
+	kvm_close(kd);
+}
+
 /*
  * Create a persistent jail and return an owning descriptor for it.
  * The jail is removed when the returned descriptor is closed.
@@ -191,11 +231,175 @@ ATF_TC_BODY(poll_close_race_get_desc, tc)
 	ATF_REQUIRE_MSG(close(owning_jd) == 0, "close: %s", strerror(errno));
 }
 
+/*
+ * Verify that a process inside a jail cannot obtain a jail descriptor for
+ * its own jail.
+ */
+ATF_TC(curjail_get);
+ATF_TC_HEAD(curjail_get, tc)
+{
+	atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(curjail_get, tc)
+{
+	char namebuf[MAXHOSTNAMELEN];
+	struct iovec iov[4];
+	int desc, error, jid, n;
+
+	(void)create_jail("jaildesc_get_desc_current_jail");
+
+	strlcpy(namebuf, "jaildesc_get_desc_current_jail", sizeof(namebuf));
+	jid = -1;
+	n = 0;
+	iov[n].iov_base = __DECONST(void *, "name");
+	iov[n++].iov_len = sizeof("name");
+	iov[n].iov_base = namebuf;
+	iov[n++].iov_len = strlen(namebuf) + 1;
+	jid = jail_get(iov, n, 0);
+	ATF_REQUIRE_MSG(jid >= 0, "jail_get: %s", strerror(errno));
+
+	error = jail_attach(jid);
+	ATF_REQUIRE_MSG(error == 0, "jail_attach: %s", strerror(errno));
+
+	/*
+	 * Now that we are inside the jail, verify that we cannot obtain a
+	 * descriptor for it.
+	 */
+	strlcpy(namebuf, "jaildesc_get_desc_current_jail", sizeof(namebuf));
+	desc = -1;
+	n = 0;
+	iov[n].iov_base = __DECONST(void *, "name");
+	iov[n++].iov_len = sizeof("name");
+	iov[n].iov_base = namebuf;
+	iov[n++].iov_len = strlen(namebuf) + 1;
+	iov[n].iov_base = __DECONST(void *, "desc");
+	iov[n++].iov_len = sizeof("desc");
+	iov[n].iov_base = &desc;
+	iov[n++].iov_len = sizeof(desc);
+	ATF_REQUIRE_MSG(jail_get(iov, n, JAIL_GET_DESC) == -1,
+	    "jail_get succeeded unexpectedly");
+	ATF_REQUIRE_MSG(jail_get(iov, n, JAIL_OWN_DESC) == -1,
+	    "jail_get succeeded unexpectedly");
+}
+
+static void
+nop(int signum __unused)
+{
+}
+
+/*
+ * Close an owning descriptor while in the corresponding jail.
+ */
+ATF_TC(self_destruct);
+ATF_TC_HEAD(self_destruct, tc)
+{
+	atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(self_destruct, tc)
+{
+	int error, jd, status;
+	pid_t pid;
+
+	jd = create_jail("jaildesc_self_destruct");
+
+	pid = fork();
+	ATF_REQUIRE_MSG(pid >= 0, "fork: %s", strerror(errno));
+	if (pid == 0) {
+		struct sigaction sa;
+
+		error = jail_attach_jd(jd);
+		if (error != 0)
+			_exit(1);
+
+		sa.sa_handler = nop;
+		sa.sa_flags = 0;
+		sigemptyset(&sa.sa_mask);
+		error = sigaction(SIGALRM, &sa, NULL);
+		if (error != 0)
+			_exit(2);
+
+		pause();
+		close(jd);
+		/* NOTREACHED? */
+		_exit(3);
+	}
+
+	wait_for_naptime(pid, "sigsusp");
+
+	error = close(jd);
+	ATF_REQUIRE_MSG(error == 0, "close: %s", strerror(errno));
+
+	error = kill(pid, SIGALRM);
+	ATF_REQUIRE_MSG(error == 0, "kill: %s", strerror(errno));
+
+	pid = waitpid(pid, &status, 0);
+	ATF_REQUIRE_MSG(pid >= 0, "waitpid: %s", strerror(errno));
+	ATF_REQUIRE(WIFSIGNALED(status));
+	ATF_REQUIRE_EQ(WTERMSIG(status), SIGKILL);
+}
+
+/*
+ * Try to get an fd for a non-existent jail.
+ */
+ATF_TC(at_desc_bad_fd);
+ATF_TC_HEAD(at_desc_bad_fd, tc)
+{
+	atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(at_desc_bad_fd, tc)
+{
+	int jd, status;
+	pid_t pid;
+
+	jd = create_jail("jaildesc_at_desc_bad_fd");
+
+	pid = fork();
+	ATF_REQUIRE_MSG(pid >= 0, "fork: %s", strerror(errno));
+	if (pid == 0) {
+		struct iovec iov[4];
+		char namebuf[MAXHOSTNAMELEN];
+		int desc, i, n;
+
+		if (jail_attach_jd(jd) != 0)
+			_exit(1);
+
+		/* Regression test: loop here to trigger a refcount leak. */
+		for (i = 0; i < 100; i++) {
+			strlcpy(namebuf, "nonexistent", sizeof(namebuf));
+			desc = STDIN_FILENO;
+			n = 0;
+			iov[n].iov_base = __DECONST(void *, "name");
+			iov[n++].iov_len = strlen("name") + 1;
+			iov[n].iov_base = namebuf;
+			iov[n++].iov_len = sizeof(namebuf);
+			iov[n].iov_base = __DECONST(void *, "desc");
+			iov[n++].iov_len = strlen("desc") + 1;
+			iov[n].iov_base = &desc;
+			iov[n++].iov_len = sizeof(desc);
+			if (jail_get(iov, n, JAIL_AT_DESC) != -1)
+				_exit(2);
+			if (errno != EINVAL)
+				_exit(3);
+		}
+		_exit(0);
+	}
+
+	pid = waitpid(pid, &status, 0);
+	ATF_REQUIRE_MSG(pid >= 0, "waitpid: %s", strerror(errno));
+	ATF_REQUIRE_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+	    "child failed with status %d", status);
+
+	ATF_REQUIRE_MSG(close(jd) == 0, "close: %s", strerror(errno));
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, poll_close_race);
 	ATF_TP_ADD_TC(tp, poll_remove_wakeup);
 	ATF_TP_ADD_TC(tp, poll_close_race_get_desc);
+	ATF_TP_ADD_TC(tp, curjail_get);
+	ATF_TP_ADD_TC(tp, self_destruct);
+	ATF_TP_ADD_TC(tp, at_desc_bad_fd);
 
 	return (atf_no_error());
 }