leak-detective: Add workaround for unknown memory reports with glibc

With glibc, there is an issue if TZ is not set, which causes a change
of the internally cached TZ value.  Because the original value was
cached before LD was active via `init_static_allocations()`, the memory
is freed as unknown memory later.  This change allows whitelisting
a function that might free such memory (tzset() only for now).
This commit is contained in:
Tobias Brunner
2026-06-05 16:40:49 +02:00
parent 4df53109a5
commit 3216646bdb
+36 -8
View File
@@ -672,6 +672,28 @@ static char *whitelist[] = {
"__cxa_thread_atexit",
};
/**
* Functions that may free memory allocated before hooks were enabled
* (e.g. during init_static_allocations()). If such a function appears
* in the backtrace of a free/realloc of unknown memory, we silently pass
* it through instead of printing a warning.
*/
static char *unknown_memory_whitelist[] = {
"tzset",
};
/**
* Check if the current call stack contains a function that is known to free
* pre-existing (i.e. unknown) memory. The backtrace is returned and used to
* report the function if that's not the case.
*/
static bool allow_unknown_memory(backtrace_t **bt)
{
*bt = backtrace_create(3);
return (*bt)->contains_function(*bt, unknown_memory_whitelist,
countof(unknown_memory_whitelist));
}
/**
* Some functions are hard to whitelist, as they don't use a symbol directly.
* Use some static initialization to suppress them on leak reports
@@ -942,7 +964,6 @@ HOOK(void, free, void *ptr)
{
memory_header_t *hdr;
memory_tail_t *tail;
backtrace_t *backtrace;
bool before;
if (!enabled || thread_disabled->get(thread_disabled))
@@ -974,6 +995,7 @@ HOOK(void, free, void *ptr)
if (hdr->magic != MEMORY_HEADER_MAGIC ||
tail->magic != MEMORY_TAIL_MAGIC)
{
backtrace_t *backtrace = NULL;
bool bt = TRUE;
/* check if memory appears to be allocated by our hooks */
@@ -1000,7 +1022,7 @@ HOOK(void, free, void *ptr)
/* just free this block of unknown memory */
hdr = ptr;
if (ignore_unknown)
if (ignore_unknown || allow_unknown_memory(&backtrace))
{
bt = FALSE;
}
@@ -1011,10 +1033,13 @@ HOOK(void, free, void *ptr)
}
if (bt)
{
backtrace = backtrace_create(2);
if (!backtrace)
{
backtrace = backtrace_create(2);
}
backtrace->log(backtrace, stderr, TRUE);
backtrace->destroy(backtrace);
}
DESTROY_IF(backtrace);
}
else
{
@@ -1037,7 +1062,6 @@ HOOK(void*, realloc, void *old, size_t bytes)
{
memory_header_t *hdr;
memory_tail_t *tail;
backtrace_t *backtrace;
bool before, have_backtrace = TRUE;
if (!enabled || thread_disabled->get(thread_disabled))
@@ -1063,6 +1087,7 @@ HOOK(void*, realloc, void *old, size_t bytes)
if (hdr->magic != MEMORY_HEADER_MAGIC ||
tail->magic != MEMORY_TAIL_MAGIC)
{
backtrace_t *backtrace = NULL;
bool bt = TRUE;
/* check if memory appears to be allocated by our hooks */
@@ -1093,7 +1118,7 @@ HOOK(void*, realloc, void *old, size_t bytes)
hdr = old;
have_backtrace = FALSE;
if (ignore_unknown)
if (ignore_unknown || allow_unknown_memory(&backtrace))
{
bt = FALSE;
}
@@ -1105,10 +1130,13 @@ HOOK(void*, realloc, void *old, size_t bytes)
}
if (bt)
{
backtrace = backtrace_create(2);
if (!backtrace)
{
backtrace = backtrace_create(2);
}
backtrace->log(backtrace, stderr, TRUE);
backtrace->destroy(backtrace);
}
DESTROY_IF(backtrace);
}
else
{