[Devel] [PATCH RFC PATCH hci-8.1 4/4] selftests/mm: test memory preservation across exec
Andrey Drobyshev
andrey.drobyshev at virtuozzo.com
Fri Oct 9 17:00:07 MSK 2026
Add an ELF-opted-in MADV_DOEXEC test and wire it into the mm test runner.
Compare CPU writes against retained long-term GUP pins across repeated
exec and a later fork, including base pages, THP, mlock, protected mappings
and fast GUP. Check existing COW mappings separately.
Exercise recoverable rejection and retry with an external CLONE_VM task,
own and non-leader threads, concurrent shared-mm clone creation, proc
inspection during repeated exec, and vhost owner reset before exec. Add
an optional task-local allocation-failure sweep.
https://virtuozzo.atlassian.net/browse/VSTOR-103860
Signed-off-by: Andrey Drobyshev <andrey.drobyshev at virtuozzo.com>
---
tools/testing/selftests/mm/Makefile | 1 +
tools/testing/selftests/mm/doexec.c | 774 ++++++++++++++++++++++++++++++
tools/testing/selftests/mm/run_vmtests.sh | 1 +
3 files changed, 776 insertions(+)
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index c837b0549c89..7f9efb0e0a38 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -74,6 +74,7 @@ endif
TEST_GEN_FILES = cow
TEST_GEN_FILES += compaction_test
+TEST_GEN_FILES += doexec
TEST_GEN_FILES += gup_longterm
TEST_GEN_FILES += gup_test
TEST_GEN_FILES += hmm-tests
diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
new file mode 100644
index 000000000000..6ec09e868679
--- /dev/null
+++ b/tools/testing/selftests/mm/doexec.c
@@ -0,0 +1,774 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Verify that exec preserves the memory backing long-term DMA pins. */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/vhost.h>
+#include <poll.h>
+#include <pthread.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../../../../mm/gup_test.h"
+#include "../kselftest.h"
+
+#ifndef MADV_DOEXEC
+#define MADV_DOEXEC 201
+#endif
+#ifndef MADV_COLLAPSE
+#define MADV_COLLAPSE 25
+#endif
+
+/* The downstream ELF loader requires this opt-in in a PT_NOTE segment. */
+asm(".pushsection .note.doexec,\"a\", at note\n"
+ ".balign 4\n"
+ ".long 17, 0, 0x07c1feed\n"
+ ".asciz \"preserved-mem-ok\"\n"
+ ".balign 4\n"
+ ".popsection\n");
+
+enum test_kind {
+ BASE,
+ HUGE,
+ LOCKED,
+ PROTECTED,
+ READ_PIN,
+ FAST_PIN,
+ NR_TESTS,
+};
+
+static const char * const names[] = {
+ "base pages",
+ "PMD huge pages",
+ "mlocked pages",
+ "read-only mapping",
+ "read-only pins",
+ "fast GUP pins",
+};
+
+static int wait_child(pid_t pid)
+{
+ int status;
+ pid_t ret;
+
+ do {
+ ret = waitpid(pid, &status, 0);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0 || !WIFEXITED(status))
+ return KSFT_FAIL;
+ return WEXITSTATUS(status);
+}
+
+static int check_pin(int fd, char *ram, size_t size, unsigned char value)
+{
+ char *copy = malloc(size);
+ uint64_t addr = (uintptr_t)copy;
+ size_t i;
+ int ret = KSFT_FAIL;
+
+ if (!copy)
+ return ret;
+ if (ioctl(fd, PIN_LONGTERM_TEST_READ, &addr)) {
+ ksft_print_msg("read pin: %s\n", strerror(errno));
+ goto out;
+ }
+ for (i = 0; i < size; i++) {
+ if ((unsigned char)ram[i] != value ||
+ (unsigned char)copy[i] != value) {
+ ksft_print_msg("CPU/pin mismatch at byte %zu\n", i);
+ goto out;
+ }
+ }
+ ret = KSFT_PASS;
+out:
+ free(copy);
+ return ret;
+}
+
+static int check_bytes(char *ram, size_t size, unsigned char value)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ if ((unsigned char)ram[i] != value)
+ return KSFT_FAIL;
+ return KSFT_PASS;
+}
+
+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);
+
+ memset(ram, 0x42, size);
+ if (write(pipe_fd, &ready, 1) != 1)
+ ret = KSFT_FAIL;
+ close(pipe_fd);
+ if (wait_child(sibling))
+ ret = KSFT_FAIL;
+ return ret;
+}
+
+/* Pages shared with a separate mm must retain their pre-existing COW state. */
+static int before_cow_exec(void)
+{
+ size_t size = 16 * sysconf(_SC_PAGESIZE);
+ char addr_arg[32], size_arg[32], pid_arg[32], fd_arg[32], ready;
+ int pipe_fd[2];
+ char *ram;
+ pid_t pid;
+
+ ram = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (ram == MAP_FAILED)
+ return KSFT_FAIL;
+ memset(ram, 0x31, size);
+ if (madvise(ram, size, MADV_DOEXEC))
+ return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ if (pipe(pipe_fd))
+ return KSFT_FAIL;
+ pid = fork();
+ if (pid < 0)
+ return KSFT_FAIL;
+ if (!pid) {
+ close(pipe_fd[1]);
+ if (read(pipe_fd[0], &ready, 1) != 1)
+ _exit(KSFT_FAIL);
+ _exit(check_bytes(ram, size, 0x31));
+ }
+ close(pipe_fd[0]);
+ snprintf(addr_arg, sizeof(addr_arg), "%llu",
+ (unsigned long long)(uintptr_t)ram);
+ snprintf(size_arg, sizeof(size_arg), "%zu", size);
+ snprintf(pid_arg, sizeof(pid_arg), "%d", pid);
+ 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);
+ close(pipe_fd[1]);
+ wait_child(pid);
+ return KSFT_FAIL;
+}
+
+static void exec_stage(int fd, char *ram, size_t size, int kind, int stage)
+{
+ char fd_arg[32], addr_arg[32], size_arg[32], kind_arg[32], stage_arg[32];
+
+ snprintf(fd_arg, sizeof(fd_arg), "%d", fd);
+ snprintf(addr_arg, sizeof(addr_arg), "%llu",
+ (unsigned long long)(uintptr_t)ram);
+ snprintf(size_arg, sizeof(size_arg), "%zu", size);
+ snprintf(kind_arg, sizeof(kind_arg), "%d", kind);
+ 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);
+}
+
+static int after_exec(int fd, char *ram, size_t size, int kind, int stage)
+{
+ unsigned char before = stage == 1 ? 0x31 : 0x42;
+ unsigned char after = stage == 1 ? 0x42 : 0x53;
+ pid_t pid;
+
+ if (check_pin(fd, ram, size, before))
+ return KSFT_FAIL;
+ if (kind == PROTECTED && mprotect(ram, size, PROT_READ | PROT_WRITE))
+ return KSFT_FAIL;
+ 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;
+ if (kind == PROTECTED && mprotect(ram, size, PROT_READ))
+ return KSFT_FAIL;
+ 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;
+ if (!pid) {
+ memset(ram, 0x64, size);
+ _exit(KSFT_PASS);
+ }
+ if (wait_child(pid) || 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;
+}
+
+static size_t huge_page_size(void)
+{
+ FILE *file = fopen("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", "r");
+ size_t size = 0;
+
+ if (file) {
+ if (fscanf(file, "%zu", &size) != 1)
+ size = 0;
+ fclose(file);
+ }
+ return size;
+}
+
+static int before_exec(int kind)
+{
+ size_t size = 16 * sysconf(_SC_PAGESIZE);
+ struct pin_longterm_test pin;
+ char *ram, *mapping;
+ uintptr_t aligned;
+ int fd;
+
+ if (kind == HUGE) {
+ size = huge_page_size();
+ if (!size)
+ return KSFT_SKIP;
+ }
+ fd = open("/sys/kernel/debug/gup_test", O_RDWR);
+ if (fd < 0)
+ return KSFT_SKIP;
+ mapping = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (mapping == MAP_FAILED)
+ return KSFT_FAIL;
+ aligned = ((uintptr_t)mapping + size - 1) & ~(size - 1);
+ ram = (char *)aligned;
+ if (ram != mapping)
+ munmap(mapping, ram - mapping);
+ if (ram + size != mapping + 2 * size)
+ munmap(ram + size, mapping + 2 * size - (ram + size));
+ if (madvise(ram, size, kind == HUGE ? MADV_HUGEPAGE : MADV_NOHUGEPAGE))
+ return KSFT_FAIL;
+ memset(ram, 0x31, size);
+ if (kind == HUGE && madvise(ram, size, MADV_COLLAPSE))
+ return KSFT_SKIP;
+ if (kind == LOCKED && mlock(ram, size))
+ return KSFT_SKIP;
+ if (madvise(ram, size, MADV_DOEXEC)) {
+ ksft_print_msg("MADV_DOEXEC: %s\n", strerror(errno));
+ return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ }
+ pin = (struct pin_longterm_test) {
+ .addr = (uintptr_t)ram,
+ .size = size,
+ .flags = kind == READ_PIN ? 0 : PIN_LONGTERM_TEST_FLAG_USE_WRITE,
+ };
+ if (kind == FAST_PIN)
+ pin.flags |= PIN_LONGTERM_TEST_FLAG_USE_FAST;
+ if (ioctl(fd, PIN_LONGTERM_TEST_START, &pin)) {
+ ksft_print_msg("pin: %s\n", strerror(errno));
+ return errno == ENOTTY ? KSFT_SKIP : KSFT_FAIL;
+ }
+ if (kind == PROTECTED && mprotect(ram, size, PROT_READ))
+ return KSFT_FAIL;
+ exec_stage(fd, ram, size, kind, 1);
+ return KSFT_FAIL;
+}
+
+static int check_exec(char *ram, size_t size, size_t stride, bool reject)
+{
+ char addr_arg[32], size_arg[32], stride_arg[32];
+
+ snprintf(addr_arg, sizeof(addr_arg), "%llu",
+ (unsigned long long)(uintptr_t)ram);
+ snprintf(size_arg, sizeof(size_arg), "%zu", size);
+ snprintf(stride_arg, sizeof(stride_arg), "%zu", stride);
+ execl("/proc/self/exe", "doexec", reject ? "--reject" : "--check", addr_arg, size_arg,
+ stride_arg, NULL);
+ return errno;
+}
+
+static char *preserved_ram(size_t size)
+{
+ char *ram = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ if (ram == MAP_FAILED)
+ return NULL;
+ memset(ram, 0x31, size);
+ if (madvise(ram, size, MADV_DOEXEC)) {
+ munmap(ram, size);
+ return NULL;
+ }
+ return ram;
+}
+
+static int blocked_task(void *arg)
+{
+ int fd = *(int *)arg;
+ char ready;
+
+ return read(fd, &ready, 1) == 1 ? KSFT_PASS : KSFT_FAIL;
+}
+
+static void *blocked_thread(void *arg)
+{
+ blocked_task(arg);
+ return NULL;
+}
+
+/* Own threads may be killed by exec; a separate CLONE_VM task may not. */
+static int shared_mm_exec(bool own_thread)
+{
+ size_t size = sysconf(_SC_PAGESIZE);
+ char *ram = preserved_ram(size), *stack;
+ int pipe_fd[2], ret;
+ pthread_t thread;
+ pid_t pid;
+
+ if (!ram)
+ return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ if (pipe(pipe_fd))
+ return KSFT_FAIL;
+ if (own_thread) {
+ if (pthread_create(&thread, NULL, blocked_thread, &pipe_fd[0]))
+ return KSFT_FAIL;
+ check_exec(ram, size, 1, false);
+ write(pipe_fd[1], "x", 1);
+ pthread_join(thread, NULL);
+ return KSFT_FAIL;
+ }
+ stack = mmap(NULL, 65536, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+ if (stack == MAP_FAILED)
+ return KSFT_FAIL;
+ pid = clone(blocked_task, stack + 65536, CLONE_VM | SIGCHLD, &pipe_fd[0]);
+ if (pid < 0)
+ return KSFT_FAIL;
+ 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;
+ write(pipe_fd[1], "x", 1);
+ if (wait_child(pid))
+ ret = KSFT_FAIL;
+ if (ret)
+ return ret;
+ /* Releasing the outside user must allow a retry. */
+ check_exec(ram, size, 1, false);
+ return KSFT_FAIL;
+}
+
+/* The caller must release its backend owner before preserving exec. */
+static int vhost_owner_exec(void)
+{
+ size_t size = sysconf(_SC_PAGESIZE);
+ char *ram;
+ int fd = open("/dev/vhost-net", O_RDWR);
+
+ if (fd < 0)
+ return KSFT_SKIP;
+ ram = preserved_ram(size);
+ if (!ram)
+ return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ if (ioctl(fd, VHOST_SET_OWNER))
+ return KSFT_SKIP;
+ if (ioctl(fd, VHOST_RESET_OWNER))
+ return KSFT_FAIL;
+ close(fd);
+ check_exec(ram, size, 1, false);
+ return KSFT_FAIL;
+}
+
+static void *exec_thread(void *arg)
+{
+ check_exec(arg, sysconf(_SC_PAGESIZE), 1, false);
+ return NULL;
+}
+
+static int nonleader_exec(void)
+{
+ char *ram = preserved_ram(sysconf(_SC_PAGESIZE));
+ pthread_t thread;
+
+ if (!ram)
+ return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ if (pthread_create(&thread, NULL, exec_thread, ram))
+ return KSFT_FAIL;
+ pthread_join(thread, NULL);
+ return KSFT_FAIL;
+}
+
+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;
+ if (stage == 32)
+ return KSFT_PASS;
+ if (madvise(ram, size, MADV_DOEXEC))
+ return KSFT_FAIL;
+ 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;
+}
+
+/* Read both pre-opened and freshly opened proc files during repeated exec. */
+static int proc_readers_exec(void)
+{
+ static const char * const files[] = { "maps", "smaps_rollup", "pagemap" };
+ int ready[2], go[2], fds[3] = { -1, -1, -1 }, status, ret = KSFT_FAIL;
+ char paths[3][80], buffer[4096];
+ unsigned int round = 0;
+ pid_t pid, waited;
+ int i;
+
+ if (pipe(ready) || pipe(go))
+ return KSFT_FAIL;
+ pid = fork();
+ if (pid < 0)
+ return KSFT_FAIL;
+ if (!pid) {
+ size_t size = 16 * sysconf(_SC_PAGESIZE);
+ char *ram = preserved_ram(size);
+
+ close(ready[0]);
+ close(go[1]);
+ if (!ram)
+ _exit(errno == EINVAL ? KSFT_SKIP : KSFT_FAIL);
+ if (write(ready[1], "x", 1) != 1 || read(go[0], buffer, 1) != 1)
+ _exit(KSFT_FAIL);
+ close(ready[1]);
+ close(go[0]);
+ _exit(proc_exec(ram, size, 0));
+ }
+ close(ready[1]);
+ close(go[0]);
+ if (read(ready[0], buffer, 1) != 1) {
+ close(ready[0]);
+ close(go[1]);
+ return wait_child(pid);
+ }
+ close(ready[0]);
+ for (i = 0; i < 3; i++) {
+ snprintf(paths[i], sizeof(paths[i]), "/proc/%d/%s", pid, files[i]);
+ fds[i] = open(paths[i], O_RDONLY);
+ if (fds[i] < 0) {
+ ret = KSFT_SKIP;
+ goto stop;
+ }
+ }
+ if (write(go[1], "x", 1) != 1)
+ goto stop;
+ close(go[1]);
+ go[1] = -1;
+ for (;;) {
+ waited = waitpid(pid, &status, WNOHANG);
+ if (waited == pid) {
+ ret = WIFEXITED(status) ? WEXITSTATUS(status) : KSFT_FAIL;
+ break;
+ }
+ if (waited < 0 && errno != EINTR)
+ break;
+ for (i = 0; i < 3; i++) {
+ lseek(fds[i], 0, SEEK_SET);
+ if (read(fds[i], buffer, sizeof(buffer)) < 0 &&
+ errno != EINTR && errno != ESRCH)
+ goto stop;
+ }
+ /* Keep two old handles while refreshing the third. */
+ i = round++ % 3;
+ close(fds[i]);
+ fds[i] = open(paths[i], O_RDONLY);
+ if (fds[i] < 0) {
+ ret = wait_child(pid);
+ break;
+ }
+ }
+ for (i = 0; i < 3; i++)
+ if (fds[i] >= 0)
+ close(fds[i]);
+ return ret;
+stop:
+ if (go[1] >= 0)
+ close(go[1]);
+ kill(pid, SIGKILL);
+ wait_child(pid);
+ for (i = 0; i < 3; i++)
+ if (fds[i] >= 0)
+ close(fds[i]);
+ return ret;
+}
+
+struct clone_race {
+ char *ram;
+ int report;
+ atomic_bool stop;
+ atomic_uint rounds;
+};
+
+static int clone_race_child(void *arg)
+{
+ struct clone_race *race = arg;
+ unsigned int i;
+
+ for (i = 0; i < 8; i++) {
+ if (__atomic_load_n(race->ram, __ATOMIC_RELAXED) != 0x31) {
+ write(race->report, "x", 1);
+ return KSFT_FAIL;
+ }
+ usleep(1000);
+ }
+ return KSFT_PASS;
+}
+
+static void *clone_race_thread(void *arg)
+{
+ struct clone_race *race = arg;
+ char *stack = mmap(NULL, 65536, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+ pid_t pid;
+
+ if (stack == MAP_FAILED) {
+ write(race->report, "x", 1);
+ atomic_store(&race->rounds, 4);
+ return NULL;
+ }
+ while (!atomic_load(&race->stop)) {
+ pid = clone(clone_race_child, stack + 65536,
+ CLONE_VM | SIGCHLD, race);
+ if (pid >= 0) {
+ if (wait_child(pid))
+ write(race->report, "x", 1);
+ } else if (errno != EBUSY && errno != EINTR) {
+ write(race->report, "x", 1);
+ }
+ atomic_fetch_add(&race->rounds, 1);
+ usleep(1000);
+ }
+ munmap(stack, 65536);
+ return NULL;
+}
+
+/* A supervisor collects errors from any shared-mm child surviving exec. */
+static int clone_exec_race(void)
+{
+ int report[2], ret;
+ struct pollfd pfd;
+ char failed;
+ pid_t pid;
+
+ if (pipe(report))
+ return KSFT_FAIL;
+ pid = fork();
+ if (pid < 0)
+ return KSFT_FAIL;
+ if (!pid) {
+ size_t size = sysconf(_SC_PAGESIZE);
+ struct clone_race race = { .report = report[1] };
+ pthread_t thread;
+ unsigned int attempt;
+
+ close(report[0]);
+ race.ram = preserved_ram(size);
+ if (!race.ram)
+ _exit(errno == EINVAL ? KSFT_SKIP : KSFT_FAIL);
+ if (pthread_create(&thread, NULL, clone_race_thread, &race))
+ _exit(KSFT_FAIL);
+ while (atomic_load(&race.rounds) < 4)
+ usleep(1000);
+ for (attempt = 0; attempt < 500; attempt++) {
+ if (check_exec(race.ram, size, 1, false) != EBUSY)
+ break;
+ if (check_bytes(race.ram, size, 0x31))
+ break;
+ usleep(1000);
+ }
+ atomic_store(&race.stop, true);
+ pthread_join(thread, NULL);
+ _exit(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;
+ close(report[0]);
+ return ret;
+}
+
+/* Opt-in sweep of allocation failures, using only the child's fail-nth. */
+static int allocation_failures(void)
+{
+ const size_t size = 256ul << 20, stride = 2ul << 20;
+ struct rlimit no_core = { 0, 0 };
+ unsigned int early = 0, fatal = 0, success = 0, loader = 0;
+ unsigned int nth;
+ pid_t pid;
+ int status;
+ pid_t waited;
+
+ if (!getenv("DOEXEC_FAULT_INJECTION") || access("/proc/self/fail-nth", W_OK))
+ return KSFT_SKIP;
+ for (nth = 1; nth <= 256; nth++) {
+ pid = fork();
+ if (pid < 0)
+ return KSFT_FAIL;
+ if (!pid) {
+ char *ram, number[32];
+ size_t i;
+ int fd, len, err;
+
+ setrlimit(RLIMIT_CORE, &no_core);
+ ram = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (ram == MAP_FAILED || madvise(ram, size, MADV_NOHUGEPAGE))
+ _exit(KSFT_FAIL);
+ for (i = 0; i < size; i += stride)
+ ram[i] = 0x31;
+ if (madvise(ram, size, MADV_DOEXEC))
+ _exit(errno == EINVAL ? KSFT_SKIP : KSFT_FAIL);
+ fd = open("/proc/self/fail-nth", O_WRONLY | O_CLOEXEC);
+ len = snprintf(number, sizeof(number), "%u", nth);
+ if (fd < 0 || write(fd, number, len) != len)
+ _exit(KSFT_FAIL);
+ err = check_exec(ram, size, stride, false);
+ /* 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(err == ENOMEM || err == EFAULT || err == EAGAIN ? 10 : KSFT_FAIL);
+ }
+ do {
+ waited = waitpid(pid, &status, 0);
+ } while (waited < 0 && errno == EINTR);
+ if (waited < 0)
+ return KSFT_FAIL;
+ if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)
+ fatal++;
+ else if (WIFEXITED(status) && WEXITSTATUS(status) == 10)
+ early++;
+ else if (WIFEXITED(status) && WEXITSTATUS(status) == KSFT_PASS)
+ success++;
+ /* A dynamic loader may exit when a post-exec allocation fails. */
+ else if (WIFEXITED(status) && WEXITSTATUS(status) == 127)
+ loader++;
+ else if (WIFEXITED(status) && WEXITSTATUS(status) == KSFT_SKIP)
+ return KSFT_SKIP;
+ else
+ return KSFT_FAIL;
+ }
+ ksft_print_msg("faults: %u recoverable, %u SIGSEGV, %u success, %u loader exits\n",
+ early, fatal, success, loader);
+ /* A sweep that injected no recoverable/fatal errors provides no coverage. */
+ return early && fatal ? KSFT_PASS : KSFT_SKIP;
+}
+
+int main(int argc, char **argv)
+{
+ pid_t pid;
+ int kind, ret;
+
+ if (argc == 7 && !strcmp(argv[1], "--exec"))
+ return after_exec(atoi(argv[2]),
+ (char *)(uintptr_t)strtoull(argv[3], NULL, 10),
+ strtoull(argv[4], NULL, 10), atoi(argv[5]), atoi(argv[6]));
+ if (argc == 6 && !strcmp(argv[1], "--cow"))
+ 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;
+ if (argc == 5 && !strcmp(argv[1], "--proc"))
+ return proc_exec((char *)(uintptr_t)strtoull(argv[2], NULL, 10),
+ strtoull(argv[3], NULL, 10), atoi(argv[4]));
+ if (argc == 5 && !strcmp(argv[1], "--check")) {
+ char *ram = (char *)(uintptr_t)strtoull(argv[2], NULL, 10);
+ size_t size = strtoull(argv[3], NULL, 10);
+ size_t stride = strtoull(argv[4], NULL, 10), i;
+ int fd = open("/proc/self/fail-nth", O_WRONLY);
+
+ if (fd >= 0) {
+ write(fd, "0", 1);
+ close(fd);
+ }
+ for (i = 0; i < size; i += stride)
+ if (ram[i] != 0x31)
+ return KSFT_FAIL;
+ return KSFT_PASS;
+ }
+ ksft_print_header();
+ ksft_set_plan(NR_TESTS + 8);
+ for (kind = 0; kind < NR_TESTS; kind++) {
+ fflush(stdout);
+ pid = fork();
+ if (!pid)
+ _exit(before_exec(kind));
+ ret = pid < 0 ? KSFT_FAIL : wait_child(pid);
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("%s: DOEXEC, GUP test or mapping unavailable\n",
+ names[kind]);
+ else
+ ksft_test_result(ret == KSFT_PASS, "%s: repeated exec and fork\n",
+ names[kind]);
+ }
+ fflush(stdout);
+ pid = fork();
+ if (!pid)
+ _exit(before_cow_exec());
+ ret = pid < 0 ? KSFT_FAIL : wait_child(pid);
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("pre-existing COW: DOEXEC unavailable\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "pre-existing COW preserved\n");
+ for (kind = 0; kind < 7; kind++) {
+ static const char * const extra_names[] = {
+ "CLONE_VM: recoverable EBUSY and retry",
+ "own threads: preserving exec allowed",
+ "vhost owner: reset before preserving exec",
+ "allocation failure sweep (opt-in)",
+ "non-leader thread: preserving exec allowed",
+ "proc readers: repeated preserving exec allowed",
+ "concurrent CLONE_VM: shared memory is not stolen",
+ };
+
+ fflush(stdout);
+ pid = fork();
+ if (!pid) {
+ switch (kind) {
+ case 0:
+ case 1:
+ _exit(shared_mm_exec(kind));
+ case 2:
+ _exit(vhost_owner_exec());
+ case 3:
+ _exit(allocation_failures());
+ case 4:
+ _exit(nonleader_exec());
+ case 5:
+ _exit(proc_readers_exec());
+ case 6:
+ _exit(clone_exec_race());
+ }
+ }
+ ret = pid < 0 ? KSFT_FAIL : wait_child(pid);
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("%s: unavailable or disabled\n", extra_names[kind]);
+ else
+ ksft_test_result(ret == KSFT_PASS, "%s\n", extra_names[kind]);
+ }
+ ksft_finished();
+}
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 6ac8ba7e2447..9643b39762b8 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -296,6 +296,7 @@ fi
# Dump pages 0, 19, and 4096, using pin_user_pages:
CATEGORY="gup_test" run_test ./gup_test -ct -F 0x1 0 19 0x1000
CATEGORY="gup_test" run_test ./gup_longterm
+CATEGORY="gup_test" run_test ./doexec
CATEGORY="userfaultfd" run_test ./uffd-unit-tests
uffd_stress_bin=./uffd-stress
--
2.47.1
More information about the Devel
mailing list