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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox