OpenVZ / Virtuozzo kernel development (devel@openvz.org)
 help / color / mirror / Atom feed
From: Konstantin Khorenko <khorenko@virtuozzo.com>
To: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>,
	Pavel Tikhomirov <ptikhomirov@virtuozzo.com>,
	Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
Cc: devel@openvz.org
Subject: [Devel] [PATCH hci-8.1 v2 2/2] selftests/mm: check MADV_DOEXEC on VFIO BARs and other PFN mappings
Date: Fri, 16 Oct 2026 23:35:40 +0200	[thread overview]
Message-ID: <20261016-b4-prep-khorenko-vstor-103860-4-doexec-vfio-bars-v2-2-2237f8fbb38e@virtuozzo.com> (raw)
In-Reply-To: <20261016-b4-prep-khorenko-vstor-103860-4-doexec-vfio-bars-v2-0-2237f8fbb38e@virtuozzo.com>

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@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

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

      parent reply	other threads:[~2026-10-16 21:37 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-16 21:35 [Devel] [PATCH hci-8.1 v2 0/2] mm: allow MADV_DOEXEC on VFIO BAR mappings Konstantin Khorenko
2026-10-16 21:35 ` [Devel] [PATCH hci-8.1 v2 1/2] mm: allow MADV_DOEXEC on shared PFN mappings of device memory Konstantin Khorenko
2026-10-16 21:35 ` 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=20261016-b4-prep-khorenko-vstor-103860-4-doexec-vfio-bars-v2-2-2237f8fbb38e@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