OpenVZ / Virtuozzo kernel development (devel@openvz.org)
 help / color / mirror / Atom feed
From: Konstantin Khorenko <khorenko@virtuozzo.com>
To: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>,
	Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>,
	Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
Cc: devel@openvz.org
Subject: [Devel] [PATCH hci-8.1 7/7] selftests/mm: say why a doexec case fails
Date: Wed, 14 Oct 2026 19:15:05 +0200	[thread overview]
Message-ID: <20261014-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v1-7-b23b454691db@virtuozzo.com> (raw)
In-Reply-To: <20261014-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v1-0-b23b454691db@virtuozzo.com>

Many failure paths of doexec return KSFT_FAIL without a word, so a
failing case shows nothing but "not ok". Print the call that failed
with its errno, or what the check found, before failing.

https://virtuozzo.atlassian.net/browse/VSTOR-150234

Feature: oracle/mm: MADV_DOEXEC madvise() flag
Assisted-by: LLM
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 tools/testing/selftests/mm/doexec.c | 223 ++++++++++++++++++++++--------------
 1 file changed, 136 insertions(+), 87 deletions(-)

diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
index 1b33bc489eb71..f74471abe13e2 100644
--- a/tools/testing/selftests/mm/doexec.c
+++ b/tools/testing/selftests/mm/doexec.c
@@ -103,6 +103,8 @@ static int wait_child(pid_t pid)
 	do {
 		ret = waitpid(pid, &status, 0);
 	} while (ret < 0 && errno == EINTR);
+	if (ret >= 0 && WIFSIGNALED(status))
+		ksft_print_msg("child killed by signal %d\n", WTERMSIG(status));
 	if (ret < 0 || !WIFEXITED(status))
 		return KSFT_FAIL;
 	return WEXITSTATUS(status);
@@ -171,6 +173,13 @@ static int fail(const char *what)
 	return KSFT_FAIL;
 }
 
