[alloc] Fix checked-slab non-tail-shrink realloc corrupting recovery - #51
Merged
Conversation
An interrupted non-tail-shrink `realloc` in `CheckedSlabBStackAllocator` (Rust) / `checked_slab_bstack_allocator_realloc` (C) could make recovery corrupt an unrelated live allocation. The shrink committed the block's smaller count into its overhead word before scrubbing the excess blocks into the free list as a second call. A fault in between left the excess holding the allocation's stale payload while the header already claimed the smaller span. On the next `recover`, the linear arena scan reached those orphaned blocks; their stale bytes read as a valid multi-block in-use marker, so the scan strode past a neighbouring live allocation's real header and then reclaimed that allocation's interior as leaked blocks -- writing free-list links over live caller data. This violates the "a fault leaves at most a leak, never corruption" contract. Invert the order: scrub the excess to a clean, zero-overhead free run before committing the smaller count. Now every crash window is safe -- the intact original, zero-overhead leaked blocks `recover` reclaims one by one while staying aligned, or (in the brief window between scrub and commit) a region reported lost (`handle: None` / `-2`) rather than torn contents. The non-atomic tail shrink keeps its commit-then-discard fast path. On-disk format and the lock-free `atomic` model are unchanged; the allocator version is bumped 0.1.1 -> 0.1.2 (magic patch byte only, so existing 0.1.x files stay compatible). Surfaced by the allocator fault-injection fuzz. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
Author
|
Verify with these seeds (fail on Both are non- |
williamwutq
added a commit
that referenced
this pull request
Aug 23, 2026
Backport of #51 to the 0.2.x line. An interrupted non-tail-shrink `realloc` in `CheckedSlabBStackAllocator` (Rust) / `checked_slab_bstack_allocator_realloc` (C) could make recovery corrupt an unrelated live allocation: the shrink committed the block's smaller count before scrubbing the excess into the free list, so a fault in between left the excess holding stale payload while the header already claimed the smaller span. `recover`'s linear scan then read those orphaned bytes as a valid multi-block in-use marker, strode past a neighbouring live allocation's header, and reclaimed its interior as leaked blocks -- writing free-list links over live data. Invert the order: scrub the excess to a clean zero-overhead free run (`write_free_run`) before committing the smaller count. The non-atomic tail shrink keeps its commit-then-discard fast path (safe at the arena tail). Magic bumped 0.1.1 -> 0.1.2 (patch byte only; the 6-byte compat prefix is unchanged, so existing 0.1.x files still open). Unlike the 0.4.x original, the 0.2.x allocator API has no surviving-handle-on-failure mechanism, so the port keeps only the on-disk crash-ordering (no `recovered`/`-2` bookkeeping). Correctness strictly increases: the neighbour-corruption path is gone; the worst remaining outcome is a leak or an allocation with zeroed tail bytes. Tests (all green): Rust alloc,set / alloc,set,atomic; C test-checked-slab / test-checked-slab-atomic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description: Fix a latent crash-consistency bug in
CheckedSlabBStackAllocatorand its C implementation. A non-tail-shrinkrealloccommitted the block's smaller count into its overhead word before scrubbing the excess blocks into the free list, so a fault in between left the excess holding the allocation's stale payload while the header already claimed the smaller span. On the nextrecover, the linear arena scan read those orphaned bytes as a valid multi-block in-use marker, strode past a neighbouring live allocation's real header, and reclaimed its interior as leaked blocks — writing free-list links over live data (breaking the "a fault leaves at most a leak, never corruption" contract). The order is now inverted: the excess is scrubbed to a clean, zero-overhead free run before the count is committed, so every crash window leaves either the intact original, zero-overhead leaked blocksrecoverreclaims cleanly, or a region reported lost (handle: None/-2) — never corruption. The non-atomic tail shrink keeps its commit-then-discard fast path. Same hazard class as #42 (which fixed the siblingSegregatedBStackAllocator);Segregateditself is unaffected here (its non-atomic path already takes the move, its atomic path fuses the carve).Important Feature: Yes
Type: Allocator - Fix
Magic Number: ALCK (bumped 0.1.1 → 0.1.2, patch byte only; first 6 bytes checked on open, so 0.1.x files stay compatible)
Bulk: No
Tests: Existing allocator fault-injection fuzz (surfaced it and covers it); no new tests added
Feature Flags: alloc + set
Breaking change: No
New Types: None
Rust Only: No
Fuzz: Yes
Safety Review: Needed: Crash Safety, Invariants
🤖 Generated with Claude Code