Changeset View
Changeset View
Standalone View
Standalone View
tests/sys/kern/jail_thread.c
- This file was added.
| /*- | |||||||||||
| * SPDX-License-Identifier: BSD-2-Clause | |||||||||||
| * | |||||||||||
| * Copyright (c) 2026 James Gritton <jamie@FreeBSD.org> | |||||||||||
| */ | |||||||||||
| #include <sys/param.h> | |||||||||||
| #include <sys/jail.h> | |||||||||||
| #include <sys/stat.h> | |||||||||||
| #include <err.h> | |||||||||||
| #include <errno.h> | |||||||||||
| #include <jail.h> | |||||||||||
| #include <pthread.h> | |||||||||||
| #include <stdio.h> | |||||||||||
| #include <stdlib.h> | |||||||||||
| #include <string.h> | |||||||||||
| #include <unistd.h> | |||||||||||
| #include <atf-c.h> | |||||||||||
| #define NJAILS 3 /* One master jail and two to race. */ | |||||||||||
| #define NROUNDS 10000 /* Number of attempts to make race happen. */ | |||||||||||
markj: Could you please use unique jail names for each test? Otherwise the tests can't run in parallel. | |||||||||||
| struct jailinfo { | |||||||||||
| int jfd; | |||||||||||
| ino_t ino; | |||||||||||
| char path[MAXPATHLEN]; | |||||||||||
| }; | |||||||||||
| static pthread_barrier_t barrier; | |||||||||||
| static struct jailinfo jinfo[NJAILS]; | |||||||||||
| /* Attach a thread to a jail with jail_attach_jd. */ | |||||||||||
| static void * | |||||||||||
| thread_jail_attach_jd(void *arg) | |||||||||||
| { | |||||||||||
| int error; | |||||||||||
Done Inline Actions
style(9) wants a newline after the return type, ditto below. markj: style(9) wants a newline after the return type, ditto below. | |||||||||||
| /* | |||||||||||
| * Synchronize to get as close as possible to the same time, | |||||||||||
| * then attach to a jail. | |||||||||||
| */ | |||||||||||
| error = pthread_barrier_wait(&barrier); | |||||||||||
| ATF_REQUIRE_MSG(error == 0 || error == PTHREAD_BARRIER_SERIAL_THREAD, | |||||||||||
| "pthread_barrier_wait: %s", strerror(errno)); | |||||||||||
| ATF_REQUIRE_MSG(jail_attach_jd(jinfo[(size_t)arg].jfd) == 0, | |||||||||||
| "jail_attach_jd: %s", strerror(errno)); | |||||||||||
| return (NULL); | |||||||||||
| } | |||||||||||
| /* Attach a thread to a jail with jail_setv. */ | |||||||||||
| static void * | |||||||||||
| thread_jail_setv(void *arg) | |||||||||||
| { | |||||||||||
| int error; | |||||||||||
| char jdescstr[16]; | |||||||||||
| error = pthread_barrier_wait(&barrier); | |||||||||||
| ATF_REQUIRE_MSG(error == 0 || error == PTHREAD_BARRIER_SERIAL_THREAD, | |||||||||||
| "pthread_barrier_wait: %s", strerror(errno)); | |||||||||||
| snprintf(jdescstr, sizeof(jdescstr), "%d", jinfo[(size_t)arg].jfd); | |||||||||||
| ATF_REQUIRE_MSG(jail_setv(JAIL_UPDATE | JAIL_ATTACH | JAIL_USE_DESC, | |||||||||||
| "desc", jdescstr, NULL) > 0, | |||||||||||
| "jail_setv: %s", jail_errmsg[0] ? jail_errmsg : strerror(errno)); | |||||||||||
| return (NULL); | |||||||||||
| } | |||||||||||
| /* Attach a thread to a jail with chroot. */ | |||||||||||
| static void * | |||||||||||
| thread_chroot(void *arg) | |||||||||||
| { | |||||||||||
| int error; | |||||||||||
| /* This is a race between jail_attach and chroot. */ | |||||||||||
| if ((size_t)arg > 1) | |||||||||||
| return thread_jail_attach_jd(arg); | |||||||||||
| error = pthread_barrier_wait(&barrier); | |||||||||||
| ATF_REQUIRE_MSG(error == 0 || error == PTHREAD_BARRIER_SERIAL_THREAD, | |||||||||||
| "pthread_barrier_wait: %s", strerror(errno)); | |||||||||||
| ATF_REQUIRE_MSG(chroot(jinfo[(size_t)arg].path) == 0 || errno == ENOENT, | |||||||||||
| "chroot: %s", strerror(errno)); | |||||||||||
| return (NULL); | |||||||||||
| } | |||||||||||
| static void | |||||||||||
| thread_attach_test(void *(*thread_handler)(void*), const char *jail_name, | |||||||||||
| const char *syscall_name, bool jail_reset) | |||||||||||
| { | |||||||||||
| int ri, spn, mixed_jails; | |||||||||||
| size_t ji, ti, ji_hostname, ji_ino, jail_namelen; | |||||||||||
| char *cwd; | |||||||||||
| struct stat st; | |||||||||||
| char jnamestr[64], jdescstr[16]; | |||||||||||
| pthread_t threads[NJAILS]; | |||||||||||
| if (jinfo[0].jfd == 0) { | |||||||||||
| /* Start with a master jail, so we can return to real root. */ | |||||||||||
| jdescstr[0] = '\0'; | |||||||||||
| ATF_REQUIRE_MSG(jail_setv(JAIL_CREATE | JAIL_OWN_DESC, | |||||||||||
| "name", jail_name, | |||||||||||
| "path", "/", | |||||||||||
| "desc", jdescstr, | |||||||||||
| "persist", "true", | |||||||||||
| NULL) > 0, | |||||||||||
| "jail_setv jail 0: %s", | |||||||||||
| jail_errmsg[0] ? jail_errmsg : strerror(errno)); | |||||||||||
| jinfo[0].jfd = strtol(jdescstr, NULL, 10); | |||||||||||
| /* Make enough jails to cause contention. */ | |||||||||||
| cwd = getcwd(NULL, MAXPATHLEN); | |||||||||||
| ATF_REQUIRE_MSG(cwd != NULL, "getcwd: %s", strerror(errno)); | |||||||||||
| for (ji = 1; ji < NJAILS; ++ji) { | |||||||||||
| snprintf(jnamestr, sizeof(jnamestr), | |||||||||||
| "%s%zu", jail_name, ji); | |||||||||||
| spn = snprintf(jinfo[ji].path, MAXPATHLEN, | |||||||||||
| "%s/jail%zu", cwd, ji); | |||||||||||
| ATF_REQUIRE_MSG((size_t)spn < MAXPATHLEN, | |||||||||||
| "snprintf exceeded MAXPATHLEN: %d", spn); | |||||||||||
| ATF_REQUIRE_MSG( | |||||||||||
| mkdir(jinfo[ji].path, 0755) == 0 || errno == EEXIST, | |||||||||||
| "mkdir %s: %s", jinfo[ji].path, strerror(errno)); | |||||||||||
| ATF_REQUIRE_MSG(stat(jinfo[ji].path, &st) == 0, | |||||||||||
| "stat %s: %s", jinfo[ji].path, strerror(errno)); | |||||||||||
| jinfo[ji].ino = st.st_ino; | |||||||||||
| jdescstr[0] = '\0'; | |||||||||||
| ATF_REQUIRE_MSG(jail_setv(JAIL_CREATE | JAIL_OWN_DESC, | |||||||||||
| "name", jnamestr, | |||||||||||
| "host.hostname", jnamestr, | |||||||||||
| "path", jinfo[ji].path, | |||||||||||
| "desc", jdescstr, | |||||||||||
| "persist", "true", | |||||||||||
| NULL) > 0, | |||||||||||
| "jail_setv: %s", | |||||||||||
| jail_errmsg[0] ? jail_errmsg : strerror(errno)); | |||||||||||
| jinfo[ji].jfd = strtol(jdescstr, NULL, 10); | |||||||||||
| } | |||||||||||
| } else | |||||||||||
| ATF_REQUIRE_MSG(jail_attach_jd(jinfo[0].jfd) == 0, | |||||||||||
| "jail_attach_jd: %s", strerror(errno)); | |||||||||||
| /* Check the different system calls that can race. */ | |||||||||||
| jail_namelen = strlen(jail_name); | |||||||||||
| mixed_jails = 0; | |||||||||||
| for (ri = 0; ri < NROUNDS; ++ri) { | |||||||||||
| ATF_REQUIRE_MSG( | |||||||||||
| pthread_barrier_init(&barrier, NULL, NJAILS - 1) == 0, | |||||||||||
| "pthread_barrier_init: %s", strerror(errno)); | |||||||||||
| for (ti = 1; ti < NJAILS; ++ti) | |||||||||||
| ATF_REQUIRE_MSG( | |||||||||||
| pthread_create(&threads[ti], NULL, thread_handler, | |||||||||||
| (void*)ti) == 0, | |||||||||||
| "pthread_create: %s", strerror(errno)); | |||||||||||
| for (ti = 1; ti < NJAILS; ++ti) | |||||||||||
| ATF_REQUIRE_MSG( | |||||||||||
| pthread_join(threads[ti], NULL) == 0, | |||||||||||
| "pthread_join: %s", strerror(errno)); | |||||||||||
| ATF_REQUIRE_MSG(pthread_barrier_destroy(&barrier) == 0, | |||||||||||
| "pthread_barrier_destroy: %s", strerror(errno)); | |||||||||||
| /* | |||||||||||
| * Find the current jail from the hostname, and also | |||||||||||
| * by the root inode. They should be the same. | |||||||||||
| */ | |||||||||||
| ATF_REQUIRE_MSG( | |||||||||||
| gethostname(jnamestr, sizeof(jnamestr)) == 0, | |||||||||||
| "gethostname: %s", strerror(errno)); | |||||||||||
| ATF_REQUIRE_MSG(strncmp(jnamestr, jail_name, jail_namelen) == 0, | |||||||||||
| "unexpected jail hostname %s", jnamestr); | |||||||||||
| ji_hostname = strtol(jnamestr + jail_namelen, NULL, 10); | |||||||||||
| ATF_REQUIRE_MSG(stat("/", &st) == 0, | |||||||||||
| "stat /: %s", strerror(errno)); | |||||||||||
| for (ji_ino = 1; ji_ino < NJAILS; ++ji_ino) | |||||||||||
| if (jinfo[ji_ino].ino == st.st_ino) | |||||||||||
| break; | |||||||||||
| ATF_REQUIRE_MSG(ji_ino < NJAILS, | |||||||||||
| "unexpected jail root inode %lu", | |||||||||||
| (unsigned long)st.st_ino); | |||||||||||
| mixed_jails += ji_hostname != ji_ino; | |||||||||||
| /* Reset to the master jail, required for chroot. */ | |||||||||||
| if (jail_reset) | |||||||||||
| ATF_REQUIRE_MSG(jail_attach_jd(jinfo[0].jfd) == 0, | |||||||||||
| "jail_attach_jd: %s", strerror(errno)); | |||||||||||
| } | |||||||||||
| /* It's an error if any of the rounds had a mismatch. */ | |||||||||||
| ATF_REQUIRE_MSG(mixed_jails == 0, | |||||||||||
| "%d of %d %s races with different root and " | |||||||||||
| "credentials", mixed_jails, NROUNDS, syscall_name); | |||||||||||
| } | |||||||||||
| #define JAIL_NAME "jail_thread_attach_test" | |||||||||||
Done Inline ActionsMost tests omit the whitespace between ATF_TC/ATC_TC_HEAD/ATF_TC_BODY. I think that makes them more readable. markj: Most tests omit the whitespace between ATF_TC/ATC_TC_HEAD/ATF_TC_BODY. I think that makes them… | |||||||||||
| ATF_TC(jail_thread_attach); | |||||||||||
| ATF_TC_HEAD(jail_thread_attach, tc) | |||||||||||
| { | |||||||||||
| atf_tc_set_md_var(tc, "require.user", "root"); | |||||||||||
| } | |||||||||||
| ATF_TC_BODY(jail_thread_attach, tc) | |||||||||||
| { | |||||||||||
| thread_attach_test(thread_jail_attach_jd, | |||||||||||
| "jail_thread_attach_test", "jail_attach_jd", false); | |||||||||||
| } | |||||||||||
| ATF_TC(jail_thread_attach_jail_set); | |||||||||||
| ATF_TC_HEAD(jail_thread_attach_jail_set, tc) | |||||||||||
| { | |||||||||||
| atf_tc_set_md_var(tc, "require.user", "root"); | |||||||||||
| } | |||||||||||
| ATF_TC_BODY(jail_thread_attach_jail_set, tc) | |||||||||||
| { | |||||||||||
| thread_attach_test(thread_jail_setv, | |||||||||||
| "jail_thread_attach_test_jail_set", "jail_set", false); | |||||||||||
| } | |||||||||||
| ATF_TC(jail_thread_attach_chroot); | |||||||||||
| ATF_TC_HEAD(jail_thread_attach_chroot, tc) | |||||||||||
| { | |||||||||||
| atf_tc_set_md_var(tc, "require.user", "root"); | |||||||||||
| } | |||||||||||
| ATF_TC_BODY(jail_thread_attach_chroot, tc) | |||||||||||
| { | |||||||||||
| thread_attach_test(thread_chroot, | |||||||||||
| "jail_thread_attach_test_chroot", "chroot", true); | |||||||||||
| } | |||||||||||
| ATF_TP_ADD_TCS(tp) | |||||||||||
| { | |||||||||||
| ATF_TP_ADD_TC(tp, jail_thread_attach); | |||||||||||
| ATF_TP_ADD_TC(tp, jail_thread_attach_jail_set); | |||||||||||
| ATF_TP_ADD_TC(tp, jail_thread_attach_chroot); | |||||||||||
| return (atf_no_error()); | |||||||||||
| } | |||||||||||
Could you please use unique jail names for each test? Otherwise the tests can't run in parallel.