+static int fail_errno(const char *what)
+{
+	ksft_print_msg("%s: %s\n", what, strerror(errno));
+	fflush(stdout);
+	return KSFT_FAIL;
+}
+
 static int check_contents(char *ram, size_t size, unsigned char value)
 {
 	return check_bytes(ram, size, value) ? fail("contents lost") : KSFT_PASS;
@@ -179,14 +188,14 @@ static int check_contents(char *ram, size_t size, unsigned char value)
 static int after_cow_exec(char *ram, size_t size, pid_t sibling, int pipe_fd)
 {
 	char ready = 1;
-	int ret = check_bytes(ram, size, 0x31);
+	int ret = check_bytes(ram, size, 0x31) ? fail("contents lost") : KSFT_PASS;
 
 	memset(ram, 0x42, size);
 	if (write(pipe_fd, &ready, 1) != 1)
-		ret = KSFT_FAIL;
+		ret = fail("lost the COW sibling");
 	close(pipe_fd);
 	if (wait_child(sibling))
-		ret = KSFT_FAIL;
+		ret = fail("COW sibling saw the new image's writes");
 	return ret;
 }
 
@@ -202,20 +211,22 @@ static int before_cow_exec(void)
 	ram = mmap(NULL, size, PROT_READ | PROT_WRITE,
 		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 	if (ram == MAP_FAILED)
-		return KSFT_FAIL;
+		return fail_errno("mmap");
 	memset(ram, 0x31, size);
 	if (madvise(ram, size, MADV_DOEXEC))
 		return unsupported();
 	if (pipe(pipe_fd))
-		return KSFT_FAIL;
+		return fail_errno("pipe");
 	pid = fork();
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (!pid) {
 		close(pipe_fd[1]);
 		if (read(pipe_fd[0], &ready, 1) != 1)
 			_exit(KSFT_FAIL);
-		_exit(check_bytes(ram, size, 0x31));
+		if (check_bytes(ram, size, 0x31))
+			_exit(fail("COW sibling lost its contents"));
+		_exit(KSFT_PASS);
 	}
 	close(pipe_fd[0]);
 	snprintf(addr_arg, sizeof(addr_arg), "%llu",
@@ -225,6 +236,7 @@ static int before_cow_exec(void)
 	snprintf(fd_arg, sizeof(fd_arg), "%d", pipe_fd[1]);
 	execl("/proc/self/exe", "doexec", "--cow", addr_arg, size_arg,
 	      pid_arg, fd_arg, NULL);
+	fail_errno("exec");
 	close(pipe_fd[1]);
 	wait_child(pid);
 	return KSFT_FAIL;
@@ -242,8 +254,7 @@ static void exec_stage(int fd, char *ram, size_t size, int kind, int stage)
 	snprintf(stage_arg, sizeof(stage_arg), "%d", stage);
 	execl("/proc/self/exe", "doexec", "--exec", fd_arg, addr_arg, size_arg,
 	      kind_arg, stage_arg, NULL);
-	ksft_print_msg("exec: %s\n", strerror(errno));
-	_exit(KSFT_FAIL);
+	_exit(fail_errno("exec"));
 }
 
 static int after_exec(int fd, char *ram, size_t size, int kind, int stage)
@@ -255,33 +266,35 @@ static int after_exec(int fd, char *ram, size_t size, int kind, int stage)
 	if (check_pin(fd, ram, size, before))
 		return KSFT_FAIL;
 	if (kind == PROTECTED && mprotect(ram, size, PROT_READ | PROT_WRITE))
-		return KSFT_FAIL;
+		return fail_errno("mprotect");
 	memset(ram, after, size);
 	if (check_pin(fd, ram, size, after))
 		return KSFT_FAIL;
 	if (stage == 1) {
 		/* VM_EXEC_KEEP is consumed, so mark again for the second exec. */
 		if (madvise(ram, size, MADV_DOEXEC))
-			return KSFT_FAIL;
+			return fail_errno("MADV_DOEXEC");
 		if (kind == PROTECTED && mprotect(ram, size, PROT_READ))
-			return KSFT_FAIL;
+			return fail_errno("mprotect");
 		exec_stage(fd, ram, size, kind, 2);
 	}
 
 	/* Fork must copy into the child and retain the parent's pinned pages. */
 	pid = fork();
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (!pid) {
 		memset(ram, 0x64, size);
 		_exit(KSFT_PASS);
 	}
-	if (wait_child(pid) || check_pin(fd, ram, size, after))
+	if (wait_child(pid))
+		return fail("fork child of the new image failed");
+	if (check_pin(fd, ram, size, after))
 		return KSFT_FAIL;
 	memset(ram, 0x75, size);
 	if (check_pin(fd, ram, size, 0x75))
 		return KSFT_FAIL;
-	return ioctl(fd, PIN_LONGTERM_TEST_STOP) ? KSFT_FAIL : KSFT_PASS;
+	return ioctl(fd, PIN_LONGTERM_TEST_STOP) ? fail_errno("PIN_LONGTERM_TEST_STOP") : KSFT_PASS;
 }
 
 static size_t huge_page_size(void)
@@ -316,7 +329,7 @@ static int before_exec(int kind)
 	mapping = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,
 		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 	if (mapping == MAP_FAILED)
-		return KSFT_FAIL;
+		return fail_errno("mmap");
 	aligned = ((uintptr_t)mapping + size - 1) & ~(size - 1);
 	ram = (char *)aligned;
 	if (ram != mapping)
@@ -325,7 +338,7 @@ static int before_exec(int kind)
 		munmap(ram + size, mapping + 2 * size - (ram + size));
 	if (madvise(ram, size, kind == HUGE ? MADV_HUGEPAGE : MADV_NOHUGEPAGE) &&
 	    (kind == HUGE || errno != EINVAL))
-		return KSFT_FAIL;
+		return fail_errno("THP madvise");
 	memset(ram, 0x31, size);
 	if (kind == HUGE && madvise(ram, size, MADV_COLLAPSE))
 		return KSFT_SKIP;
@@ -345,7 +358,7 @@ static int before_exec(int kind)
 		return errno == ENOTTY ? KSFT_SKIP : KSFT_FAIL;
 	}
 	if (kind == PROTECTED && mprotect(ram, size, PROT_READ))
-		return KSFT_FAIL;
+		return fail_errno("mprotect");
 	exec_stage(fd, ram, size, kind, 1);
 	return KSFT_FAIL;
 }
