[Devel] [PATCH hci-8.1 7/7] selftests/mm: check MADV_DOEXEC on /dev/zero and exec permission
Konstantin Khorenko
khorenko at virtuozzo.com
Wed Oct 14 20:12:26 MSK 2026
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 at 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
More information about the Devel
mailing list