1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * 32-bit test to check vDSO mremap. |
4 | * |
5 | * Copyright (c) 2016 Dmitry Safonov |
6 | * Suggested-by: Andrew Lutomirski |
7 | */ |
8 | /* |
9 | * Can be built statically: |
10 | * gcc -Os -Wall -static -m32 test_mremap_vdso.c |
11 | */ |
12 | #define _GNU_SOURCE |
13 | #include <stdio.h> |
14 | #include <errno.h> |
15 | #include <unistd.h> |
16 | #include <string.h> |
17 | |
18 | #include <sys/mman.h> |
19 | #include <sys/auxv.h> |
20 | #include <sys/syscall.h> |
21 | #include <sys/wait.h> |
22 | |
23 | #define PAGE_SIZE 4096 |
24 | |
25 | static int try_to_remap(void *vdso_addr, unsigned long size) |
26 | { |
27 | void *dest_addr, *new_addr; |
28 | |
29 | /* Searching for memory location where to remap */ |
30 | dest_addr = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); |
31 | if (dest_addr == MAP_FAILED) { |
32 | printf("[WARN]\tmmap failed (%d): %m\n" , errno); |
33 | return 0; |
34 | } |
35 | |
36 | printf("[NOTE]\tMoving vDSO: [%p, %#lx] -> [%p, %#lx]\n" , |
37 | vdso_addr, (unsigned long)vdso_addr + size, |
38 | dest_addr, (unsigned long)dest_addr + size); |
39 | fflush(stdout); |
40 | |
41 | new_addr = mremap(vdso_addr, size, size, |
42 | MREMAP_FIXED|MREMAP_MAYMOVE, dest_addr); |
43 | if ((unsigned long)new_addr == (unsigned long)-1) { |
44 | munmap(dest_addr, size); |
45 | if (errno == EINVAL) { |
46 | printf("[NOTE]\tvDSO partial move failed, will try with bigger size\n" ); |
47 | return -1; /* Retry with larger */ |
48 | } |
49 | printf("[FAIL]\tmremap failed (%d): %m\n" , errno); |
50 | return 1; |
51 | } |
52 | |
53 | return 0; |
54 | |
55 | } |
56 | |
57 | int main(int argc, char **argv, char **envp) |
58 | { |
59 | pid_t child; |
60 | |
61 | child = fork(); |
62 | if (child == -1) { |
63 | printf("[WARN]\tfailed to fork (%d): %m\n" , errno); |
64 | return 1; |
65 | } |
66 | |
67 | if (child == 0) { |
68 | unsigned long vdso_size = PAGE_SIZE; |
69 | unsigned long auxval; |
70 | int ret = -1; |
71 | |
72 | auxval = getauxval(AT_SYSINFO_EHDR); |
73 | printf("\tAT_SYSINFO_EHDR is %#lx\n" , auxval); |
74 | if (!auxval || auxval == -ENOENT) { |
75 | printf("[WARN]\tgetauxval failed\n" ); |
76 | return 0; |
77 | } |
78 | |
79 | /* Simpler than parsing ELF header */ |
80 | while (ret < 0) { |
81 | ret = try_to_remap(vdso_addr: (void *)auxval, size: vdso_size); |
82 | vdso_size += PAGE_SIZE; |
83 | } |
84 | |
85 | #ifdef __i386__ |
86 | /* Glibc is likely to explode now - exit with raw syscall */ |
87 | asm volatile ("int $0x80" : : "a" (__NR_exit), "b" (!!ret)); |
88 | #else /* __x86_64__ */ |
89 | syscall(SYS_exit, ret); |
90 | #endif |
91 | } else { |
92 | int status; |
93 | |
94 | if (waitpid(child, &status, 0) != child || |
95 | !WIFEXITED(status)) { |
96 | printf("[FAIL]\tmremap() of the vDSO does not work on this kernel!\n" ); |
97 | return 1; |
98 | } else if (WEXITSTATUS(status) != 0) { |
99 | printf("[FAIL]\tChild failed with %d\n" , |
100 | WEXITSTATUS(status)); |
101 | return 1; |
102 | } |
103 | printf("[OK]\n" ); |
104 | } |
105 | |
106 | return 0; |
107 | } |
108 | |