All Virtuozzo development lists (kernel + QEMU)
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.