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 #include <stdbool.h>
18
19 #include <sys/mman.h>
20 #include <sys/auxv.h>
21 #include <sys/syscall.h>
22 #include <sys/wait.h>
23 #include "../kselftest.h"
24
25 #define PAGE_SIZE 4096
26
try_to_remap(void * vdso_addr,unsigned long size)27 static int try_to_remap(void *vdso_addr, unsigned long size)
28 {
29 void *dest_addr, *new_addr;
30
31 /* Searching for memory location where to remap */
32 dest_addr = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
33 if (dest_addr == MAP_FAILED) {
34 ksft_print_msg("WARN: mmap failed (%d): %m\n", errno);
35 return 0;
36 }
37
38 ksft_print_msg("Moving vDSO: [%p, %#lx] -> [%p, %#lx]\n",
39 vdso_addr, (unsigned long)vdso_addr + size,
40 dest_addr, (unsigned long)dest_addr + size);
41 fflush(stdout);
42
43 new_addr = mremap(vdso_addr, size, size,
44 MREMAP_FIXED|MREMAP_MAYMOVE, dest_addr);
45 if ((unsigned long)new_addr == (unsigned long)-1) {
46 munmap(dest_addr, size);
47 if (errno == EINVAL) {
48 ksft_print_msg("vDSO partial move failed, will try with bigger size\n");
49 return -1; /* Retry with larger */
50 }
51 ksft_print_msg("[FAIL]\tmremap failed (%d): %m\n", errno);
52 return 1;
53 }
54
55 return 0;
56
57 }
58
59 #define VDSO_NAME "[vdso]"
60 #define VMFLAGS "VmFlags:"
61 #define MSEAL_FLAGS "sl"
62 #define MAX_LINE_LEN 512
63
vdso_sealed(FILE * maps)64 bool vdso_sealed(FILE *maps)
65 {
66 char line[MAX_LINE_LEN];
67 bool has_vdso = false;
68
69 while (fgets(line, sizeof(line), maps)) {
70 if (strstr(line, VDSO_NAME))
71 has_vdso = true;
72
73 if (has_vdso && !strncmp(line, VMFLAGS, strlen(VMFLAGS))) {
74 if (strstr(line, MSEAL_FLAGS))
75 return true;
76
77 return false;
78 }
79 }
80
81 return false;
82 }
83
main(int argc,char ** argv,char ** envp)84 int main(int argc, char **argv, char **envp)
85 {
86 pid_t child;
87 FILE *maps;
88
89 ksft_print_header();
90 ksft_set_plan(1);
91
92 maps = fopen("/proc/self/smaps", "r");
93 if (!maps) {
94 ksft_test_result_skip(
95 "Could not open /proc/self/smaps, errno=%d\n",
96 errno);
97
98 return 0;
99 }
100
101 if (vdso_sealed(maps)) {
102 ksft_test_result_skip("vdso is sealed\n");
103 return 0;
104 }
105
106 fclose(maps);
107
108 child = fork();
109 if (child == -1)
110 ksft_exit_fail_msg("failed to fork (%d): %m\n", errno);
111
112 if (child == 0) {
113 unsigned long vdso_size = PAGE_SIZE;
114 unsigned long auxval;
115 int ret = -1;
116
117 auxval = getauxval(AT_SYSINFO_EHDR);
118 ksft_print_msg("AT_SYSINFO_EHDR is %#lx\n", auxval);
119 if (!auxval || auxval == -ENOENT) {
120 ksft_print_msg("WARN: getauxval failed\n");
121 return 0;
122 }
123
124 /* Simpler than parsing ELF header */
125 while (ret < 0) {
126 ret = try_to_remap((void *)auxval, vdso_size);
127 vdso_size += PAGE_SIZE;
128 }
129
130 #ifdef __i386__
131 /* Glibc is likely to explode now - exit with raw syscall */
132 asm volatile ("int $0x80" : : "a" (__NR_exit), "b" (!!ret));
133 #else /* __x86_64__ */
134 syscall(SYS_exit, ret);
135 #endif
136 } else {
137 int status;
138
139 if (waitpid(child, &status, 0) != child ||
140 !WIFEXITED(status))
141 ksft_test_result_fail("mremap() of the vDSO does not work on this kernel!\n");
142 else if (WEXITSTATUS(status) != 0)
143 ksft_test_result_fail("Child failed with %d\n", WEXITSTATUS(status));
144 else
145 ksft_test_result_pass("%s\n", __func__);
146 }
147
148 ksft_finished();
149 }
150