@@ -366,7 +379,13 @@ static int exec_path(const char *exe, char *ram, size_t size, size_t stride,
 
 static int check_exec(char *ram, size_t size, size_t stride, bool reject)
 {
-	return exec_path("/proc/self/exe", ram, size, stride, reject);
+	int err = exec_path("/proc/self/exe", ram, size, stride, reject);
+
+	if (!reject) {
+		errno = err;
+		fail_errno("exec");
+	}
+	return err;
 }
 
 static char *preserved_ram(size_t size)
@@ -417,10 +436,10 @@ static int shared_mm_exec(bool own_thread)
 	if (!ram)
 		return unsupported();
 	if (pipe(pipe_fd))
-		return KSFT_FAIL;
+		return fail_errno("pipe");
 	if (own_thread) {
 		if (pthread_create(&thread, NULL, blocked_thread, &pipe_fd[0]))
-			return KSFT_FAIL;
+			return fail("pthread_create failed");
 		check_exec(ram, size, 1, false);
 		write(pipe_fd[1], "x", 1);
 		pthread_join(thread, NULL);
@@ -429,16 +448,18 @@ static int shared_mm_exec(bool own_thread)
 	stack = mmap(NULL, 65536, PROT_READ | PROT_WRITE,
 		     MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
 	if (stack == MAP_FAILED)
-		return KSFT_FAIL;
+		return fail_errno("mmap");
 	pid = clone(blocked_clone, stack + 65536, CLONE_VM | SIGCHLD, pipe_fd);
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("clone");
 	ret = check_exec(ram, size, 1, true);
 	/* Exec must return EBUSY with the old mapping intact. */
-	if (ret != EBUSY || check_bytes(ram, size, 0x31))
-		ret = KSFT_FAIL;
-	else
-		ret = KSFT_PASS;
+	if (ret != EBUSY) {
+		errno = ret;
+		ret = fail_errno("exec with a CLONE_VM task not refused with EBUSY");
+	} else {
+		ret = check_bytes(ram, size, 0x31) ? fail("contents lost") : KSFT_PASS;
+	}
 	write(pipe_fd[1], "x", 1);
 	if (wait_child(pid))
 		ret = KSFT_FAIL;
@@ -464,7 +485,7 @@ static int vhost_owner_exec(void)
 	if (ioctl(fd, VHOST_SET_OWNER))
 		return KSFT_SKIP;
 	if (ioctl(fd, VHOST_RESET_OWNER))
-		return KSFT_FAIL;
+		return fail_errno("VHOST_RESET_OWNER");
 	close(fd);
 	check_exec(ram, size, 1, false);
 	return KSFT_FAIL;
@@ -520,7 +541,7 @@ static int unprivileged_exec(const char *path, bool preserve)
 	int err;
 
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (pid)
 		return wait_child(pid);
 	if (setgroups(0, NULL) || setresgid(65534, 65534, 65534) ||
@@ -535,12 +556,16 @@ static int unprivileged_exec(const char *path, bool preserve)
 		_exit(unsupported());
 	err = exec_path(path, ram, size, 1, true);
 	/* Exec must return EPERM with the old mapping intact. */
-	if (err != EPERM || check_bytes(ram, size, 0x31))
-		_exit(KSFT_FAIL);
+	if (err != EPERM) {
+		errno = err;
+		_exit(fail_errno("setuid exec not refused with EPERM"));
+	}
+	if (check_bytes(ram, size, 0x31))
+		_exit(fail("contents lost by a refused setuid exec"));
 	if (madvise(ram, size, MADV_DONTEXEC))
-		_exit(KSFT_FAIL);
+		_exit(fail_errno("MADV_DONTEXEC"));
 	execl(path, "doexec", "--euid", NULL);
-	_exit(KSFT_FAIL);
+	_exit(fail_errno("exec of the setuid copy"));
 }
 
 /* The ELF opt-in must not pull memory into a more privileged image. */
@@ -575,7 +600,7 @@ static int devzero_exec(void)
 	ram = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
 	close(fd);
 	if (ram == MAP_FAILED)
-		return KSFT_FAIL;
+		return fail_errno("mmap");
 	memset(ram, 0x31, size);
 	if (madvise(ram, size, MADV_DOEXEC))
 		return unsupported();
@@ -655,6 +680,8 @@ static int noexec_dontexec(void)
 	}
 	if (!madvise(ram, size, MADV_DONTEXEC) && exec_refused(ram, size))
 		ret = KSFT_PASS;
+	else
+		ret = fail("MADV_DONTEXEC made a noexec mapping executable");
 out:
 	umount2(dir, MNT_DETACH);
 	rmdir(dir);
@@ -670,9 +697,13 @@ static int doexec_noexec(void)
 
 	if (!ram)
 		return unsupported();
-	if (!exec_refused(ram, size) || madvise(ram, size, MADV_DONTEXEC) ||
-	    mprotect(ram, size, PROT_READ | PROT_EXEC))
-		return KSFT_FAIL;
+	if (!exec_refused(ram, size))
+		return fail("marked range can be made executable");
+	if (madvise(ram, size, MADV_DONTEXEC) || !smaps_has_flag(ram, " me "))
+		return fail("MADV_DONTEXEC did not give VM_MAYEXEC back");
+	/* SELinux execmem or MDWE may still refuse it. */
+	if (mprotect(ram, size, PROT_READ | PROT_EXEC))
+		return KSFT_SKIP;
 	ret = noexec_dontexec();
 	if (ret == KSFT_SKIP)
 		ksft_print_msg("noexec mount part skipped\n");
@@ -684,11 +715,11 @@ static int after_pfnmap_exec(char *bar, size_t size)
 	size_t page = sysconf(_SC_PAGESIZE), i;
 
 	if (!smaps_has_flag(bar, " pf ") || !smaps_has_flag(bar, " sh "))
-		return KSFT_FAIL;
+		return fail("BAR not a shared PFN map after exec");
 	for (i = 0; i < size / page; i++)
 		if (__atomic_load_n((uint32_t *)(bar + i * page), __ATOMIC_RELAXED) !=
 		    (0xd0e00000 | i))
-			return KSFT_FAIL;
+			return fail("BAR contents changed across exec");
 	return munmap(bar, size) ? KSFT_FAIL : KSFT_PASS;
 }
 
@@ -709,7 +740,9 @@ static int devmem_refused(void)
 	close(fd);
 	if (ram == MAP_FAILED || !smaps_has_flag(ram, " pf "))
 		return KSFT_SKIP;
-	return madvise(ram, size, MADV_DOEXEC) && errno == EINVAL ? KSFT_PASS : KSFT_FAIL;
+	if (madvise(ram, size, MADV_DOEXEC) && errno == EINVAL)
+		return KSFT_PASS;
+	return fail("MADV_DOEXEC on a shared /dev/mem mapping not refused");
 }
 
 static int vfio_device_fd(const char *bdf)
