[Devel] [PATCH hci-8.1 2/2] selftests/mm: check MADV_DOEXEC on VFIO BARs and other PFN mappings

Konstantin Khorenko khorenko at virtuozzo.com
Wed Oct 14 20:14:01 MSK 2026


Add two 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, so that huge PFN entries can be used. The
  test fills it with a pattern, marks it and 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. A group that has only a noiommu node
  is set up with the no-IOMMU container. Without the variable the case
  is 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 | 141 +++++++++++++++++++++++++++++++++++-
 1 file changed, 139 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
index 97e045ba678e8..a89fe2f067df5 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,132 @@ 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;
+
+	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;
+	return munmap(bar, size) ? 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(void)
+{
+	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) ||
+	    !(info.flags & VFIO_REGION_INFO_FLAG_MMAP) || info.size < page) {
+		ksft_print_msg("VFIO device %s: %s\n", bdf, strerror(errno));
+		return KSFT_FAIL;
+	}
+	size = info.size < (4ul << 20) ? info.size : 4ul << 20;
+	/* 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;
+	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;
+	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 +1054,9 @@ int main(int argc, char **argv)
 		return KSFT_FAIL;
 	if (argc == 2 && !strcmp(argv[1], "--euid"))
 		return geteuid() ? KSFT_SKIP : KSFT_PASS;
+	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 +1076,7 @@ int main(int argc, char **argv)
 		return KSFT_PASS;
 	}
 	ksft_print_header();
-	ksft_set_plan(NR_TESTS + 11);
+	ksft_set_plan(NR_TESTS + 13);
 	for (kind = 0; kind < NR_TESTS; kind++) {
 		fflush(stdout);
 		pid = fork();
@@ -968,7 +1099,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 < 12; kind++) {
 		static const char * const extra_names[] = {
 			"CLONE_VM: recoverable EBUSY and retry",
 			"own threads: preserving exec allowed",
@@ -980,6 +1111,8 @@ 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",
 		};
 
 		fflush(stdout);
@@ -1005,6 +1138,10 @@ int main(int argc, char **argv)
 				_exit(devzero_exec());
 			case 9:
 				_exit(doexec_noexec());
+			case 10:
+				_exit(devmem_refused());
+			case 11:
+				_exit(vfio_bar_exec());
 			}
 		}
 		ret = pid < 0 ? KSFT_FAIL : wait_child(pid);

-- 
2.43.0



More information about the Devel mailing list