std: add AddressSanitizer poisoning to allocators and ArrayList#23
std: add AddressSanitizer poisoning to allocators and ArrayList#23Jarred-Sumner wants to merge 2 commits into
Conversation
Adds std.debug.Asan with inline wrappers around the ASAN runtime's manual-poison API (__asan_poison_memory_region / unpoison / __sanitizer_annotate_contiguous_container). All calls are no-ops when builtin.sanitize_address is false. Instrumented: - ArrayList / ArrayListUnmanaged: annotate [len..capacity] as poisoned via the contiguous-container API, mirroring libc++ vector. Catches out-of-bounds reads/writes into reserved capacity. - FixedBufferAllocator: poison freed regions and the previously-used range on reset(). Adds deinit() to unpoison the backing buffer. Does not eagerly poison on init() since Zig does not yet emit per-frame shadow cleanup, which would leave stale poison on stack buffers. - ArenaAllocator: poison the unused tail of each buffer node and freed/reset regions; unpoison before returning memory to the child allocator. - MemoryPool: poison destroyed items past the free-list link; unpoison on create(). Catches use-after-destroy. - DebugAllocator: poison freed bucket slots and the unallocated tail of fresh pages; unpoison before returning pages to the backing allocator. - StackFallbackAllocator: poison on free from the fixed buffer. Detection tests in std/debug/asan_test.zig verify each via Asan.isPoisoned(); they skip when ASAN is disabled.
WalkthroughAdds AddressSanitizer (ASAN) support across the standard library: a new Changes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/std/debug/Asan.zig`:
- Around line 52-66: annotateContiguousContainer trusts callers; add fail-fast
debug precondition checks to validate bounds before calling
__sanitizer_annotate_contiguous_container: assert that capacity != 0 (already
checked), that old_len <= capacity, new_len <= capacity, and that old_len <=
new_len (or vice‑versa if the function expects shrink/expand semantics), and
optionally assert storage is non-null when enabled; place these checks in
annotateContiguousContainer near the top so misuse is caught in debug builds
before invoking __sanitizer_annotate_contiguous_container.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6f0cc3da-d78e-4798-af82-52ba06b2d6c3
📒 Files selected for processing (1)
lib/std/debug/Asan.zig
| pub inline fn annotateContiguousContainer( | ||
| storage: [*]const u8, | ||
| capacity: usize, | ||
| old_len: usize, | ||
| new_len: usize, | ||
| ) void { | ||
| if (!enabled) return; | ||
| if (capacity == 0) return; | ||
| __sanitizer_annotate_contiguous_container( | ||
| storage, | ||
| storage + capacity, | ||
| storage + old_len, | ||
| storage + new_len, | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Add fail-fast precondition checks for container annotation bounds.
The docstring defines the contract, but Line 60 currently trusts callers completely. Adding debug assertions for old_len/new_len against capacity would catch misuse earlier.
Suggested diff
pub inline fn annotateContiguousContainer(
storage: [*]const u8,
capacity: usize,
old_len: usize,
new_len: usize,
) void {
if (!enabled) return;
if (capacity == 0) return;
+ `@import`("std").debug.assert(old_len <= capacity);
+ `@import`("std").debug.assert(new_len <= capacity);
__sanitizer_annotate_contiguous_container(
storage,
storage + capacity,
storage + old_len,
storage + new_len,
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub inline fn annotateContiguousContainer( | |
| storage: [*]const u8, | |
| capacity: usize, | |
| old_len: usize, | |
| new_len: usize, | |
| ) void { | |
| if (!enabled) return; | |
| if (capacity == 0) return; | |
| __sanitizer_annotate_contiguous_container( | |
| storage, | |
| storage + capacity, | |
| storage + old_len, | |
| storage + new_len, | |
| ); | |
| } | |
| pub inline fn annotateContiguousContainer( | |
| storage: [*]const u8, | |
| capacity: usize, | |
| old_len: usize, | |
| new_len: usize, | |
| ) void { | |
| if (!enabled) return; | |
| if (capacity == 0) return; | |
| `@import`("std").debug.assert(old_len <= capacity); | |
| `@import`("std").debug.assert(new_len <= capacity); | |
| __sanitizer_annotate_contiguous_container( | |
| storage, | |
| storage + capacity, | |
| storage + old_len, | |
| storage + new_len, | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/std/debug/Asan.zig` around lines 52 - 66, annotateContiguousContainer
trusts callers; add fail-fast debug precondition checks to validate bounds
before calling __sanitizer_annotate_contiguous_container: assert that capacity
!= 0 (already checked), that old_len <= capacity, new_len <= capacity, and that
old_len <= new_len (or vice‑versa if the function expects shrink/expand
semantics), and optionally assert storage is non-null when enabled; place these
checks in annotateContiguousContainer near the top so misuse is caught in debug
builds before invoking __sanitizer_annotate_contiguous_container.
Adds
std.debug.Asanand instruments stdlib allocators/containers so ASAN catches bug classes it currently misses entirely.What ASAN now catches that it didn't before
ArrayList/ArrayListUnmanaged[len..capacity](container overflow)FixedBufferAllocatorArenaAllocatorMemoryPoolDebugAllocatorStackFallbackAllocatorAll calls compile to no-ops when
builtin.sanitize_addressis false.Design notes
Asan.annotateContiguousContainer()wraps__sanitizer_annotate_contiguous_container(the libc++ vector mechanism) for ArrayList.init(): Zig doesn't yet emit per-frame ASAN shadow cleanup (no lifetime markers), so poisoning a stack buffer would leave stale poison after the FBA goes out of scope. Only freed regions are poisoned.deinit()is added for explicit cleanup.Tests
lib/std/debug/asan_test.zigverifies each viaAsan.isPoisoned(); skips when ASAN is disabled.asan:detection testsRun with ASAN:
zig test lib/std/std.zig --zig-lib-dir lib -fsanitize-address -lc <asan_rt.a> -lpthread -ldl -lm -lrt -lunwind --test-filter 'asan:'