uaf.c 7.6 KB

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