@@ -764,12 +797,12 @@ static int vfio_bar_exec(void)
 	/* Align to the size so that the BAR can be mapped with huge PFN entries. */
 	area = mmap(NULL, 2 * size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 	if (area == MAP_FAILED)
-		return KSFT_FAIL;
+		return fail_errno("mmap");
 	aligned = ((uintptr_t)area + size - 1) & ~(size - 1);
 	bar = mmap((void *)aligned, size, PROT_READ | PROT_WRITE,
 		   MAP_SHARED | MAP_FIXED, dev, info.offset);
 	if (bar == MAP_FAILED)
-		return KSFT_FAIL;
+		return fail_errno("mmap");
 	for (i = 0; i < size / page; i++)
 		__atomic_store_n((uint32_t *)(bar + i * page), 0xd0e00000 | i,
 				 __ATOMIC_RELAXED);
@@ -781,7 +814,7 @@ static int vfio_bar_exec(void)
 		 (unsigned long long)(uintptr_t)bar);
 	snprintf(size_arg, sizeof(size_arg), "%zu", size);
 	execl("/proc/self/exe", "doexec", "--pfnmap", addr_arg, size_arg, NULL);
-	return KSFT_FAIL;
+	return fail_errno("exec");
 }
 
 static void *exec_thread(void *arg)
@@ -798,7 +831,7 @@ static int nonleader_exec(void)
 	if (!ram)
 		return unsupported();
 	if (pthread_create(&thread, NULL, exec_thread, ram))
