musl replaces its allocator to protect allocator metadata
musl 1.2.1 introduces mallocng. Its design separates allocator bookkeeping from application data and changes how allocation history affects memory use.
An allocator uses memory to describe the memory it manages. If an application can corrupt that bookkeeping through a buffer error, the allocator may act on the corrupted state during a later allocation or release. The musl 1.2.1 announcement introduces mallocng as a replacement for the project's original allocator, with hardening and memory-return behavior among the reasons for the change.
The design history matters because the replacement did not arise simply from a claim of faster allocation. Rich Felker's May review of its motivation argues that hardening most strongly required a new design. Other problems might have admitted improvements within the old architecture. Protecting bookkeeping efficiently needed a different relationship between allocation groups and their metadata.
C requires a programmer to remain within the limits of each object and its lifetime. I regard an allocator that protects its own state as useful infrastructure within that environment. The application still needs correct ownership, but one error should have fewer opportunities to corrupt the mechanism that supplies every other allocation.
Bookkeeping has a different owner
Application data and allocator metadata have different purposes. The application needs access to its requested objects. The allocator needs reliable information about which storage is available and how it is organized. Placing both kinds of information close together can make some operations convenient, while also exposing allocator state to application mistakes.
Felker's motivation review explains that arbitrary splitting and merging complicate efficient access to metadata stored separately from application allocations. mallocng groups slots so that bookkeeping can be shared. That gives the implementation a way to separate important metadata without imposing an independently large metadata cost on every small allocation.
The practical significance follows from who is allowed to change each object. A caller should be able to change its allocation's contents. It should not be able to redefine the allocator's account of which memory is free. Separating those roles gives the implementation a clearer consistency boundary.
This is a defense against consequences of invalid application behavior. It does not make a buffer overflow a valid operation, and it does not establish that the application will survive such an error. The allocator can instead reject inconsistent state before using it to carry out another memory-management operation.
That distinction is useful when interpreting an allocator failure. A trap can expose an earlier mistake in the caller. The immediate call into the allocator may merely be the point where the implementation has enough information to detect the inconsistency.
Detection depends on what is checked and when
The May hardening comparison describes the development design's separation of most security-sensitive metadata from application storage. It also discusses consistency checks and detection at allocator entry points. These are concrete mechanisms, with limits that matter more than the general label of hardening.
A check performed during release observes the state at release time. It need not observe the exact instruction that originally wrote beyond an object. The time between those events can include unrelated application work. Consequently, understanding the reported failure may require tracing the affected allocation's earlier lifetime.
The comparison also examines delayed exact-address reuse through slot selection and cycling the offset used within a slot. The relevant problem is that a stale pointer can remain numerically identical to the address of a new allocation. Extending the interval before exact reuse reduces one opportunity for that confusion.
A pointer's numerical value alone does not express ownership history. An application that retains an old pointer needs a valid lifetime argument before using it again. Reusing storage cannot retroactively supply that argument, even if a new object happens to occupy the same address.
These mechanisms therefore address different consequences of the same ownership mistake. Metadata protection restricts damage to allocator state. Delaying exact-address reuse changes when stale addresses can coincide with valid allocations. Neither mechanism supplies an application-wide account of which references remain live.
Allocation history can affect available memory
The old allocator's splitting and merging model also had fragmentation problems. Felker's review describes cases where the allocator could grow despite memory that should have been useful, including complications caused by visibility of changing internal state. mallocng's organization changes the set of histories that produce those outcomes.
The general fragmentation problem is that the total amount of unused memory does not describe its usefulness for a particular request. An application might release several objects whose storage is separated by objects that remain live. Another request can need a contiguous region that those separated pieces cannot provide together.
For an allocator, that produces a choice among reusing available storage, obtaining more storage, and arranging allocations so that future release creates useful regions. The implementation cannot know every future request. Its organization determines which patterns it handles predictably.
A grouped layout changes that organization by making allocation classes and groups central to the bookkeeping. The useful question is then how groups become available for reuse or release as their slots change state. The motivation review connects the new design's fragmentation behavior to this organization.
This has an operational implication for a process that temporarily creates a large structure. The number of bytes requested by currently live objects may fall after the structure is destroyed. Whether the process's retained memory falls in the same way depends on what the allocator can return and what remains mixed with live allocations.
Returning memory has its own cost
Felker's upstreaming discussion describes aggressive return of unused memory, with backoff when repeated release and reacquisition would perform badly. It also describes expected differences between high and low usage. Those are design tradeoffs, not evidence of a universal speed improvement.
Returning storage can reduce what an idle phase retains. A later phase may need to acquire storage again. If those phases alternate frequently, an allocator can spend work reversing its own recent decisions. Keeping some storage available can avoid that work, while retaining more than the application presently uses.
The appropriate balance depends on the allocation pattern. A short burst followed by a long idle period differs from a program that repeatedly needs the same capacity. The new design's policy attempts to respond to that distinction without requiring the application to replace the standard allocation interface.
That makes allocator behavior part of system behavior even when the source does not change. The same calls can produce a different retained-memory profile because the implementation manages released storage differently. A comparison that only counts allocation calls would miss the policy responsible for that result.
I am interested in that reduction in dependence on allocation history. It can make an application's memory behavior easier to explain after temporary work ends. The relevant explanation still needs to distinguish live objects from storage the allocator retains for possible reuse.
A shared interface permits a substantial replacement
The standard allocation interface does not require applications to know how the allocator groups storage. That separation makes this replacement possible without asking every caller to express the new bookkeeping model. The implementation can change its internal organization while callers continue to request and release objects through the established interface.
The separation also constrains application assumptions. A caller that relies on an incidental arrangement of adjacent allocations has coupled itself to details outside the allocation request. A new allocator can make such assumptions visible even when the public operation has not changed.
Likewise, using freed memory because its contents appear unchanged depends on behavior that the interface does not promise. An implementation that reuses or returns that storage differently can change the symptom. The ownership error exists before the symptom changes.
Hardening creates another reason for a visible difference. Earlier behavior might allow an inconsistent state to persist until a later failure. Detection can terminate the process closer to a memory-management operation that exposes the inconsistency. That is an improvement in containment, although the application still needs a source-level repair.
For a library author, the replacement also reinforces the need to document who releases an allocation. If a public function returns an owned object, callers need to know the corresponding release operation and when it is permitted. Allocator metadata cannot resolve a disagreement between two components that each believes it owns the same object. A clearer allocation interface reduces the opportunities for that disagreement before any hardening check becomes relevant.
The release announcement makes mallocng part of musl 1.2.1. Its rationale combines protection of allocator state with a different approach to fragmentation and returning memory. The practical change is an implementation whose grouping and checking rules make fewer application memory errors capable of redefining the allocator's own bookkeeping.