All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: Konstantin Khorenko <khorenko@virtuozzo.com>
To: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>,
	Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>,
	Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Cc: devel@openvz.org
Subject: [Devel] [PATCH hci-8.1 2/2] selftests/mm: check MADV_DOEXEC on VFIO BARs and other PFN mappings
Date: Wed, 14 Oct 2026 19:14:01 +0200	[thread overview]
Message-ID: <20261014-b4-prep-khorenko-vstor-103860-4-doexec-vfio-bars-v1-2-7be6c4d68114@virtuozzo.com> (raw)
In-Reply-To: <20261014-b4-prep-khorenko-vstor-103860-4-doexec-vfio-bars-v1-0-7be6c4d68114@virtuozzo.com>

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

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

      parent reply	other threads:[~2026-10-14 17:15 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 17:13 [Devel] [PATCH hci-8.1 0/2] mm: allow MADV_DOEXEC on VFIO BAR mappings Konstantin Khorenko
2026-10-14 17:14 ` [Devel] [PATCH hci-8.1 1/2] mm: allow MADV_DOEXEC on shared PFN mappings of device memory Konstantin Khorenko
2026-10-16 12:57   ` Andrey Drobyshev
2026-10-14 17:14 ` 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=20261014-b4-prep-khorenko-vstor-103860-4-doexec-vfio-bars-v1-2-7be6c4d68114@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.