Skip to content

[alloc] Fix checked-slab non-tail-shrink realloc corrupting recovery - #51

Merged
williamwutq merged 1 commit into
masterfrom
allocator
Aug 19, 2026
Merged

[alloc] Fix checked-slab non-tail-shrink realloc corrupting recovery#51
williamwutq merged 1 commit into
masterfrom
allocator

Conversation

@williamwutq

Copy link
Copy Markdown
Owner

Description: Fix a latent crash-consistency bug in CheckedSlabBStackAllocator and its C implementation. A non-tail-shrink realloc committed 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 next recover, 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 blocks recover reclaims 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 sibling SegregatedBStackAllocator); Segregated itself 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

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>
@williamwutq

Copy link
Copy Markdown
Owner Author

Verify with these seeds (fail on master, pass here):

BSTACK_FUZZ_SEED=11391386408313407811 cargo test --features alloc,set,fault-injection,debug-no-sync check_slab_16::fault_fuzz
BSTACK_FUZZ_SEED=2157494443327340441  cargo test --features alloc,set,fault-injection,debug-no-sync check_slab_64::fault_fuzz

Both are non-atomic reproductions (block sizes 16 and 64); the fix covers the atomic path too. debug-no-sync just speeds the run up — it reproduces without it. Original CI hits were check_slab_16 (salt 0x5555) and check_slab_64 (salt 0x6666).

@williamwutq williamwutq self-assigned this Aug 19, 2026

@williamwutq williamwutq left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@williamwutq
williamwutq merged commit 0e9f818 into master Aug 19, 2026
19 checks passed
@williamwutq
williamwutq deleted the allocator branch August 19, 2026 08:00
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant