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 7/7] selftests/mm: check MADV_DOEXEC on /dev/zero and exec permission
Date: Wed, 14 Oct 2026 19:12:26 +0200 [thread overview]
Message-ID: <20261014-b4-prep-khorenko-vstor-149847-3-doexec-oracle-fixes-v1-7-e34cbe0d1440@virtuozzo.com> (raw)
In-Reply-To: <20261014-b4-prep-khorenko-vstor-149847-3-doexec-oracle-fixes-v1-0-e34cbe0d1440@virtuozzo.com>
Add two cases to doexec:
- A private /dev/zero mapping is marked and preserved across exec. The
mapping is anonymous but holds a reference to the file; a copy that
does not take its own shows up as an fput() underflow at teardown.
- A marked range cannot be made executable until MADV_DONTEXEC, and
MADV_DONTEXEC does not make a /dev/zero mapping from a noexec tmpfs
executable. The noexec part needs a private mount namespace, a tmpfs
mount and a /dev/zero node on it; the case is reported as skipped
when it cannot get them.
https://virtuozzo.atlassian.net/browse/VSTOR-150232
Feature: oracle/mm: MADV_DOEXEC madvise() flag
Assisted-by: LLM
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
tools/testing/selftests/mm/doexec.c | 94 ++++++++++++++++++++++++++++++++++++-
1 file changed, 92 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c
index dfa3da2c43934..97e045ba678e8 100644
--- a/tools/testing/selftests/mm/doexec.c
+++ b/tools/testing/selftests/mm/doexec.c
@@ -17,10 +17,12 @@
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
+#include <sys/mount.h>
#include <sys/resource.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/syscall.h>
+#include <sys/sysmacros.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -501,6 +503,88 @@ static int setuid_exec(void)
return ret;
}
+/* A private /dev/zero mapping is anonymous but holds a file reference. */
+static int devzero_exec(void)
+{
+ size_t size = 16 * sysconf(_SC_PAGESIZE);
+ int fd = open("/dev/zero", O_RDWR);
+ char *ram;
+
+ if (fd < 0)
+ return KSFT_SKIP;
+ ram = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (ram == MAP_FAILED)
+ return KSFT_FAIL;
+ memset(ram, 0x31, size);
+ if (madvise(ram, size, MADV_DOEXEC))
+ return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ check_exec(ram, size, 1, false);
+ return KSFT_FAIL;
+}
+
+static bool exec_refused(char *ram, size_t size)
+{
+ return mprotect(ram, size, PROT_READ | PROT_EXEC) && errno == EACCES;
+}
+
+/* MADV_DONTEXEC must not grant more than mmap() did on a noexec mount. */
+static int noexec_dontexec(void)
+{
+ size_t size = sysconf(_SC_PAGESIZE);
+ char dir[] = "/tmp/doexec-noexec.XXXXXX", node[64];
+ int fd, ret = KSFT_FAIL;
+ char *ram;
+
+ if (unshare(CLONE_NEWNS) || mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) ||
+ !mkdtemp(dir))
+ return KSFT_SKIP;
+ if (mount("tmpfs", dir, "tmpfs", MS_NOEXEC, NULL)) {
+ rmdir(dir);
+ return KSFT_SKIP;
+ }
+ snprintf(node, sizeof(node), "%s/zero", dir);
+ fd = mknod(node, S_IFCHR | 0600, makedev(1, 5)) ? -1 : open(node, O_RDWR);
+ if (fd < 0) {
+ ret = KSFT_SKIP;
+ goto out;
+ }
+ ram = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (ram == MAP_FAILED || !exec_refused(ram, size)) {
+ ret = KSFT_SKIP;
+ goto out;
+ }
+ if (madvise(ram, size, MADV_DOEXEC)) {
+ ret = errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ goto out;
+ }
+ if (!madvise(ram, size, MADV_DONTEXEC) && exec_refused(ram, size))
+ ret = KSFT_PASS;
+out:
+ umount2(dir, MNT_DETACH);
+ rmdir(dir);
+ return ret;
+}
+
+/* A marked range cannot become executable until it is unmarked. */
+static int doexec_noexec(void)
+{
+ size_t size = sysconf(_SC_PAGESIZE);
+ char *ram = preserved_ram(size);
+ int ret;
+
+ if (!ram)
+ return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL;
+ if (!exec_refused(ram, size) || madvise(ram, size, MADV_DONTEXEC) ||
+ mprotect(ram, size, PROT_READ | PROT_EXEC))
+ return KSFT_FAIL;
+ ret = noexec_dontexec();
+ if (ret == KSFT_SKIP)
+ ksft_print_msg("noexec mount part skipped\n");
+ return ret;
+}
+
static void *exec_thread(void *arg)
{
check_exec(arg, sysconf(_SC_PAGESIZE), 1, false);
@@ -861,7 +945,7 @@ int main(int argc, char **argv)
return KSFT_PASS;
}
ksft_print_header();
- ksft_set_plan(NR_TESTS + 9);
+ ksft_set_plan(NR_TESTS + 11);
for (kind = 0; kind < NR_TESTS; kind++) {
fflush(stdout);
pid = fork();
@@ -884,7 +968,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 < 8; kind++) {
+ for (kind = 0; kind < 10; kind++) {
static const char * const extra_names[] = {
"CLONE_VM: recoverable EBUSY and retry",
"own threads: preserving exec allowed",
@@ -894,6 +978,8 @@ int main(int argc, char **argv)
"proc readers: repeated preserving exec allowed",
"concurrent CLONE_VM: shared memory is not stolen",
"setuid exec: preserving exec refused",
+ "private /dev/zero mapping: preserved across exec",
+ "marked range: not executable until unmarked",
};
fflush(stdout);
@@ -915,6 +1001,10 @@ int main(int argc, char **argv)
_exit(clone_exec_race());
case 7:
_exit(setuid_exec());
+ case 8:
+ _exit(devzero_exec());
+ case 9:
+ _exit(doexec_noexec());
}
}
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:14 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 17:12 [Devel] [PATCH hci-8.1 0/7] mm, exec: fix the MADV_DOEXEC code we carry from Oracle Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 1/7] oracle/mm: take a file reference when preserving a file-backed VMA Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 2/7] oracle/mm: refuse MADV_DOEXEC on special mappings Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 3/7] oracle/mm: keep a MADV_DOEXEC range from becoming executable Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 4/7] oracle/exec, elf: look for the opt-in note in every PT_NOTE segment Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 5/7] exec: refuse to preserve memory across a privilege-changing exec Konstantin Khorenko
2026-10-14 17:12 ` [Devel] [PATCH hci-8.1 6/7] selftests/mm: check that a setuid exec refuses preserved memory Konstantin Khorenko
2026-10-14 17:12 ` 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-149847-3-doexec-oracle-fixes-v1-7-e34cbe0d1440@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.