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, ®) || 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
prev 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