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
prev 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.