From: Konstantin Khorenko <khorenko@virtuozzo.com>
To: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>,
Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>,
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
Cc: devel@openvz.org
Subject: [Devel] [PATCH hci-8.1 3/7] selftests/mm: cover large, swapped and partly marked THP ranges
Date: Wed, 14 Oct 2026 19:15:01 +0200 [thread overview]
Message-ID: <20261014-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v1-3-b23b454691db@virtuozzo.com> (raw)
In-Reply-To: <20261014-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v1-0-b23b454691db@virtuozzo.com>
Add cases for the kinds of memory a preserved range can hold:
- A 1 GB range touched every 2 MB, large enough for vma_dup() to split
the copy into several padata jobs with CONFIG_PADATA.
- A range pushed to swap with MADV_PAGEOUT, so that the copy takes swap
entries and the new image swaps the pages back in. Skipped when none
of the pages reaches swap.
- A THP of which only the first half is marked, which splits its PMD
and makes the copy batch PTEs of a large folio. Every page of the
second half must be absent in the new image. Skipped unless smaps
shows the range mapped by a THP before marking.
Both base page cases tolerate EINVAL from MADV_NOHUGEPAGE, which a
kernel without THP returns.
https://virtuozzo.atlassian.net/browse/VSTOR-150234
Feature: oracle/mm: MADV_DOEXEC madvise() flag
Assisted-by: LLM
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
tools/testing/selftests/mm/doexec.c | 132 +++++++++++++++++++++++++++++++++---
1 file changed, 124 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
index 5eaf752b8413c..ab1d461de5517 100644
--- a/tools/testing/selftests/mm/doexec.c
+++ b/tools/testing/selftests/mm/doexec.c
@@ -56,6 +56,9 @@
#ifndef MADV_WIPEONFORK
#define MADV_WIPEONFORK 18
#endif
+#ifndef MADV_PAGEOUT
+#define MADV_PAGEOUT 21
+#endif
/* The downstream ELF loader requires this opt-in in a PT_NOTE segment. */
asm(".pushsection .note.doexec,\"a\",%note\n"
@@ -642,29 +645,35 @@ static int doexec_noexec(void)
return ret;
}
-static bool smaps_has_flag(char *addr, const char *flag)
+/* Copy the line of the smaps entry of the VMA at @addr that starts with @prefix. */
+static bool smaps_line(char *addr, const char *prefix, char *line, size_t len)
{
FILE *file = fopen("/proc/self/smaps", "r");
- char line[256], want[32];
bool in_vma = false, found = false;
+ char want[32];
if (!file)
return false;
snprintf(want, sizeof(want), "%lx-", (unsigned long)addr);
- while (fgets(line, sizeof(line), file)) {
+ while (!found && fgets(line, len, file)) {
char *space = strchr(line, ' ');
- if (!space)
- continue;
- if (memchr(line, '-', space - line) && !memchr(line, ':', space - line))
+ if (space && 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;
+ else if (in_vma)
+ found = !strncmp(line, prefix, strlen(prefix));
}
fclose(file);
return found;
}
+static bool smaps_has_flag(char *addr, const char *flag)
+{
+ char line[512];
+
+ return smaps_line(addr, "VmFlags:", line, sizeof(line)) && strstr(line, flag);
+}
+
static int after_pfnmap_exec(char *bar, size_t size)
{
size_t page = sysconf(_SC_PAGESIZE), i;
@@ -1704,6 +1713,109 @@ static int stack_overlap_exec(void)
}
#endif
+/* Large enough for vma_dup() to split the copy between several threads. */
+static int sparse_exec(void)
+{
+ const size_t size = 1ul << 30, stride = 2ul << 20;
+ char *ram = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+ size_t i;
+
+ if (ram == MAP_FAILED)
+ return KSFT_SKIP;
+ /* EINVAL: no THP support, the range is mapped with base pages anyway. */
+ if (madvise(ram, size, MADV_NOHUGEPAGE) && errno != EINVAL)
+ return KSFT_FAIL;
+ for (i = 0; i < size; i += stride)
+ ram[i] = 0x31;
+ if (madvise(ram, size, MADV_DOEXEC))
+ return unsupported();
+ check_exec(ram, size, stride, false);
+ return KSFT_FAIL;
+}
+
+static bool page_swapped(char *addr)
+{
+ size_t page = sysconf(_SC_PAGESIZE);
+ int fd = open("/proc/self/pagemap", O_RDONLY);
+ uint64_t entry = 0;
+
+ if (fd < 0)
+ return false;
+ if (pread(fd, &entry, sizeof(entry), (uintptr_t)addr / page * sizeof(entry)) !=
+ sizeof(entry))
+ entry = 0;
+ close(fd);
+ return entry & (1ull << 62);
+}
+
+/* Swap entries are copied, and the new image swaps the pages back in. */
+static int swapped_exec(void)
+{
+ size_t page = sysconf(_SC_PAGESIZE), size = 64 * page, i;
+ char *ram = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ if (ram == MAP_FAILED ||
+ (madvise(ram, size, MADV_NOHUGEPAGE) && errno != EINVAL))
+ return KSFT_FAIL;
+ memset(ram, 0x31, size);
+ if (madvise(ram, size, MADV_PAGEOUT))
+ return KSFT_SKIP;
+ for (i = 0; i < size && !page_swapped(ram + i); i += page)
+ ;
+ if (i == size)
+ return KSFT_SKIP;
+ if (madvise(ram, size, MADV_DOEXEC))
+ return unsupported();
+ check_exec(ram, size, page, false);
+ return KSFT_FAIL;
+}
+
+static unsigned long smaps_kb(char *addr, const char *field)
+{
+ char line[256];
+
+ if (!smaps_line(addr, field, line, sizeof(line)))
+ return 0;
+ return strtoul(line + strlen(field), NULL, 10);
+}
+
+/* Marking half of a THP splits its PMD; the PTE-mapped half carries over. */
+static int thp_half_exec(void)
+{
+ size_t size = huge_page_size();
+ char *area, *ram;
+
+ if (!size)
+ return KSFT_SKIP;
+ area = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (area == MAP_FAILED)
+ return KSFT_FAIL;
+ ram = (char *)(((uintptr_t)area + size - 1) & ~(size - 1));
+ if (madvise(ram, size, MADV_HUGEPAGE))
+ return KSFT_SKIP;
+ memset(ram, 0x31, size);
+ madvise(ram, size, MADV_COLLAPSE);
+ if (smaps_kb(ram, "AnonHugePages:") < size >> 10)
+ return KSFT_SKIP;
+ if (madvise(ram, size / 2, MADV_DOEXEC))
+ return unsupported();
+ exec_mode("/proc/self/exe", "--thp-half", ram, size);
+ return KSFT_FAIL;
+}
+
+static int after_thp_half(char *ram, size_t size)
+{
+ size_t page = sysconf(_SC_PAGESIZE), off;
+
+ for (off = size / 2; off < size; off += page)
+ if (range_mapped(ram + off, page))
+ return fail("unmarked half preserved");
+ return check_contents(ram, size / 2, 0x31);
+}
+
struct after_mode {
const char *mode;
int (*fn)(char *ram, size_t size);
@@ -1719,6 +1831,7 @@ static const struct after_mode after_modes[] = {
{ "--wipeonfork", after_wipeonfork },
{ "--no-uffd", after_no_uffd },
{ "--mempolicy", after_mempolicy },
+ { "--thp-half", after_thp_half },
};
struct more_case {
@@ -1742,6 +1855,9 @@ static const struct more_case more_cases[] = {
{ "opt-in note not in the last PT_NOTE: preserved", note_not_last_exec },
{ "malformed opt-in note not in the last PT_NOTE: skipped", bad_note_not_last_exec },
{ "preserved range under the new stack: exec kills the task", stack_overlap_exec },
+ { "1 GB sparse range: preserved", sparse_exec },
+ { "swapped out pages: preserved", swapped_exec },
+ { "half of a THP: preserved", thp_half_exec },
};
int main(int argc, char **argv)
--
2.43.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
next prev parent reply other threads:[~2026-10-14 17:16 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 17:14 [Devel] [PATCH hci-8.1 0/7] selftests/mm: broader MADV_DOEXEC coverage Konstantin Khorenko
2026-10-14 17:14 ` [Devel] [PATCH hci-8.1 1/7] selftests/mm: cover the MADV_DOEXEC interface and VMA attributes Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 2/7] selftests/mm: cover the MADV_DOEXEC opt-in note and the new stack Konstantin Khorenko
2026-10-14 17:15 ` Konstantin Khorenko [this message]
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 4/7] selftests/mm: check that MADV_DOEXEC refuses an exec from a vfork() child Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 5/7] selftests/mm: check VM_MAYEXEC of MADV_DOEXEC ranges in smaps Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 6/7] selftests/mm: check who can still reach the old mm of a preserving exec Konstantin Khorenko
2026-10-14 17:15 ` [Devel] [PATCH hci-8.1 7/7] selftests/mm: say why a doexec case fails Konstantin Khorenko
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-149847-5-doexec-selftests-v1-3-b23b454691db@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.