[PATCH v3 23/23] x86/vdso/selftest: Add a test for unmapping vDSO

From: Dmitry Safonov
Date: Fri Jun 11 2021 - 14:04:46 EST


Output for landing on x86:
> [root@localhost ~]# ./test_munmap_vdso_64
> AT_SYSINFO_EHDR is 0x7fffead9f000
> [NOTE] unmapping vDSO: [0x7fffead9f000, 0x7fffeada0000]
> [NOTE] vDSO partial move failed, will try with bigger size
> [NOTE] unmapping vDSO: [0x7fffead9f000, 0x7fffeada1000]
> [OK]
> [root@localhost ~]# ./test_munmap_vdso_32
> AT_SYSINFO_EHDR is 0xf7eef000
> [NOTE] unmapping vDSO: [0xf7eef000, 0xf7ef0000]
> [NOTE] vDSO partial move failed, will try with bigger size
> [NOTE] unmapping vDSO: [0xf7eef000, 0xf7ef1000]
> [OK]

The test also can check force_sigsegv(SIGSEGV) in do_fast_syscall_32():
> [root@localhost ~]# ./test_munmap_vdso_32 sysenter
> [NOTE] Using sysenter after munmap
> AT_SYSINFO_EHDR is 0xf7efe000
> [NOTE] unmapping vDSO: [0xf7efe000, 0xf7eff000]
> [NOTE] vDSO partial move failed, will try with bigger size
> [NOTE] unmapping vDSO: [0xf7efe000, 0xf7f00000]
> [OK] 32-bit process gets segfault on fast syscall with unmapped vDSO

Cc: Shuah Khan <shuah@xxxxxxxxxx>
Signed-off-by: Dmitry Safonov <dima@xxxxxxxxxx>
---
tools/testing/selftests/x86/.gitignore | 1 +
tools/testing/selftests/x86/Makefile | 11 +-
.../testing/selftests/x86/test_munmap_vdso.c | 151 ++++++++++++++++++
3 files changed, 158 insertions(+), 5 deletions(-)
create mode 100644 tools/testing/selftests/x86/test_munmap_vdso.c

diff --git a/tools/testing/selftests/x86/.gitignore b/tools/testing/selftests/x86/.gitignore
index 1aaef5bf119a..9ce8337e8fa0 100644
--- a/tools/testing/selftests/x86/.gitignore
+++ b/tools/testing/selftests/x86/.gitignore
@@ -6,6 +6,7 @@ sysret_ss_attrs
syscall_nt
ptrace_syscall
test_mremap_vdso
+test_munmap_vdso
check_initial_reg_state
sigreturn
ldt_gdt
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 333980375bc7..43016351ddb3 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -10,12 +10,13 @@ CAN_BUILD_I386 := $(shell ./check_cc.sh $(CC) trivial_32bit_program.c -m32)
CAN_BUILD_X86_64 := $(shell ./check_cc.sh $(CC) trivial_64bit_program.c)
CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh $(CC) trivial_program.c -no-pie)

-TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \
- check_initial_reg_state sigreturn iopl ioperm \
- test_vsyscall mov_ss_trap \
+TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt \
+ test_mremap_vdso test_munmap_vdso \
+ check_initial_reg_state sigreturn iopl ioperm \
+ test_vsyscall mov_ss_trap \
syscall_arg_fault fsgsbase_restore
-TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
- test_FCMOV test_FCOMI test_FISTTP \
+TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
+ test_FCMOV test_FCOMI test_FISTTP \
vdso_restorer
TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering
# Some selftests require 32bit support enabled also on 64bit systems
diff --git a/tools/testing/selftests/x86/test_munmap_vdso.c b/tools/testing/selftests/x86/test_munmap_vdso.c
new file mode 100644
index 000000000000..f56433dae279
--- /dev/null
+++ b/tools/testing/selftests/x86/test_munmap_vdso.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 32/64-bit test to check vDSO munmap.
+ *
+ * Copyright (c) 2021 Dmitry Safonov
+ */
+/*
+ * Can be built statically:
+ * gcc -Os -Wall -static -m32 test_munmap_vdso.c
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/mman.h>
+#include <sys/auxv.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#define PAGE_SIZE 4096
+
+static int try_to_unmap(void *vdso_addr, unsigned long size)
+{
+ int ret;
+
+ printf("[NOTE]\tunmapping vDSO: [%p, %#lx]\n",
+ vdso_addr, (unsigned long)vdso_addr + size);
+ fflush(stdout);
+
+#ifdef __i386__
+ /* vDSO is a landing for fast syscalls - don't use it for munmap() */
+ asm volatile ("int $0x80" : "=a" (ret)
+ : "a" (SYS_munmap),
+ "b" (vdso_addr),
+ "c" (size));
+ errno = -ret;
+#else /* __x86_64__ */
+ ret = munmap(vdso_addr, size);
+#endif
+ if (ret) {
+ if (errno == EINVAL) {
+ printf("[NOTE]\tvDSO partial move failed, will try with bigger size\n");
+ return -1; /* Retry with larger */
+ }
+ printf("[FAIL]\tmunmap failed (%d): %m\n", errno);
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv, char **envp)
+{
+ pid_t child;
+
+#ifdef __i386__
+ enum syscall_type_t {
+ INT80, SYSCALL32, SYSENTER
+ } syscall_type = INT80;
+
+ if (argc > 1) {
+ if (!strcmp(argv[1], "syscall32")) {
+ syscall_type = SYSCALL32;
+ printf("[NOTE]\tUsing syscall32 after munmap\n");
+ } else if (!strcmp(argv[1], "sysenter")) {
+ syscall_type = SYSENTER;
+ printf("[NOTE]\tUsing sysenter after munmap\n");
+ }
+ }
+#endif
+
+ child = fork();
+ if (child == -1) {
+ printf("[WARN]\tfailed to fork (%d): %m\n", errno);
+ return 1;
+ }
+
+ if (child == 0) {
+ unsigned long vdso_size = PAGE_SIZE;
+ unsigned long auxval;
+ int ret = -1;
+
+ auxval = getauxval(AT_SYSINFO_EHDR);
+ printf("\tAT_SYSINFO_EHDR is %#lx\n", auxval);
+ if (!auxval || auxval == -ENOENT) {
+ printf("[WARN]\tgetauxval failed\n");
+ return 0;
+ }
+
+ /* Simpler than parsing ELF header */
+ while (ret < 0) {
+ ret = try_to_unmap((void *)auxval, vdso_size);
+ vdso_size += PAGE_SIZE;
+ }
+
+ /* Glibc is likely to explode now - exit with raw syscall */
+#ifdef __i386__
+ switch (syscall_type) {
+ case SYSCALL32:
+ asm volatile ("syscall" : : "a" (__NR_exit), "b" (!!ret));
+ case SYSENTER:
+ asm volatile ("sysenter" : : "a" (__NR_exit), "b" (!!ret));
+ default:
+ case INT80:
+ asm volatile ("int $0x80" : : "a" (__NR_exit), "b" (!!ret));
+ }
+#else /* __x86_64__ */
+ syscall(SYS_exit, ret);
+#endif
+ } else {
+ int status;
+
+ if (waitpid(child, &status, 0) != child) {
+ printf("[FAIL]\tUnexpected child, killing the expected one\n");
+ kill(child, SIGKILL);
+ return 1;
+ }
+
+
+#ifdef __i386__
+ switch (syscall_type) {
+ case SYSCALL32:
+ case SYSENTER:
+ if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) {
+ printf("[OK]\t32-bit process gets segfault on fast syscall with unmapped vDSO\n");
+ return 0;
+ }
+ default:
+ case INT80:
+ /* same as on x86_64 */
+ }
+#endif
+
+ if (!WIFEXITED(status)) {
+ printf("[FAIL]\tmunmap() of the vDSO does not work on this kernel!\n");
+ if (WIFSIGNALED(status))
+ printf("[FAIL]\tprocess crashed with %s\n",
+ strsignal(WTERMSIG(status)));
+ return 1;
+ } else if (WEXITSTATUS(status) != 0) {
+ printf("[FAIL]\tChild failed with %d\n",
+ WEXITSTATUS(status));
+ return 1;
+ }
+ printf("[OK]\n");
+ }
+
+ return 0;
+}
--
2.31.1