Fix memory leak of old curr_reg on san_bump_grow_locked failure

When san_bump_grow_locked fails, it sets sba->curr_reg to NULL.
The old curr_reg (saved in to_destroy) was never freed or restored,
leaking the virtual memory extent. Restore sba->curr_reg from
to_destroy on failure so the old region remains usable.
This commit is contained in:
Slobodan Predolac 2026-03-27 10:07:05 -07:00
parent 2fceece256
commit 5904a42187
2 changed files with 89 additions and 1 deletions

View file

@ -4,6 +4,50 @@
#include "jemalloc/internal/arena_structs.h"
#include "jemalloc/internal/san_bump.h"
static extent_hooks_t *san_bump_default_hooks;
static extent_hooks_t san_bump_hooks;
static bool fail_retained_alloc;
static unsigned retained_alloc_fail_calls;
static void *
san_bump_fail_alloc_hook(extent_hooks_t *UNUSED extent_hooks, void *new_addr,
size_t size, size_t alignment, bool *zero, bool *commit,
unsigned arena_ind) {
if (fail_retained_alloc && new_addr == NULL
&& size >= SBA_RETAINED_ALLOC_SIZE) {
retained_alloc_fail_calls++;
return NULL;
}
return san_bump_default_hooks->alloc(san_bump_default_hooks, new_addr,
size, alignment, zero, commit, arena_ind);
}
static void
install_san_bump_fail_alloc_hooks(unsigned arena_ind) {
size_t hooks_mib[3];
size_t hooks_miblen = sizeof(hooks_mib) / sizeof(size_t);
size_t old_size = sizeof(extent_hooks_t *);
size_t new_size = sizeof(extent_hooks_t *);
extent_hooks_t *new_hooks;
extent_hooks_t *old_hooks;
expect_d_eq(
mallctlnametomib("arena.0.extent_hooks", hooks_mib, &hooks_miblen),
0, "Unexpected mallctlnametomib() failure");
hooks_mib[1] = (size_t)arena_ind;
expect_d_eq(mallctlbymib(hooks_mib, hooks_miblen, (void *)&old_hooks,
&old_size, NULL, 0),
0, "Unexpected extent_hooks error");
san_bump_default_hooks = old_hooks;
san_bump_hooks = *old_hooks;
san_bump_hooks.alloc = san_bump_fail_alloc_hook;
new_hooks = &san_bump_hooks;
expect_d_eq(mallctlbymib(hooks_mib, hooks_miblen, NULL, NULL,
(void *)&new_hooks, new_size),
0, "Unexpected extent_hooks install failure");
}
TEST_BEGIN(test_san_bump_alloc) {
test_skip_if(!maps_coalesce || !opt_retain);
@ -69,6 +113,48 @@ TEST_BEGIN(test_san_bump_alloc) {
}
TEST_END
TEST_BEGIN(test_failed_grow_preserves_curr_reg) {
test_skip_if(!maps_coalesce || !opt_retain);
tsdn_t *tsdn = tsdn_fetch();
san_bump_alloc_t sba;
san_bump_alloc_init(&sba);
unsigned arena_ind = do_arena_create(0, 0);
assert_u_ne(arena_ind, UINT_MAX, "Failed to create an arena");
install_san_bump_fail_alloc_hooks(arena_ind);
arena_t *arena = arena_get(tsdn, arena_ind, false);
pac_t *pac = &arena->pa_shard.pac;
size_t small_alloc_size = PAGE * 16;
edata_t *edata = san_bump_alloc(tsdn, &sba, pac, pac_ehooks_get(pac),
small_alloc_size, /* zero */ false);
expect_ptr_not_null(edata, "Initial san_bump allocation failed");
expect_ptr_not_null(sba.curr_reg,
"Expected retained region remainder after initial allocation");
fail_retained_alloc = true;
retained_alloc_fail_calls = 0;
edata_t *failed = san_bump_alloc(tsdn, &sba, pac, pac_ehooks_get(pac),
SBA_RETAINED_ALLOC_SIZE, /* zero */ false);
expect_ptr_null(failed, "Expected retained grow allocation failure");
expect_u_eq(retained_alloc_fail_calls, 1,
"Expected exactly one failed retained allocation attempt");
edata_t *reused = san_bump_alloc(tsdn, &sba, pac, pac_ehooks_get(pac),
small_alloc_size, /* zero */ false);
expect_ptr_not_null(
reused, "Expected allocator to reuse preexisting current region");
expect_u_eq(retained_alloc_fail_calls, 1,
"Reuse path should not attempt another retained grow allocation");
fail_retained_alloc = false;
}
TEST_END
TEST_BEGIN(test_large_alloc_size) {
test_skip_if(!maps_coalesce || !opt_retain);
@ -105,5 +191,6 @@ TEST_END
int
main(void) {
return test(test_san_bump_alloc, test_large_alloc_size);
return test(test_san_bump_alloc, test_failed_grow_preserves_curr_reg,
test_large_alloc_size);
}