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