Skip to content

Commit deef743

Browse files
leifericfclaude
andcommitted
gc: disable ASan fake-stack so conservative scanner works on gcc-built binaries
Define __asan_default_options() in main.c returning "detect_stack_use_after_return=0". libasan's fake-stack feature relocated each call's address-taken locals to its own region, so the &probe captured in gc_note_host_frame at state init and the &probe read inside gc_scan_stack lived in different regions with unmapped pages between -- the conservative scanner walked between them and SEGVed. detect_leaks stays on; heap-corruption / red-zone coverage unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3490b49 commit deef743

3 files changed

Lines changed: 59 additions & 1 deletion

File tree

CHANGELOG.md

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

3+
## v0.255.17 — Fix: Disable ASan fake-stack for conservative scanner
4+
5+
After v0.255.16 let libasan print its full report on ubuntu-24.04
6+
x86_64, the report named the real bug: a SEGV inside `gc_scan_stack`
7+
at `src/gc/roots.c` reading from an unmapped page near `0x7f7a65220000`.
8+
Root cause is ASan's fake-stack (use-after-return detection) feature,
9+
which is default-on in gcc-built ASan binaries.
10+
11+
When fake-stack is active, each function's address-taken locals are
12+
relocated to a separately allocated region. `&probe` recorded at state
13+
init (`gc_note_host_frame`) and `&probe` taken inside `gc_scan_stack`
14+
end up in **different** fake-stack regions, with unrelated heap pages
15+
between them. The conservative scanner walks word-by-word from one
16+
`&probe` toward the other, hits a guard page, and SEGVs.
17+
18+
The `no_sanitize_address` attribute added in v0.255.14 only suppresses
19+
red-zone checks; it does not change where local addresses live. The
20+
correct fix is to disable fake-stack at the ASan runtime layer.
21+
22+
Define `__asan_default_options()` in `main.c` (a weak hook libasan
23+
calls before `main`) that returns `"detect_stack_use_after_return=0"`
24+
when the binary is built with ASan. Use-after-return is a
25+
nice-to-have; the heap-corruption / red-zone class of bugs the
26+
release-gate ASan run actually targets is unaffected. `detect_leaks`
27+
remains on, so LSan continues to catch what ubuntu-24.04-arm was
28+
flagging in v0.255.15.
29+
30+
macos-14 / windows-2022 / ubuntu-24.04-arm stayed green in v0.255.16;
31+
only ubuntu-24.04 x86_64 carried this layer.
32+
333
## v0.255.16 — Fix: Defer to libsanitizer + filter JIT note from parity diff
434

535
Two remaining CI matrix failures after v0.255.15:

main.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,34 @@ static void crash_handler(int sig)
754754
raise(sig);
755755
}
756756

757+
/* Weak hook called by libasan before main() runs. Disables fake-stack
758+
* (use-after-return detection) so conservative stack scanning works.
759+
* ASan's fake-stack feature relocates each function's address-taken
760+
* locals into a separately allocated region; the address returned by
761+
* &local for one call lives in a different region than another call's
762+
* &local. gc_scan_stack uses &probe at scan time and gc_note_host_frame
763+
* recorded another &probe at state init -- under fake-stack those two
764+
* pointers can sit in different fake-stack regions with unmapped memory
765+
* between them, so walking word-by-word from one to the other SEGVs.
766+
* The other use-after-return-style coverage we'd lose is non-essential
767+
* for catching the heap-corruption / red-zone class of bugs that
768+
* release-gate's ASan run is actually meant to find. The detect_leaks
769+
* setting stays on. */
770+
#if defined(__has_feature)
771+
# if __has_feature(address_sanitizer)
772+
# define MINO_BUILT_WITH_ASAN 1
773+
# endif
774+
#elif defined(__SANITIZE_ADDRESS__)
775+
# define MINO_BUILT_WITH_ASAN 1
776+
#endif
777+
#ifdef MINO_BUILT_WITH_ASAN
778+
const char *__asan_default_options(void);
779+
const char *__asan_default_options(void)
780+
{
781+
return "detect_stack_use_after_return=0";
782+
}
783+
#endif
784+
757785
static void install_crash_handler(mino_state_t *S)
758786
{
759787
const char *disabled = getenv("MINO_NO_CRASH_HANDLER");

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 16
31+
#define MINO_VERSION_PATCH 17
3232

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

0 commit comments

Comments
 (0)