1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2024 SUSE LLC <[email protected]>
4 */
5
6 /*\
7 * [Description]
8 *
9 * Test that address space layout randomization (ASLR) is sufficiently random.
10 * A bug in dynamic library mmapping may reduce ASLR randomness if the library
11 * file is larger than hugepage size. In 32bit compat mode, this may
12 * completely disable ASLR and force large dynamic libraries to be loaded
13 * at fixed addresses.
14 *
15 * The issue may not be reproducible if hugepage support is missing or no
16 * sufficiently large library is loaded into the test program. If libc is not
17 * large enough, you may use `export LD_PRELOAD=...` to load another
18 * sufficiently large library. The export keyword is required because
19 * the checks are done on a subprocess.
20 *
21 * In normal mode, the test checks that library base address has a minimum
22 * number of random bits (configurable using the -b option). In strict mode,
23 * the test checks that library base address is aligned to regular pagesize
24 * (not hugepage) and the number of random bits is at least
25 * CONFIG_ARCH_MMAP_RND_BITS_MIN or the compat equivalent. The -b option is
26 * ignored.
27 */
28
29 #include <unistd.h>
30 #include <string.h>
31 #include <stdio.h>
32 #include <inttypes.h>
33 #include <sys/wait.h>
34
35 #include "tst_test.h"
36 #include "tst_kernel.h"
37 #include "tst_kconfig.h"
38 #include "tst_safe_stdio.h"
39
40 /* Indices for aslr_kconfigs[] below */
41 #define ASLR_HAVE_COMPAT 0
42 #define ASLR_MINBITS 1
43 #define ASLR_COMPAT_MINBITS 2
44
45 static int pagebits, minbits = 8;
46 static char *minbits_str, *strict_check;
47 static char lib_path[PATH_MAX];
48 static FILE *ldd;
49
50 static struct tst_kconfig_var aslr_kconfigs[] = {
51 TST_KCONFIG_INIT("CONFIG_HAVE_ARCH_MMAP_RND_COMPAT_BITS"),
52 TST_KCONFIG_INIT("CONFIG_ARCH_MMAP_RND_BITS_MIN"),
53 TST_KCONFIG_INIT("CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN"),
54 };
55
count_align_bits(size_t val)56 static int count_align_bits(size_t val)
57 {
58 int ret = 0;
59
60 for (; val && !(val & 0x1); val >>= 1, ret++)
61 ;
62
63 return ret;
64 }
65
count_bits(size_t val)66 static int count_bits(size_t val)
67 {
68 int ret = 0;
69
70 for (; val; val >>= 1) {
71 if (val & 1)
72 ret++;
73 }
74
75 return ret;
76 }
77
78 /* Extract library path and base address from a line of ldd output. */
parse_mapping(const char * line,char * path,uint64_t * addr)79 static int parse_mapping(const char *line, char *path, uint64_t *addr)
80 {
81 int ret;
82
83 line = strchr(line, '/');
84
85 if (!line)
86 return 0;
87
88 ret = sscanf(line, "%s (0x%" PRIx64 ")", path, addr);
89 return ret >= 2;
90 }
91
92 /*
93 * Run ldd on the test executable and pass each library/address to callback.
94 * If the callback returns non-zero, the reader loop will immediately exit.
95 */
read_shared_libraries(int (* callback)(void *,const char *,uint64_t),void * arg)96 static void read_shared_libraries(int (*callback)(void*, const char*, uint64_t),
97 void *arg)
98 {
99 char line[PATH_MAX], path[PATH_MAX];
100 uint64_t addr;
101 int ret;
102
103 sprintf(path, "ldd /proc/%d/exe", getpid());
104 ldd = SAFE_POPEN(path, "r");
105
106 while (fgets(line, PATH_MAX, ldd)) {
107 if (*line && !feof(ldd) && line[strlen(line) - 1] != '\n')
108 tst_brk(TBROK, "Dynamic library entry too long");
109
110 if (!parse_mapping(line, path, &addr))
111 continue;
112
113 if (callback(arg, path, addr))
114 break;
115 }
116
117 while (fgets(line, PATH_MAX, ldd))
118 ;
119
120 ret = pclose(ldd);
121 ldd = NULL;
122
123 if (!WIFEXITED(ret) || WEXITSTATUS(ret))
124 tst_brk(TBROK, "Reading dynamic libraries failed");
125 }
126
find_large_lib_callback(void * arg,const char * path,uint64_t addr LTP_ATTRIBUTE_UNUSED)127 static int find_large_lib_callback(void *arg, const char *path,
128 uint64_t addr LTP_ATTRIBUTE_UNUSED)
129 {
130 size_t *libsize = arg;
131 struct stat statbuf;
132
133 SAFE_STAT(path, &statbuf);
134
135 if (*libsize < (size_t)statbuf.st_size) {
136 strcpy(lib_path, path);
137 *libsize = statbuf.st_size;
138 }
139
140 return 0;
141 }
142
find_large_lib(void)143 static void find_large_lib(void)
144 {
145 size_t hpsize, libsize = 0;
146
147 read_shared_libraries(find_large_lib_callback, &libsize);
148
149 if (!libsize) {
150 tst_brk(TCONF,
151 "No dynamic libraries loaded, please use LD_PRELOAD");
152 }
153
154 hpsize = tst_get_hugepage_size();
155 tst_res(TINFO, "Largest loaded library: %s (%zu bytes)", lib_path,
156 libsize);
157
158 if (!hpsize) {
159 tst_res(TCONF, "Hugepage support appears to be missing");
160 } else if (libsize < hpsize) {
161 tst_res(TCONF, "The largest dynamic library is smaller than hugepage size, "
162 "please use LD_PRELOAD to add larger library");
163 }
164 }
165
get_lib_address_callback(void * arg,const char * path,uint64_t addr)166 static int get_lib_address_callback(void *arg, const char *path, uint64_t addr)
167 {
168 uint64_t *out_addr = arg;
169
170 if (!strcmp(path, lib_path)) {
171 *out_addr = addr;
172 return 1;
173 }
174
175 return 0;
176 }
177
setup(void)178 static void setup(void)
179 {
180 int compat = tst_is_compat_mode();
181 const char *kconf_minbits, *minbits_path;
182
183 pagebits = count_align_bits(getpagesize());
184 tst_kconfig_read(aslr_kconfigs, ARRAY_SIZE(aslr_kconfigs));
185
186 if (compat && aslr_kconfigs[ASLR_HAVE_COMPAT].choice != 'y')
187 tst_brk(TCONF, "ASLR not supported in compat mode");
188
189 if (!strict_check && tst_parse_int(minbits_str, &minbits, 1, 64))
190 tst_brk(TBROK, "Invalid bit count argument '%s'", minbits_str);
191
192 if (strict_check) {
193 if (compat) {
194 kconf_minbits = aslr_kconfigs[ASLR_COMPAT_MINBITS].val;
195 minbits_path = "/proc/sys/vm/mmap_rnd_compat_bits";
196 } else {
197 kconf_minbits = aslr_kconfigs[ASLR_MINBITS].val;
198 minbits_path = "/proc/sys/vm/mmap_rnd_bits";
199 }
200
201 /*
202 * Reading mmap_rnd_bits usually requires root privileges.
203 * Fall back to kernel config values if unprivileged.
204 */
205 if (!access(minbits_path, R_OK))
206 SAFE_FILE_SCANF(minbits_path, "%d", &minbits);
207 else if (!kconf_minbits)
208 tst_brk(TBROK, "Cannot determine kernel ASLR min bits");
209 else if (tst_parse_int(kconf_minbits, &minbits, 1, 64))
210 tst_brk(TBROK, "Invalid kernel ASLR min bits value");
211 }
212
213 find_large_lib();
214 }
215
run(void)216 static void run(void)
217 {
218 uint64_t rndbits = 0, fixbits, addr;
219 int rndcount, aligncount, i;
220
221 fixbits = ~rndbits;
222
223 for (i = 0; i < 512; i++) {
224 addr = 0;
225 read_shared_libraries(get_lib_address_callback, &addr);
226
227 if (!addr) {
228 tst_res(TWARN, "Library %s not found?!", lib_path);
229 continue;
230 }
231
232 rndbits |= addr;
233 fixbits &= addr;
234 }
235
236 rndcount = count_bits(rndbits & ~fixbits);
237 aligncount = count_align_bits(rndbits);
238
239 if (rndcount < minbits) {
240 tst_res(TFAIL,
241 "Large lib base address has less than %d random bits",
242 minbits);
243 return;
244 }
245
246 tst_res(TPASS, "Library address has %d random bits", rndcount);
247 tst_res(TINFO, "Library base address alignment: %d bits", aligncount);
248
249 if (aligncount > pagebits) {
250 tst_res(strict_check ? TFAIL : TINFO,
251 "Base address alignment is higher than expected (%d)",
252 pagebits);
253 }
254 }
255
cleanup(void)256 static void cleanup(void)
257 {
258 if (ldd) {
259 char buf[PATH_MAX];
260
261 while (fgets(buf, PATH_MAX, ldd))
262 ;
263
264 pclose(ldd);
265 }
266 }
267
268 static struct tst_test test = {
269 .test_all = run,
270 .setup = setup,
271 .cleanup = cleanup,
272 .forks_child = 1,
273 .options = (struct tst_option []) {
274 {"b:", &minbits_str, "Minimum ASLR random bits (default: 8)"},
275 {"s", &strict_check, "Run in strict mode"},
276 {}
277 },
278 .needs_kconfigs = (const char *[]) {
279 "CONFIG_HAVE_ARCH_MMAP_RND_BITS=y",
280 NULL
281 },
282 .needs_cmds = (const char *[]) {
283 "ldd",
284 NULL
285 },
286 };
287