xref: /aosp_15_r20/external/ltp/testcases/kernel/security/aslr/aslr01.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
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