[Devel] [PATCH hci-8.1 v2 2/2] selftests/mm: check MADV_DOEXEC on VFIO BARs and other PFN mappings
Konstantin Khorenko
khorenko at virtuozzo.com
Sat Oct 17 00:35:40 MSK 2026
Add three cases to doexec:
- A shared mapping of /dev/mem is a PFN mapping of a driver other than
VFIO, and MADV_DOEXEC must refuse it.
- BAR 0 of the PCI device named by DOEXEC_VFIO_DEVICE, which has to be
bound to vfio-pci, is mapped through its VFIO device fd at an address
aligned to the mapped size, filled with a pattern and marked, and the
process execs. The new image must find the mapping at the same
address with the pfn and shared flags in smaps and the pattern in
place. It then execs again without marking, and that image must not
find the mapping.
- The same with the BAR mapped one page off that alignment. The aligned
mapping gets huge PFN entries, which the copy leaves to be faulted in
again in the new image; this one gets PTEs, which copy_page_range()
duplicates.
A group that has only a noiommu node is set up with the no-IOMMU
container. Without the variable both VFIO cases are skipped. The test
overwrites BAR 0, so the variable has to name a spare device whose BAR 0
is memory, such as an unused VGA frame buffer.
https://virtuozzo.atlassian.net/browse/VSTOR-150233
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 | 157 +++++++++++++++++++++++++++++++++++-
1 file changed, 155 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
index 97e045ba678e8..87a9c9493fe35 100644
--- a/tools/testing/selftests/mm/doexec.c
+++ b/tools/testing/selftests/mm/doexec.c
@@ -4,6 +4,8 @@
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
+#include <limits.h>
+#include <linux/vfio.h>
#include <linux/vhost.h>
#include <poll.h>
#include <pthread.h>
@@ -585,6 +587,144 @@ static int doexec_noexec(void)
return ret;
}
+static bool smaps_has_flag(char *addr, const char *flag)
+{
+ FILE *file = fopen("/proc/self/smaps", "r");
+ char line[256], want[32];
+ bool in_vma = false, found = false;
+
+ if (!file)
+ return false;
+ snprintf(want, sizeof(want), "%lx-", (unsigned long)addr);
+ while (fgets(line, sizeof(line), file)) {
+ char *space = strchr(line, ' ');
+
+ if (!space)
+ continue;
+ if (memchr(line, '-', space - line) && !memchr(line, ':', space - line))
+ in_vma = !strncmp(line, want, strlen(want));
+ else if (in_vma && !strncmp(line, "VmFlags:", 8))
+ found = strstr(line, flag) != NULL;
+ }
+ fclose(file);
+ return found;
+}
+
+static int after_pfnmap_exec(char *bar, size_t size)
+{
+ size_t page = sysconf(_SC_PAGESIZE), i;
+ char addr_arg[32];
+
+ if (!smaps_has_flag(bar, " pf ") || !smaps_has_flag(bar, " sh "))
+ return KSFT_FAIL;
+ for (i = 0; i < size / page; i++)
+ if (__atomic_load_n((uint32_t *)(bar + i * page), __ATOMIC_RELAXED) !=
+ (0xd0e00000 | i))
+ return KSFT_FAIL;
+ /* 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;
+}
+
+static int after_pfnmap_gone(char *bar)
+{
+ return smaps_has_flag(bar, " pf ") ? KSFT_FAIL : KSFT_PASS;
+}
+
+/* Only VFIO device mappings may be preserved; /dev/mem is a plain PFN map. */
+static int devmem_refused(void)
+{
+ size_t size = sysconf(_SC_PAGESIZE);
+ char *ram;
+ int fd;
+
+ if (!preserved_ram(size))
+ return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ /* Without FMODE_WRITE, do_mmap() drops VM_SHARED from the mapping. */
+ fd = open("/dev/mem", O_RDWR);
+ if (fd < 0)
+ return KSFT_SKIP;
+ ram = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ 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;
+}
+
+static int vfio_device_fd(const char *bdf)
+{
+ char path[PATH_MAX], link[PATH_MAX], *group_id;
+ int container, group, noiommu;
+ ssize_t len;
+
+ snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/iommu_group", bdf);
+ len = readlink(path, link, sizeof(link) - 1);
+ if (len < 0)
+ return -1;
+ link[len] = 0;
+ group_id = strrchr(link, '/') + 1;
+ snprintf(path, sizeof(path), "/dev/vfio/%s", group_id);
+ noiommu = access(path, F_OK);
+ if (noiommu)
+ snprintf(path, sizeof(path), "/dev/vfio/noiommu-%s", group_id);
+ container = open("/dev/vfio/vfio", O_RDWR);
+ group = open(path, O_RDWR);
+ if (container < 0 || group < 0 ||
+ ioctl(group, VFIO_GROUP_SET_CONTAINER, &container) ||
+ ioctl(container, VFIO_SET_IOMMU, noiommu ? VFIO_NOIOMMU_IOMMU : VFIO_TYPE1v2_IOMMU))
+ return -1;
+ return ioctl(group, VFIO_GROUP_GET_DEVICE_FD, bdf);
+}
+
+/* Overwrites BAR 0 of DOEXEC_VFIO_DEVICE, so name a spare memory BAR. */
+static int vfio_bar_exec(bool ptes)
+{
+ struct vfio_region_info info = {
+ .argsz = sizeof(info),
+ .index = VFIO_PCI_BAR0_REGION_INDEX,
+ };
+ size_t page = sysconf(_SC_PAGESIZE), size, i;
+ const char *bdf = getenv("DOEXEC_VFIO_DEVICE");
+ char addr_arg[32], size_arg[32], *area, *bar;
+ uintptr_t aligned;
+ int dev;
+
+ if (!bdf || !preserved_ram(page))
+ return KSFT_SKIP;
+ dev = vfio_device_fd(bdf);
+ if (dev < 0 || ioctl(dev, VFIO_DEVICE_GET_REGION_INFO, &info)) {
+ ksft_print_msg("VFIO device %s: %s\n", bdf, strerror(errno));
+ return KSFT_FAIL;
+ }
+ if (!(info.flags & VFIO_REGION_INFO_FLAG_MMAP) || info.size < page) {
+ ksft_print_msg("VFIO device %s: BAR 0 cannot be mapped\n", bdf);
+ return KSFT_FAIL;
+ }
+ size = info.size < (4ul << 20) ? info.size : 4ul << 20;
+ /* 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;
+ 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;
+ for (i = 0; i < size / page; i++)
+ __atomic_store_n((uint32_t *)(bar + i * page), 0xd0e00000 | i,
+ __ATOMIC_RELAXED);
+ if (madvise(bar, size, MADV_DOEXEC)) {
+ ksft_print_msg("MADV_DOEXEC on a VFIO BAR: %s\n", strerror(errno));
+ return KSFT_FAIL;
+ }
+ snprintf(addr_arg, sizeof(addr_arg), "%llu",
+ (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;
+}
+
static void *exec_thread(void *arg)
{
check_exec(arg, sysconf(_SC_PAGESIZE), 1, false);
@@ -926,6 +1066,11 @@ int main(int argc, char **argv)
return KSFT_FAIL;
if (argc == 2 && !strcmp(argv[1], "--euid"))
return geteuid() ? KSFT_SKIP : KSFT_PASS;
+ if (argc == 3 && !strcmp(argv[1], "--pfnmap-gone"))
+ return after_pfnmap_gone((char *)(uintptr_t)strtoull(argv[2], NULL, 10));
+ if (argc == 4 && !strcmp(argv[1], "--pfnmap"))
+ return after_pfnmap_exec((char *)(uintptr_t)strtoull(argv[2], NULL, 10),
+ strtoull(argv[3], NULL, 10));
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]));
@@ -945,7 +1090,7 @@ int main(int argc, char **argv)
return KSFT_PASS;
}
ksft_print_header();
- ksft_set_plan(NR_TESTS + 11);
+ ksft_set_plan(NR_TESTS + 14);
for (kind = 0; kind < NR_TESTS; kind++) {
fflush(stdout);
pid = fork();
@@ -968,7 +1113,7 @@ int main(int argc, char **argv)
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 < 10; kind++) {
+ for (kind = 0; kind < 13; kind++) {
static const char * const extra_names[] = {
"CLONE_VM: recoverable EBUSY and retry",
"own threads: preserving exec allowed",
@@ -980,6 +1125,9 @@ int main(int argc, char **argv)
"setuid exec: preserving exec refused",
"private /dev/zero mapping: preserved across exec",
"marked range: not executable until unmarked",
+ "shared /dev/mem mapping: MADV_DOEXEC refused",
+ "VFIO BAR: preserved across exec",
+ "VFIO BAR mapped with PTEs: preserved across exec",
};
fflush(stdout);
@@ -1005,6 +1153,11 @@ int main(int argc, char **argv)
_exit(devzero_exec());
case 9:
_exit(doexec_noexec());
+ case 10:
+ _exit(devmem_refused());
+ case 11:
+ case 12:
+ _exit(vfio_bar_exec(kind == 12));
}
}
ret = pid < 0 ? KSFT_FAIL : wait_child(pid);
--
2.43.0
More information about the Devel
mailing list