https://bugs.kde.org/show_bug.cgi?id=523335
Bug ID: 523335
Summary: Duplicate allocation records for calloc on musl/Alpine
due to interposed internal malloc call
Classification: Applications
Product: Heaptrack
Version First 1.5.0
Reported In:
Platform: Other
OS: Other
Status: REPORTED
Severity: normal
Priority: NOR
Component: general
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: ---
DESCRIPTION
When Heaptrack is used through `LD_PRELOAD` on Alpine Linux with musl libc, a
single call to `calloc()` is recorded as two allocations for the same pointer.
This happens because musl's `calloc()` implementation calls the public
`malloc()` symbol internally after calculating the total allocation size. Since
Heaptrack is preloaded, this internal `malloc()` call is intercepted by
Heaptrack's `malloc` wrapper and recorded as an allocation.
After musl's `calloc()` returns, Heaptrack's outer `calloc` wrapper records the
same pointer and size again.
The sequence is:
1. The application calls `calloc(m, n)`.
2. Heaptrack's `calloc` wrapper calls the real musl `calloc` using `RTLD_NEXT`.
3. musl's `calloc` calls the public `malloc(total_size)` symbol.
4. That call resolves to Heaptrack's preloaded `malloc` wrapper.
5. Heaptrack records the allocation returned by the real musl `malloc`.
6. Control returns to musl's `calloc`, which returns the same pointer.
7. Heaptrack's outer `calloc` wrapper records the same pointer again.
8. The application later calls `free()` once, producing only one free record.
The resulting raw Heaptrack data is conceptually:
```text
+ size trace(heaptrack malloc -> musl calloc -> caller) P
+ size trace(heaptrack calloc -> caller) P
- P
```
Both allocation records refer to the same pointer and requested size, but may
have different stack traces.
This appears to be specific to libc implementations where `calloc()` calls an
interposable public `malloc()` symbol internally, such as musl.
STEPS TO REPRODUCE
1. Build the following test program on Alpine Linux:
```c
#include <stdlib.h>
int main(void)
{
void *ptr = calloc(4, 16);
free(ptr);
return 0;
}
```
2. Run the program under Heaptrack using its preload-based tracking mode:
```bash
heaptrack ./calloc-test
```
3. Inspect the raw Heaptrack allocation data or add temporary logging around
Heaptrack's `malloc`, `calloc`, and `free` recording functions.
4. Observe that the pointer returned by `calloc()` is recorded twice as an
allocation but is freed only once.
OBSERVED RESULT
Heaptrack records two allocation events for one `calloc()` call:
* One allocation is recorded by Heaptrack's `malloc` wrapper when musl's
`calloc()` internally calls `malloc()`.
* A second allocation is recorded by Heaptrack's outer `calloc` wrapper after
musl's `calloc()` returns.
Both records contain the same pointer and allocation size.
Only one corresponding free event is recorded when the application calls
`free()`.
This can cause incorrect allocation counts and may affect leak detection,
stack-trace attribution, peak-memory calculations, and other analysis based on
the allocation diary.
EXPECTED RESULT
A single application call to `calloc()` should produce exactly one allocation
record in Heaptrack.
Heaptrack should avoid recording the internal `malloc()` call made by musl's
`calloc()`, or otherwise detect that the outer `calloc()` record refers to an
allocation that was already recorded.
SOFTWARE/OS VERSIONS
Operating System: Alpine Linux [VERSION]
musl libc version: [VERSION]
Heaptrack version or Git commit: [VERSION OR COMMIT]
KDE Plasma Version: Not applicable
KDE Frameworks Version: [VERSION, IF APPLICABLE]
Qt Version: [VERSION, IF APPLICABLE]
Compiler: [COMPILER AND VERSION]
Architecture: [FOR EXAMPLE x86_64 OR aarch64]
ADDITIONAL INFORMATION
Relevant Heaptrack wrapper behavior:
* Heaptrack's `calloc` wrapper calls the real `calloc` through `RTLD_NEXT` and
then calls `heaptrack_malloc(ret, num * size)`.
* Heaptrack's `malloc` wrapper calls the real `malloc` and independently calls
`heaptrack_malloc()` for the returned pointer.
Relevant musl behavior:
musl's `calloc()` implementation calculates the total size and then invokes the
public `malloc()` symbol. Because Heaptrack is loaded with `LD_PRELOAD`, that
internal call is interposed by Heaptrack.
musl source:
```text
https://git.musl-libc.org/cgit/musl/tree/src/malloc/calloc.c
```
The duplicate records appear in this order:
```text
inner malloc allocation
outer calloc allocation
single free
```
The inner allocation's stack includes Heaptrack's `malloc` wrapper and musl's
`calloc`, while the outer allocation's stack includes Heaptrack's `calloc`
wrapper and the application caller.
The problem does not appear to be an application double allocation or double
free. It is caused by nested allocator interposition within musl's `calloc()`
implementation.
--
You are receiving this mail because:
You are watching all bug changes.