-		return KSFT_FAIL;
+		return fail("pthread_create failed");
 	pthread_join(thread, NULL);
 	return KSFT_FAIL;
 }
@@ -808,18 +841,18 @@ static int proc_exec(char *ram, size_t size, int stage)
 	char addr_arg[32], size_arg[32], stage_arg[32];
 
 	if (check_bytes(ram, size, 0x31))
-		return KSFT_FAIL;
+		return fail("contents lost in a repeated exec");
 	if (stage == 32)
 		return KSFT_PASS;
 	if (madvise(ram, size, MADV_DOEXEC))
-		return KSFT_FAIL;
+		return fail_errno("MADV_DOEXEC");
 	snprintf(addr_arg, sizeof(addr_arg), "%llu",
 		 (unsigned long long)(uintptr_t)ram);
 	snprintf(size_arg, sizeof(size_arg), "%zu", size);
 	snprintf(stage_arg, sizeof(stage_arg), "%d", stage + 1);
 	execl("/proc/self/exe", "doexec", "--proc", addr_arg, size_arg,
 	      stage_arg, NULL);
-	return KSFT_FAIL;
+	return fail_errno("exec");
 }
 
 /* Read both pre-opened and freshly opened proc files during repeated exec. */
@@ -833,10 +866,10 @@ static int proc_readers_exec(void)
 	int i;
 
 	if (pipe(ready) || pipe(go))
