[Devel] [PATCH hci-8.1 v2 7/9] selftests/mm: say why a doexec case fails
Konstantin Khorenko
khorenko at virtuozzo.com
Wed Oct 21 19:57:20 MSK 2026
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.
When a page that should be gone after exec is still mapped, also print
the smaps entry that covers it and its first byte. The test fills
preserved pages with 0x31, so the output tells whether the kernel
carried the page over or the new image mapped something else at that
address.
The VFIO BAR case is skipped unless DOEXEC_VFIO_DEVICE names a device,
so say how to enable it, in the skip and in the run_vmtests.sh help.
https://virtuozzo.atlassian.net/browse/VSTOR-150234
Feature: oracle/mm: MADV_DOEXEC madvise() flag
Assisted-by: LLM
Signed-off-by: Konstantin Khorenko <khorenko at virtuozzo.com>
---
tools/testing/selftests/mm/doexec.c | 278 ++++++++++++++++++++----------
tools/testing/selftests/mm/run_vmtests.sh | 4 +-
2 files changed, 187 insertions(+), 95 deletions(-)
diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
index c9f9f9f1befe5..25005e98fdd1d 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");
@@ -685,20 +716,22 @@ static int after_pfnmap_exec(char *bar, size_t size)
char addr_arg[32];
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");
/* The copy is not marked, so the next exec has to drop it. */
snprintf(addr_arg, sizeof(addr_arg), "%llu", (unsigned long long)(uintptr_t)bar);
execl("/proc/self/exe", "doexec", "--pfnmap-gone", addr_arg, NULL);
- return KSFT_FAIL;
+ return fail_errno("exec");
}
static int after_pfnmap_gone(char *bar)
{
- return smaps_has_flag(bar, " pf ") ? KSFT_FAIL : KSFT_PASS;
+ if (smaps_has_flag(bar, " pf "))
+ return fail("BAR still mapped after an exec without the mark");
+ return KSFT_PASS;
}
/* Only VFIO device mappings may be preserved; /dev/mem is a plain PFN map. */
@@ -718,7 +751,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)
@@ -759,8 +794,10 @@ static int vfio_bar_exec(bool ptes)
uintptr_t aligned;
int dev;
- if (!bdf)
+ if (!bdf) {
+ ksft_print_msg("set DOEXEC_VFIO_DEVICE to a spare PCI device bound to vfio-pci\n");
return KSFT_SKIP;
+ }
if (!preserved_ram(page))
return unsupported();
dev = vfio_device_fd(bdf);
@@ -776,12 +813,12 @@ static int vfio_bar_exec(bool ptes)
/* Size-aligned: huge PFN entries, faulted after exec; a page off: PTEs, copied. */
area = mmap(NULL, 2 * size + page, 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)) + (ptes ? page : 0);
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);
@@ -793,7 +830,7 @@ static int vfio_bar_exec(bool ptes)
(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)
@@ -810,7 +847,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;
}
@@ -820,18 +857,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. */
@@ -845,10 +882,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);
@@ -886,7 +923,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)
@@ -980,13 +1018,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;
@@ -995,25 +1034,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;
}
@@ -1053,7 +1098,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;
@@ -1064,7 +1109,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))
@@ -1074,13 +1119,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
@@ -1096,7 +1141,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)
@@ -1134,6 +1179,40 @@ static bool range_mapped(char *addr, size_t size)
return !msync(addr, size, MS_ASYNC);
}
+/* A first byte of 0x31 means the preserved page itself, else something new. */
+static int fail_mapped(char *addr, const char *what)
+{
+ FILE *file = fopen("/proc/self/smaps", "r");
+ bool in_vma = false;
+ unsigned char byte;
+ char line[512];
+ int fd;
+
+ while (file && fgets(line, sizeof(line), file)) {
+ char *space = strchr(line, ' ');
+ unsigned long start, end;
+
+ if (space && memchr(line, '-', space - line) && !memchr(line, ':', space - line)) {
+ in_vma = sscanf(line, "%lx-%lx", &start, &end) == 2 &&
+ start <= (unsigned long)addr && (unsigned long)addr < end;
+ if (in_vma)
+ ksft_print_msg("%p is in %s", addr, line);
+ } else if (in_vma && (!strncmp(line, "Rss:", 4) ||
+ !strncmp(line, "Anonymous:", 10) ||
+ !strncmp(line, "VmFlags:", 8))) {
+ ksft_print_msg(" %s", line);
+ }
+ }
+ if (file)
+ fclose(file);
+ fd = open("/proc/self/mem", O_RDONLY);
+ if (fd >= 0 && pread(fd, &byte, 1, (off_t)(uintptr_t)addr) == 1)
+ ksft_print_msg("first byte %#x\n", byte);
+ if (fd >= 0)
+ close(fd);
+ return fail(what);
+}
+
static int sysctl_flag(void)
{
FILE *file = fopen("/proc/sys/vm/madv_doexec_flag", "r");
@@ -1153,9 +1232,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;
@@ -1228,7 +1309,7 @@ static int after_never(char *ram, size_t size)
static int after_absent(char *ram, size_t size)
{
- return range_mapped(ram, size) ? fail("range still mapped") : KSFT_PASS;
+ return range_mapped(ram, size) ? fail_mapped(ram, "range still mapped") : KSFT_PASS;
}
/* Marking the middle page splits the VMA; only that page carries over. */
@@ -1239,7 +1320,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();
@@ -1251,8 +1332,10 @@ static int after_partial(char *ram, size_t size)
{
size_t page = size / 3;
- if (range_mapped(ram, page) || range_mapped(ram + 2 * page, page))
- return fail("unmarked page preserved");
+ if (range_mapped(ram, page))
+ return fail_mapped(ram, "unmarked page preserved");
+ if (range_mapped(ram + 2 * page, page))
+ return fail_mapped(ram + 2 * page, "unmarked page preserved");
if (!range_mapped(ram + page, page))
return fail("marked page not preserved");
return check_contents(ram + page, page, 0x31);
@@ -1346,11 +1429,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_mapped(ram, "child of the new image got a DONTFORK range"));
+ _exit(KSFT_PASS);
}
return wait_child(pid);
}
@@ -1593,7 +1678,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)
@@ -1616,7 +1701,8 @@ 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();
@@ -1744,7 +1830,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");
@@ -1769,7 +1855,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))
@@ -1802,7 +1888,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;
@@ -1836,7 +1922,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;
@@ -1856,7 +1942,7 @@ static int after_thp_half(char *ram, size_t size)
for (off = size / 2; off < size; off += page)
if (range_mapped(ram + off, page))
- return fail("unmarked half preserved");
+ return fail_mapped(ram + off, "unmarked half preserved");
return check_contents(ram, size / 2, 0x31);
}
@@ -1885,7 +1971,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;
@@ -1932,11 +2018,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];
@@ -1954,7 +2040,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]);
@@ -1969,8 +2055,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;
@@ -2021,7 +2109,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;
@@ -2056,10 +2144,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[] = {
@@ -2095,7 +2183,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),
@@ -2104,7 +2192,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),
@@ -2135,8 +2223,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();
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 5604fc8939a33..e23f950f11ff5 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -78,7 +78,9 @@ to the -t argument containing one or more of the following categories
- page_frag
test handling of page fragment allocation and freeing
- doexec
- test memory preserved across exec with MADV_DOEXEC
+ test memory preserved across exec with MADV_DOEXEC; to cover a VFIO
+ BAR too, set DOEXEC_VFIO_DEVICE to a spare PCI device bound to
+ vfio-pci, its BAR 0 gets overwritten
example: ./run_vmtests.sh -t "hmm mmap ksm"
EOF
--
2.43.0
More information about the Devel
mailing list