xref: /netbsd-src/external/bsd/jemalloc/dist/test/unit/uaf.c (revision f8cf1a9151c7af1cb0bd8b09c13c66bca599c027)
1 #include "test/jemalloc_test.h"
2 #include "test/arena_util.h"
3 #include "test/san.h"
4 
5 #include "jemalloc/internal/cache_bin.h"
6 #include "jemalloc/internal/san.h"
7 #include "jemalloc/internal/safety_check.h"
8 
9 const char *malloc_conf = TEST_SAN_UAF_ALIGN_ENABLE;
10 
11 static size_t san_uaf_align;
12 
13 static bool fake_abort_called;
14 void fake_abort(const char *message) {
15 	(void)message;
16 	fake_abort_called = true;
17 }
18 
19 static void
20 test_write_after_free_pre(void) {
21 	safety_check_set_abort(&fake_abort);
22 	fake_abort_called = false;
23 }
24 
25 static void
26 test_write_after_free_post(void) {
27 	assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
28 	    0, "Unexpected tcache flush failure");
29 	expect_true(fake_abort_called, "Use-after-free check didn't fire.");
30 	safety_check_set_abort(NULL);
31 }
32 
33 static bool
34 uaf_detection_enabled(void) {
35 	if (!config_uaf_detection || !san_uaf_detection_enabled()) {
36 		return false;
37 	}
38 
39 	ssize_t lg_san_uaf_align;
40 	size_t sz = sizeof(lg_san_uaf_align);
41 	assert_d_eq(mallctl("opt.lg_san_uaf_align", &lg_san_uaf_align, &sz,
42 	    NULL, 0), 0, "Unexpected mallctl failure");
43 	if (lg_san_uaf_align < 0) {
44 		return false;
45 	}
46 	assert_zd_ge(lg_san_uaf_align, LG_PAGE, "san_uaf_align out of range");
47 	san_uaf_align = (size_t)1 << lg_san_uaf_align;
48 
49 	bool tcache_enabled;
50 	sz = sizeof(tcache_enabled);
51 	assert_d_eq(mallctl("thread.tcache.enabled", &tcache_enabled, &sz, NULL,
52 	    0), 0, "Unexpected mallctl failure");
53 	if (!tcache_enabled) {
54 		return false;
55 	}
56 
57 	return true;
58 }
59 
60 static size_t
61 read_tcache_stashed_bytes(unsigned arena_ind) {
62 	if (!config_stats) {
63 		return 0;
64 	}
65 
66 	uint64_t epoch;
67 	assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
68 	    0, "Unexpected mallctl() failure");
69 
70 	size_t tcache_stashed_bytes;
71 	size_t sz = sizeof(tcache_stashed_bytes);
72 	assert_d_eq(mallctl(
73 	    "stats.arenas." STRINGIFY(MALLCTL_ARENAS_ALL)
74 	    ".tcache_stashed_bytes", &tcache_stashed_bytes, &sz, NULL, 0), 0,
75 	    "Unexpected mallctl failure");
76 
77 	return tcache_stashed_bytes;
78 }
79 
80 static void
81 test_use_after_free(size_t alloc_size, bool write_after_free) {
82 	void *ptr = (void *)(uintptr_t)san_uaf_align;
83 	assert_true(cache_bin_nonfast_aligned(ptr), "Wrong alignment");
84 	ptr = (void *)((uintptr_t)123 * (uintptr_t)san_uaf_align);
85 	assert_true(cache_bin_nonfast_aligned(ptr), "Wrong alignment");
86 	ptr = (void *)((uintptr_t)san_uaf_align + 1);
87 	assert_false(cache_bin_nonfast_aligned(ptr), "Wrong alignment");
88 
89 	/*
90 	 * Disable purging (-1) so that all dirty pages remain committed, to
91 	 * make use-after-free tolerable.
92 	 */
93 	unsigned arena_ind = do_arena_create(-1, -1);
94 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
95 
96 	size_t n_max = san_uaf_align * 2;
97 	void **items = mallocx(n_max * sizeof(void *), flags);
98 	assert_ptr_not_null(items, "Unexpected mallocx failure");
99 
100 	bool found = false;
101 	size_t iter = 0;
102 	char magic = 's';
103 	assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
104 	    0, "Unexpected tcache flush failure");
105 	while (!found) {
106 		ptr = mallocx(alloc_size, flags);
107 		assert_ptr_not_null(ptr, "Unexpected mallocx failure");
108 
109 		found = cache_bin_nonfast_aligned(ptr);
110 		*(char *)ptr = magic;
111 		items[iter] = ptr;
112 		assert_zu_lt(iter++, n_max, "No aligned ptr found");
113 	}
114 
115 	if (write_after_free) {
116 		test_write_after_free_pre();
117 	}
118 	bool junked = false;
119 	while (iter-- != 0) {
120 		char *volatile mem = items[iter];
121 		assert_c_eq(*mem, magic, "Unexpected memory content");
122 		size_t stashed_before = read_tcache_stashed_bytes(arena_ind);
123 		free(mem);
124 		if (*mem != magic) {
125 			junked = true;
126 			assert_c_eq(*mem, (char)uaf_detect_junk,
127 			    "Unexpected junk-filling bytes");
128 			if (write_after_free) {
129 				*(char *)mem = magic + 1;
130 			}
131 
132 			size_t stashed_after = read_tcache_stashed_bytes(
133 			    arena_ind);
134 			/*
135 			 * An edge case is the deallocation above triggering the
136 			 * tcache GC event, in which case the stashed pointers
137 			 * may get flushed immediately, before returning from
138 			 * free().  Treat these cases as checked already.
139 			 */
140 			if (stashed_after <= stashed_before) {
141 				fake_abort_called = true;
142 			}
143 		}
144 		/* Flush tcache (including stashed). */
145 		assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
146 		    0, "Unexpected tcache flush failure");
147 	}
148 	expect_true(junked, "Aligned ptr not junked");
149 	if (write_after_free) {
150 		test_write_after_free_post();
151 	}
152 
153 	dallocx(items, flags);
154 	do_arena_destroy(arena_ind);
155 }
156 
157 TEST_BEGIN(test_read_after_free) {
158 	test_skip_if(!uaf_detection_enabled());
159 
160 	test_use_after_free(sizeof(void *), /* write_after_free */ false);
161 	test_use_after_free(sizeof(void *) + 1, /* write_after_free */ false);
162 	test_use_after_free(16, /* write_after_free */ false);
163 	test_use_after_free(20, /* write_after_free */ false);
164 	test_use_after_free(32, /* write_after_free */ false);
165 	test_use_after_free(33, /* write_after_free */ false);
166 	test_use_after_free(48, /* write_after_free */ false);
167 	test_use_after_free(64, /* write_after_free */ false);
168 	test_use_after_free(65, /* write_after_free */ false);
169 	test_use_after_free(129, /* write_after_free */ false);
170 	test_use_after_free(255, /* write_after_free */ false);
171 	test_use_after_free(256, /* write_after_free */ false);
172 }
173 TEST_END
174 
175 TEST_BEGIN(test_write_after_free) {
176 	test_skip_if(!uaf_detection_enabled());
177 
178 	test_use_after_free(sizeof(void *), /* write_after_free */ true);
179 	test_use_after_free(sizeof(void *) + 1, /* write_after_free */ true);
180 	test_use_after_free(16, /* write_after_free */ true);
181 	test_use_after_free(20, /* write_after_free */ true);
182 	test_use_after_free(32, /* write_after_free */ true);
183 	test_use_after_free(33, /* write_after_free */ true);
184 	test_use_after_free(48, /* write_after_free */ true);
185 	test_use_after_free(64, /* write_after_free */ true);
186 	test_use_after_free(65, /* write_after_free */ true);
187 	test_use_after_free(129, /* write_after_free */ true);
188 	test_use_after_free(255, /* write_after_free */ true);
189 	test_use_after_free(256, /* write_after_free */ true);
190 }
191 TEST_END
192 
193 static bool
194 check_allocated_intact(void **allocated, size_t n_alloc) {
195 	for (unsigned i = 0; i < n_alloc; i++) {
196 		void *ptr = *(void **)allocated[i];
197 		bool found = false;
198 		for (unsigned j = 0; j < n_alloc; j++) {
199 			if (ptr == allocated[j]) {
200 				found = true;
201 				break;
202 			}
203 		}
204 		if (!found) {
205 			return false;
206 		}
207 	}
208 
209 	return true;
210 }
211 
212 TEST_BEGIN(test_use_after_free_integration) {
213 	test_skip_if(!uaf_detection_enabled());
214 
215 	unsigned arena_ind = do_arena_create(-1, -1);
216 	int flags = MALLOCX_ARENA(arena_ind);
217 
218 	size_t n_alloc = san_uaf_align * 2;
219 	void **allocated = mallocx(n_alloc * sizeof(void *), flags);
220 	assert_ptr_not_null(allocated, "Unexpected mallocx failure");
221 
222 	for (unsigned i = 0; i < n_alloc; i++) {
223 		allocated[i] = mallocx(sizeof(void *) * 8, flags);
224 		assert_ptr_not_null(allocated[i], "Unexpected mallocx failure");
225 		if (i > 0) {
226 			/* Emulate a circular list. */
227 			*(void **)allocated[i] = allocated[i - 1];
228 		}
229 	}
230 	*(void **)allocated[0] = allocated[n_alloc - 1];
231 	expect_true(check_allocated_intact(allocated, n_alloc),
232 	    "Allocated data corrupted");
233 
234 	for (unsigned i = 0; i < n_alloc; i++) {
235 		free(allocated[i]);
236 	}
237 	/* Read-after-free */
238 	expect_false(check_allocated_intact(allocated, n_alloc),
239 	    "Junk-filling not detected");
240 
241 	test_write_after_free_pre();
242 	for (unsigned i = 0; i < n_alloc; i++) {
243 		allocated[i] = mallocx(sizeof(void *), flags);
244 		assert_ptr_not_null(allocated[i], "Unexpected mallocx failure");
245 		*(void **)allocated[i] = (void *)(uintptr_t)i;
246 	}
247 	/* Write-after-free */
248 	for (unsigned i = 0; i < n_alloc; i++) {
249 		free(allocated[i]);
250 		*(void **)allocated[i] = NULL;
251 	}
252 	test_write_after_free_post();
253 }
254 TEST_END
255 
256 int
257 main(void) {
258 	return test(
259 	    test_read_after_free,
260 	    test_write_after_free,
261 	    test_use_after_free_integration);
262 }
263