Skip to content

Commit ac2f719

Browse files
committed
gc: dual-path no_sanitize_address for gcc-built ASan scanner
gc_scan_stack walks every aligned word between gc_stack_bottom and the collector's frame as a conservative pointer scan. It crosses every prior frame and reads ASan's poisoned red zones; the function is decorated with __attribute__((no_sanitize_address)) to suppress the false positive. The decoration was gated on __has_feature(address_sanitizer), which is a clang-only builtin. gcc exposes ASan via __SANITIZE_ADDRESS__. On gcc-built ASan binaries (ubuntu-24.04 release-gate path) the attribute was silently dropped and libsanitizer flagged every cross-frame word read as stack-buffer-overflow. The same dual-detection pattern is already in gc/internal.h for MINO_GC_PIN_LOUD_ASSERT; adopt it here. Apple clang's ASan was permissive enough to let the scan slide without the attribute, which is why the bug stayed latent through every macos-only ASan run in the cycle.
1 parent e75dcc1 commit ac2f719

3 files changed

Lines changed: 63 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,53 @@
11
# Changelog
22

3+
## v0.255.14 — Fix: gc_scan_stack no_sanitize attribute on gcc-built ASan
4+
5+
With the v0.255.13 stencil-check drop letting CI's `Release gate`
6+
reach the ASan suite step on Linux runners for the first time, a
7+
latent gcc-only bug surfaced:
8+
9+
```
10+
==2674==ERROR: AddressSanitizer: stack-buffer-overflow
11+
READ of size 8 at 0x7f44bb31daa0 thread T0
12+
#0 ... in memcpy /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29
13+
#1 ... in gc_scan_stack src/gc/roots.c:560
14+
#2 ... in gc_minor_collect src/gc/minor.c:460
15+
```
16+
17+
`gc_scan_stack` is a conservative stack scanner -- it walks every
18+
aligned machine word between `gc_stack_bottom` and the collector's
19+
own frame, treating each as a candidate pointer. By design it
20+
crosses every prior frame and reads ASan's poisoned red zones; the
21+
function is decorated with `__attribute__((no_sanitize_address))`
22+
to suppress the false positive.
23+
24+
The decoration was gated on `__has_feature(address_sanitizer)`,
25+
which is a clang-only builtin. gcc exposes ASan via the
26+
`__SANITIZE_ADDRESS__` predefined macro instead. On gcc-built ASan
27+
binaries the attribute was silently dropped and libsanitizer
28+
flagged every cross-frame read in the scan loop.
29+
30+
The same dual-detection pattern was already in
31+
`src/gc/internal.h` for the `MINO_GC_PIN_LOUD_ASSERT` macro --
32+
adopted here for the scanner attribute. macos clang's ASan was
33+
permissive enough to let the scan slide without the attribute,
34+
which is why the bug went undetected for the cycle.
35+
36+
### Changes
37+
38+
- `src/gc/roots.c`: dual-path `no_sanitize_address` attribute for
39+
`gc_scan_stack` -- clang (`__has_feature`) plus gcc
40+
(`__SANITIZE_ADDRESS__`).
41+
- `src/mino.h`: `MINO_VERSION_PATCH` bumped to 14.
42+
43+
### Verification
44+
45+
- Local `release-gate` 17/17 green (ASan step inclusive).
46+
- Local `tests/run.clj` 1274 tests / 4557 assertions green.
47+
- CI matrix expected to land all four entries green once this
48+
pushes -- macos-14 / windows-2022 were already green on
49+
v0.255.13; ubuntu-24.04 and ubuntu-24.04-arm should now match.
50+
351
## v0.255.13 — Fix: Drop byte-identity stencil checks from CI
452

553
With the v0.255.12 fanout fix landing, CI finally reached the

src/gc/roots.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -530,11 +530,22 @@ void gc_mark_roots(mino_state_t *S)
530530
* ASan inserts red zones between locals; a conservative scan that
531531
* walks through them looks like stack-buffer-overflow to the
532532
* sanitizer. The scan is the entire point, so suppress the check.
533+
*
534+
* Clang exposes ASan via `__has_feature(address_sanitizer)`; gcc
535+
* uses the `__SANITIZE_ADDRESS__` predefined macro. The `__has_feature`
536+
* check is nested inside its own `defined` test because gcc evaluates
537+
* the second half of an `&&` syntactically even when the first half is
538+
* false. Without the gcc branch the attribute is silently dropped and
539+
* libsanitizer flags every cross-frame word read in the scan loop --
540+
* which surfaced as a CI failure on ubuntu-24.04 when release-gate's
541+
* ASan suite ran on a non-clang host for the first time.
533542
*/
534543
#if defined(__has_feature)
535-
# if __has_feature(address_sanitizer)
536-
__attribute__((no_sanitize("address")))
537-
# endif
544+
# if __has_feature(address_sanitizer)
545+
__attribute__((no_sanitize_address))
546+
# endif
547+
#elif defined(__SANITIZE_ADDRESS__)
548+
__attribute__((no_sanitize_address))
538549
#endif
539550
void gc_scan_stack(mino_state_t *S)
540551
{

src/mino.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
*/
2929
#define MINO_VERSION_MAJOR 0
3030
#define MINO_VERSION_MINOR 255
31-
#define MINO_VERSION_PATCH 13
31+
#define MINO_VERSION_PATCH 14
3232

3333
/*
3434
* Human-readable version string of the *linked* runtime, e.g. "0.48.0".

0 commit comments

Comments
 (0)