Date: Tue, 09 Jun 2026 19:19:13 +0000 From: Mark Johnston <markj@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org Subject: git: 547fc2a98a24 - releng/14.4 - imgact_elf: Clear no-ASLR and -WXORX flags earlier for setugid images Message-ID: <6a286731.3c5ff.2dc8d33d@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch releng/14.4 has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=547fc2a98a24129b12c573531130630f162c1cdd commit 547fc2a98a24129b12c573531130630f162c1cdd Author: Mark Johnston <markj@FreeBSD.org> AuthorDate: 2026-06-02 20:29:00 +0000 Commit: Mark Johnston <markj@FreeBSD.org> CommitDate: 2026-06-08 13:21:34 +0000 imgact_elf: Clear no-ASLR and -WXORX flags earlier for setugid images Otherwise an unprivileged user can disable randomization of the base address for PIEs even if they are setugid. Add a regression test. Approved by: so Security: FreeBSD-SA-26:32.elf Security: CVE-2026-49414 Reported by: David Berard Reviewed by: kib Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D57397 --- sys/kern/imgact_elf.c | 55 ++++++++--------- tests/sys/kern/Makefile | 2 + tests/sys/kern/aslr.c | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 27 deletions(-) diff --git a/sys/kern/imgact_elf.c b/sys/kern/imgact_elf.c index ea74c07cbc48..df9156ad76ca 100644 --- a/sys/kern/imgact_elf.c +++ b/sys/kern/imgact_elf.c @@ -1230,11 +1230,39 @@ __CONCAT(exec_, __elfN(imgact))(struct image_params *imgp) error = ENOEXEC; goto ret; } + + /* + * Avoid a possible deadlock if the current address space is destroyed + * and that address space maps the locked vnode. In the common case, + * the locked vnode's v_usecount is decremented but remains greater + * than zero. Consequently, the vnode lock is not needed by vrele(). + * However, in cases where the vnode lock is external, such as nullfs, + * v_usecount may become zero. + * + * The VV_TEXT flag prevents modifications to the executable while + * the vnode is unlocked. + */ + VOP_UNLOCK(imgp->vp); + + /* + * Decide whether to enable randomization of user mappings. First, + * reset user preferences for the setid binaries. Then, account for the + * support of randomization by the ABI, by user preferences, and make + * special treatment for PIE binaries. + */ + if (imgp->credential_setid) { + PROC_LOCK(imgp->proc); + imgp->proc->p_flag2 &= ~(P2_ASLR_ENABLE | P2_ASLR_DISABLE | + P2_WXORX_DISABLE | P2_WXORX_ENABLE_EXEC); + PROC_UNLOCK(imgp->proc); + } + sv = brand_info->sysvec; if (hdr->e_type == ET_DYN) { if ((brand_info->flags & BI_CAN_EXEC_DYN) == 0) { uprintf("Cannot execute shared object\n"); error = ENOEXEC; + (void)vn_lock(imgp->vp, LK_SHARED | LK_RETRY); goto ret; } /* @@ -1253,33 +1281,6 @@ __CONCAT(exec_, __elfN(imgact))(struct image_params *imgp) imgp->et_dyn_addr = __elfN(pie_base); } } - - /* - * Avoid a possible deadlock if the current address space is destroyed - * and that address space maps the locked vnode. In the common case, - * the locked vnode's v_usecount is decremented but remains greater - * than zero. Consequently, the vnode lock is not needed by vrele(). - * However, in cases where the vnode lock is external, such as nullfs, - * v_usecount may become zero. - * - * The VV_TEXT flag prevents modifications to the executable while - * the vnode is unlocked. - */ - VOP_UNLOCK(imgp->vp); - - /* - * Decide whether to enable randomization of user mappings. - * First, reset user preferences for the setid binaries. - * Then, account for the support of the randomization by the - * ABI, by user preferences, and make special treatment for - * PIE binaries. - */ - if (imgp->credential_setid) { - PROC_LOCK(imgp->proc); - imgp->proc->p_flag2 &= ~(P2_ASLR_ENABLE | P2_ASLR_DISABLE | - P2_WXORX_DISABLE | P2_WXORX_ENABLE_EXEC); - PROC_UNLOCK(imgp->proc); - } if ((sv->sv_flags & SV_ASLR) == 0 || (imgp->proc->p_flag2 & P2_ASLR_DISABLE) != 0 || (fctl0 & NT_FREEBSD_FCTL_ASLR_DISABLE) != 0) { diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile index 3dcfbc71f6e5..67cdf19f6d6e 100644 --- a/tests/sys/kern/Makefile +++ b/tests/sys/kern/Makefile @@ -8,6 +8,7 @@ TESTSRC= ${SRCTOP}/contrib/netbsd-tests/kernel TESTSDIR= ${TESTSBASE}/sys/kern +ATF_TESTS_C+= aslr ATF_TESTS_C+= basic_signal ATF_TESTS_C+= copy_file_range .if ${MACHINE_ARCH} != "i386" && ${MACHINE_ARCH} != "powerpc" && \ @@ -75,6 +76,7 @@ PROGS+= coredump_phnum_helper PROGS+= pdeathsig_helper PROGS+= sendfile_helper +LIBADD.aslr+= util LIBADD.copy_file_range+= md LIBADD.jail_lookup_root+= jail util CFLAGS.sys_getrandom+= -I${SRCTOP}/sys/contrib/zstd/lib diff --git a/tests/sys/kern/aslr.c b/tests/sys/kern/aslr.c new file mode 100644 index 000000000000..13038054603c --- /dev/null +++ b/tests/sys/kern/aslr.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2026 The FreeBSD Foundation + * + * This software was developed by Mark Johnston under sponsorship from the + * FreeBSD Foundation. + */ + +#include <sys/param.h> +#include <sys/procctl.h> +#include <sys/stat.h> +#include <sys/user.h> +#include <sys/wait.h> + +#include <libutil.h> +#include <pwd.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <atf-c.h> + +/* + * Spawn an unprivileged child with ASLR force-disabled, which then execs + * /sbin/ping (setuid root). + */ +static pid_t +spawn_ping(const atf_tc_t *tc) +{ + const char *user; + struct passwd *passwd; + pid_t child; + int arg, error; + + user = atf_tc_get_config_var(tc, "unprivileged_user"); + passwd = getpwnam(user); + ATF_REQUIRE(passwd != NULL); + + child = fork(); + ATF_REQUIRE(child >= 0); + if (child == 0) { + if (seteuid(passwd->pw_uid) != 0) + _exit(1); + + arg = PROC_ASLR_FORCE_DISABLE; + error = procctl(P_PID, getpid(), PROC_ASLR_CTL, &arg); + if (error != 0) + _exit(2); + + execl("/sbin/ping", "ping", "127.0.0.1", NULL); + _exit(127); + } + usleep(500000); /* XXX-MJ */ + + return (child); +} + +/* + * Return the base address of the first mapping backed by the specified + * executable in the given process, or 0 if not found. + */ +static uint64_t +text_base(pid_t pid, const char *path) +{ + struct kinfo_vmentry *vmmap; + uint64_t base; + int cnt; + + base = 0; + vmmap = kinfo_getvmmap(pid, &cnt); + if (vmmap == NULL) + return (0); + for (int i = 0; i < cnt; i++) { + if (vmmap[i].kve_type == KVME_TYPE_VNODE && + strcmp(vmmap[i].kve_path, path) == 0) { + base = vmmap[i].kve_start; + break; + } + } + free(vmmap); + return (base); +} + +/* + * Make sure that ASLR can't be disabled for a setuid executable by an + * unprivileged user. + */ +ATF_TC(aslr_setuid); +ATF_TC_HEAD(aslr_setuid, tc) +{ + atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.config", "unprivileged_user"); +} +ATF_TC_BODY(aslr_setuid, tc) +{ + struct stat sb; + uint64_t bases[5]; + pid_t child, pid; + int arg, error, st; + + if (!atf_tc_has_config_var(tc, "unprivileged_user")) + atf_tc_skip("unprivileged_user not set"); + + error = stat("/sbin/ping", &sb); + ATF_REQUIRE(error == 0); + ATF_REQUIRE_MSG(sb.st_uid == 0 && (sb.st_mode & S_ISUID) != 0, + "/sbin/ping is not setuid root"); + + child = spawn_ping(tc); + bases[0] = text_base(child, "/sbin/ping"); + ATF_REQUIRE_MSG(bases[0] != 0, + "failed to find /sbin/ping text segment"); + + arg = 0; + error = procctl(P_PID, child, PROC_ASLR_STATUS, &arg); + ATF_REQUIRE_MSG(error == 0, "procctl ASLR_STATUS failed: %s", + strerror(errno)); + ATF_REQUIRE_MSG((arg & PROC_ASLR_ACTIVE) != 0, + "ASLR is not active for setuid child"); + ATF_REQUIRE_MSG((arg & ~PROC_ASLR_ACTIVE) == PROC_ASLR_NOFORCE, + "expected NOFORCE for setuid child, got %d", + arg & ~PROC_ASLR_ACTIVE); + + error = kill(child, SIGTERM); + ATF_REQUIRE(error == 0); + pid = waitpid(child, &st, 0); + ATF_REQUIRE(pid == child); + ATF_REQUIRE(WIFSIGNALED(st) && WTERMSIG(st) == SIGTERM); + + for (size_t i = 1; i < nitems(bases); i++) { + child = spawn_ping(tc); + bases[i] = text_base(child, "/sbin/ping"); + ATF_REQUIRE_MSG(bases[i] != 0, + "failed to find /sbin/ping text segment"); + error = kill(child, SIGTERM); + ATF_REQUIRE(error == 0); + pid = waitpid(child, &st, 0); + ATF_REQUIRE(pid == child); + ATF_REQUIRE(WIFSIGNALED(st) && WTERMSIG(st) == SIGTERM); + } + + /* Verify that the text base is different across all runs. */ + for (size_t i = 0; i < nitems(bases); i++) { + for (size_t j = i + 1; j < nitems(bases); j++) { + ATF_REQUIRE_MSG(bases[i] != bases[j], + "ping text base collision 0x%jx", + (uintmax_t)bases[i]); + } + } +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, aslr_setuid); + + return (atf_no_error()); +}home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a286731.3c5ff.2dc8d33d>