-		return KSFT_FAIL;
+		return fail_errno("pipe");
 	pid = fork();
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (!pid) {
 		size_t size = 16 * sysconf(_SC_PAGESIZE);
 		char *ram = preserved_ram(size);
@@ -874,7 +907,8 @@ static int proc_readers_exec(void)
 	for (;;) {
 		waited = waitpid(pid, &status, WNOHANG);
 		if (waited == pid) {
-			ret = WIFEXITED(status) ? WEXITSTATUS(status) : KSFT_FAIL;
+			ret = WIFEXITED(status) ? WEXITSTATUS(status) :
+			      fail("repeated exec killed by a signal");
 			break;
 		}
 		if (waited < 0 && errno != EINTR)
@@ -968,13 +1002,14 @@ static int clone_exec_race(void)
 	pid_t pid;
 
 	if (pipe(report))
-		return KSFT_FAIL;
+		return fail_errno("pipe");
 	pid = fork();
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (!pid) {
 		size_t size = sysconf(_SC_PAGESIZE);
 		struct clone_race race = { .report = report[1] };
+		const char *why = "exec refused with EBUSY every time";
 		pthread_t thread;
 		unsigned int attempt;
 
@@ -983,25 +1018,31 @@ static int clone_exec_race(void)
 		if (!race.ram)
 			_exit(unsupported());
 		if (pthread_create(&thread, NULL, clone_race_thread, &race))
-			_exit(KSFT_FAIL);
+			_exit(fail("pthread_create failed"));
 		while (atomic_load(&race.rounds) < 4)
 			usleep(1000);
 		for (attempt = 0; attempt < 500; attempt++) {
-			if (check_exec(race.ram, size, 1, false) != EBUSY)
+			errno = exec_path("/proc/self/exe", race.ram, size, 1, false);
+			if (errno != EBUSY) {
+				why = NULL;
+				fail_errno("exec");
 				break;
-			if (check_bytes(race.ram, size, 0x31))
+			}
+			if (check_bytes(race.ram, size, 0x31)) {
+				why = "contents changed while exec was refused";
 				break;
+			}
 			usleep(1000);
 		}
 		atomic_store(&race.stop, true);
 		pthread_join(thread, NULL);
-		_exit(KSFT_FAIL);
+		_exit(why ? fail(why) : KSFT_FAIL);
 	}
 	close(report[1]);
 	ret = wait_child(pid);
 	pfd = (struct pollfd) { .fd = report[0], .events = POLLIN | POLLHUP };
 	if (poll(&pfd, 1, 3000) <= 0 || read(report[0], &failed, 1) != 0)
-		ret = KSFT_FAIL;
+		ret = fail("a racing CLONE_VM clone failed");
 	close(report[0]);
 	return ret;
 }
@@ -1041,7 +1082,7 @@ static int allocation_failures(void)
 	for (nth = 1; nth <= 256; nth++) {
 		pid = fork();
 		if (pid < 0)
-			return KSFT_FAIL;
+			return fail_errno("fork");
 		if (!pid) {
 			char *ram, number[32];
 			size_t i;
@@ -1052,7 +1093,7 @@ static int allocation_failures(void)
 				   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 			if (ram == MAP_FAILED ||
 			    (madvise(ram, size, MADV_NOHUGEPAGE) && errno != EINVAL))
-				_exit(KSFT_FAIL);
+				_exit(fail_errno("mmap or MADV_NOHUGEPAGE"));
 			for (i = 0; i < size; i += stride)
 				ram[i] = 0x31;
 			if (madvise(ram, size, MADV_DOEXEC))
@@ -1062,13 +1103,13 @@ static int allocation_failures(void)
 			fd = open("/proc/self/fail-nth", O_WRONLY | O_CLOEXEC);
 			len = snprintf(number, sizeof(number), "%u", nth);
 			if (exe < 0 || fd < 0 || write(fd, number, len) != len)
-				_exit(KSFT_FAIL);
+				_exit(fail_errno("fail-nth setup"));
 			err = check_exec_fd(exe, ram, size, stride);
 			/* Disarm before checking mappings or exiting. */
 			pwrite(fd, "0", 1, 0);
 			for (i = 0; i < size; i += stride)
 				if (ram[i] != 0x31)
-					_exit(KSFT_FAIL);
+					_exit(fail("contents lost by a failed exec"));
 			/*
 			 * Failed allocations also surface as E2BIG from
 			 * copy_strings(), and as ELIBBAD or EIO from reading the
@@ -1084,7 +1125,7 @@ static int allocation_failures(void)
 			waited = waitpid(pid, &status, 0);
 		} while (waited < 0 && errno == EINTR);
 		if (waited < 0)
-			return KSFT_FAIL;
+			return fail_errno("waitpid");
 		if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)
 			fatal++;
 		else if (WIFEXITED(status) && WEXITSTATUS(status) == 10)
@@ -1141,9 +1182,11 @@ static int sysctl_flag(void)
 static bool doexec_refused(char *addr, size_t size, const char *what)
 {
 	if (addr == MAP_FAILED) {
-		ksft_print_msg("%s: mmap: %s\n", what, strerror(errno));
 		/* A security policy may forbid the mapping itself. */
-		return errno == EACCES || errno == EPERM;
+		if (errno == EACCES || errno == EPERM)
+			return true;
+		ksft_print_msg("%s: mmap: %s\n", what, strerror(errno));
+		return false;
 	}
 	if (madvise(addr, size, MADV_DOEXEC) && errno == EINVAL)
 		return true;
@@ -1227,7 +1270,7 @@ static int partial_exec(void)
 			 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 
 	if (ram == MAP_FAILED)
-		return KSFT_FAIL;
+		return fail_errno("mmap");
 	memset(ram, 0x31, 3 * page);
 	if (madvise(ram + page, page, MADV_DOEXEC))
 		return unsupported();
@@ -1334,11 +1377,13 @@ static int after_fork_flag(char *ram, size_t size, const char *flag, bool wipe)
 		return fail("contents lost");
 	pid = fork();
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (!pid) {
-		if (wipe)
-			_exit(range_mapped(ram, size) ? check_bytes(ram, size, 0) : KSFT_FAIL);
-		_exit(range_mapped(ram, size) ? KSFT_FAIL : KSFT_PASS);
+		if (wipe && (!range_mapped(ram, size) || check_bytes(ram, size, 0)))
+			_exit(fail("child of the new image did not get a wiped range"));
+		if (!wipe && range_mapped(ram, size))
+			_exit(fail("child of the new image got a DONTFORK range"));
+		_exit(KSFT_PASS);
 	}
 	return wait_child(pid);
 }
@@ -1581,7 +1626,7 @@ static int copy_control(void)
 	int exe;
 
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (!pid) {
 		ram = preserved_ram(size);
 		if (!ram)
@@ -1604,7 +1649,7 @@ static int no_note_exec(void)
 
 	err = copy_control();
 	if (err)
-		return err == KSFT_SKIP ? KSFT_SKIP : fail("unpatched copy lost the memory");
+		return err == KSFT_SKIP ? KSFT_SKIP : fail("unpatched copy did not keep the memory");
 	ram = preserved_ram(size);
 	if (!ram)
 		return unsupported();
@@ -1732,7 +1777,7 @@ static int stack_overlap_exec(void)
 	/* ...within the 128K the stack is grown by, it kills the task. */
 	status = stack_exec(16);
 	if (status < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork or waitpid");
 	if (WIFEXITED(status))
 		return WEXITSTATUS(status) == KSFT_SKIP ? KSFT_SKIP :
 			fail("exec did not kill the task");
@@ -1757,7 +1802,7 @@ static int sparse_exec(void)
 		return KSFT_SKIP;
 	/* EINVAL: no THP support, the range is mapped with base pages anyway. */
 	if (madvise(ram, size, MADV_NOHUGEPAGE) && errno != EINVAL)
-		return KSFT_FAIL;
+		return fail_errno("MADV_NOHUGEPAGE");
 	for (i = 0; i < size; i += stride)
 		ram[i] = 0x31;
 	if (madvise(ram, size, MADV_DOEXEC))
@@ -1790,7 +1835,7 @@ static int swapped_exec(void)
 
 	if (ram == MAP_FAILED ||
 	    (madvise(ram, size, MADV_NOHUGEPAGE) && errno != EINVAL))
-		return KSFT_FAIL;
+		return fail_errno("mmap or MADV_NOHUGEPAGE");
 	memset(ram, 0x31, size);
 	if (madvise(ram, size, MADV_PAGEOUT))
 		return KSFT_SKIP;
@@ -1824,7 +1869,7 @@ static int thp_half_exec(void)
 	area = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,
 		    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 	if (area == MAP_FAILED)
-		return KSFT_FAIL;
+		return fail_errno("mmap");
 	ram = (char *)(((uintptr_t)area + size - 1) & ~(size - 1));
 	if (madvise(ram, size, MADV_HUGEPAGE))
 		return KSFT_SKIP;
@@ -1873,7 +1918,7 @@ static int after_old_mm(char *ram, size_t size, int done, int check)
 	char c;
 
 	if (write(done, "x", 1) != 1 || read(check, &c, 1) != 1)
-		return KSFT_FAIL;
+		return fail("lost the parent of the old mm write case");
 	if (check_bytes(ram, size, 0x31))
 		return fail("a write through the old mm reached the new image");
 	return KSFT_PASS;
@@ -1920,11 +1965,11 @@ static int old_mm_write_exec(void)
 	reg.range.len = size;
 	if (w.src == MAP_FAILED || fill == MAP_FAILED || ioctl(uffd, UFFDIO_API, &api) ||
 	    ioctl(uffd, UFFDIO_REGISTER, &reg) || pipe(to_child) || pipe(to_parent))
-		return KSFT_FAIL;
+		return fail_errno("userfaultfd setup");
 	memset(fill, 0x77, size);
 	pid = fork();
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (!pid) {
 		char *ram = preserved_ram(size), args[4][32];
 
@@ -1942,7 +1987,7 @@ static int old_mm_write_exec(void)
 		snprintf(args[3], sizeof(args[3]), "%d", to_child[0]);
 		execl("/proc/self/exe", "doexec", "--old-mm", args[0], args[1], args[2],
 		      args[3], NULL);
-		_exit(KSFT_FAIL);
+		_exit(fail_errno("exec"));
 	}
 	close(to_child[0]);
 	close(to_parent[1]);
@@ -1957,8 +2002,10 @@ static int old_mm_write_exec(void)
 		ret = KSFT_SKIP;
 		goto out;
 	}
-	if (pthread_create(&thread, NULL, mem_writer, &w))
+	if (pthread_create(&thread, NULL, mem_writer, &w)) {
+		ret = fail("pthread_create failed");
 		goto out;
+	}
 	/* The writer holds the child's mm while it faults on its source. */
 	pfd.fd = uffd;
 	pfd.events = POLLIN;
@@ -2009,7 +2056,7 @@ static int vhost_kthread_exec(void)
 	if (err != EBUSY || check_bytes(ram, size, 0x31))
 		return fail("preserving exec with a vhost kthread not refused");
 	if (ioctl(fd, VHOST_RESET_OWNER))
-		return KSFT_FAIL;
+		return fail_errno("VHOST_RESET_OWNER");
 	close(fd);
 	check_exec(ram, size, 1, false);
 	return KSFT_FAIL;
@@ -2044,10 +2091,10 @@ static int vfork_exec(void)
 		_exit(errno == EBUSY ? REFUSED : KSFT_FAIL);
 	}
 	if (pid < 0)
-		return KSFT_FAIL;
+		return fail_errno("fork");
 	if (wait_child(pid) != REFUSED)
 		return fail("preserving exec from a vfork() child not refused");
-	return check_bytes(ram, size, 0x31) ? KSFT_FAIL : KSFT_PASS;
+	return check_bytes(ram, size, 0x31) ? fail("contents lost by a refused exec") : KSFT_PASS;
 }
 
 static const struct more_case more_cases[] = {
@@ -2083,7 +2130,7 @@ int main(int argc, char **argv)
 	/* The setuid copy only reports that it runs as root. */
 	if (geteuid() != getuid())
 		return argc == 2 && !strcmp(argv[1], "--euid") && !geteuid() ?
-		       KSFT_PASS : KSFT_FAIL;
+		       KSFT_PASS : fail("setuid copy started in another mode");
 	if (argc == 7 && !strcmp(argv[1], "--exec"))
 		return after_exec(atoi(argv[2]),
 			(char *)(uintptr_t)strtoull(argv[3], NULL, 10),
@@ -2092,7 +2139,7 @@ int main(int argc, char **argv)
 		return after_cow_exec((char *)(uintptr_t)strtoull(argv[2], NULL, 10),
 			strtoull(argv[3], NULL, 10), atoi(argv[4]), atoi(argv[5]));
 	if (argc == 5 && !strcmp(argv[1], "--reject"))
-		return KSFT_FAIL;
+		return fail("exec went through instead of failing");
 	for (kind = 0; argc == 4 && kind < (int)ARRAY_SIZE(after_modes); kind++)
 		if (!strcmp(argv[1], after_modes[kind].mode))
 			return after_modes[kind].fn((char *)(uintptr_t)strtoull(argv[2], NULL, 10),
@@ -2121,8 +2168,10 @@ int main(int argc, char **argv)
 		if (!range_mapped(ram, size))
 			return fail("range not preserved");
 		for (i = 0; i < size; i += stride)
-			if (ram[i] != 0x31)
-				return KSFT_FAIL;
+			if (ram[i] != 0x31) {
+				ksft_print_msg("byte %zu: %#x\n", i, (unsigned char)ram[i]);
+				return fail("preserved contents changed");
+			}
 		return KSFT_PASS;
 	}
 	ksft_print_header();

-- 
2.43.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

      parent reply	other threads:[~2026-10-14 17:16 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 17:14 [Devel] [PATCH hci-8.1 0/7] selftests/mm: broader MADV_DOEXEC coverage Konstantin Khorenko
2026-10-14 17:14 ` [Devel] [PATCH hci-8.1 1/7] selftests/mm: cover the MADV_DOEXEC interface and VMA attributes Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 2/7] selftests/mm: cover the MADV_DOEXEC opt-in note and the new stack Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 3/7] selftests/mm: cover large, swapped and partly marked THP ranges Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 4/7] selftests/mm: check that MADV_DOEXEC refuses an exec from a vfork() child Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 5/7] selftests/mm: check VM_MAYEXEC of MADV_DOEXEC ranges in smaps Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 6/7] selftests/mm: check who can still reach the old mm of a preserving exec Konstantin Khorenko
2026-10-14 17:15 ` Konstantin Khorenko [this message]

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=20261014-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v1-7-b23b454691db@virtuozzo.com \
    --to=khorenko@virtuozzo.com \
    --cc=andrey.drobyshev@virtuozzo.com \
    --cc=devel@openvz.org \
    --cc=ptikhomirov@virtuozzo.com \
    --cc=vasileios.almpanis@virtuozzo.com \
    /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