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