fix(linux): survive the decomp's latent stack-buffer overruns (D189) (#24)

* fix(linux): survive the decomp's latent stack-buffer overruns (D189)

The decompiled game code has a class of fixed-size stack-buffer over/under-
runs that write a few words into adjacent stack scratch:

  * stan.c  sub_GAME_7F0B1DDC  — `StandTile *tileStack[39]` with a bail check
    of `if (cat >= 41)` that only runs once per outer iteration, so `cat`
    reaches 40 + pointCount and the append writes several slots past the end.
    BUNKER1 (-level_09) prop room-list setup legitimately visits ~40 tiles.
  * bondview2.c  bondviewCalcIntroSwirlCamera  — `f32 pointbuf[10]` indexed
    `&pointbuf[i*3]` for i in [-1..2] then written [0..5], i.e. [-3 .. 11].

On the N64 and on the MinGW/Windows build (neither uses a stack protector)
these are harmless in practice — the Windows build is stable over thousands
of frames. Only Ubuntu's gcc default (`-fstack-protector-strong`) turns them
into a fatal `__stack_chk_fail`, at BUNKER1 stage load and again at the intro
swirl camera (~frame 5).

Two-part fix:
  * `-fno-stack-protector` globally (CMakeLists) — matches the N64 and MinGW
    builds so the same code runs the same way. A hardened Linux build would
    need the whole overrun class swept first.
  * `StandTile *tileStack[64]` under `#ifdef PORT` (stan.c) — the one case
    that is cleanly understood and bounded (`pointCount` is 4-bit, so 40+15
    is the true worst case). Pure local scratch, never escapes, no behaviour
    change. N64 keeps `[39]` under `#else`.
  * stan.c:2225 link-resolve also switched to the D177 PORT_PTRADD macro for
    idiom consistency (the blob maps below 4 GiB so the bare cast is
    currently lossless; #else is byte-identical).

Stacked on the D188 xprintf fix. Verified: Windows -level_09 framediff 3/3
within golden threshold (all changes are #ifdef PORT or no-op on MinGW);
Linux -O2 -level_09 now runs to frame 2100+ with no crash.

* docs: D189 update (stack-protector / latent overrun class) + porting-notes D8

* docs: D190 — Linux -level_45 objective.c load crash (open); D189 sweep 5/6
This commit is contained in:
James Dansereau authored and GitHub committed 2026-09-03 20:18:25 -04:00
1 parent 1261389a5c
commit 86440e3b58
4 files changed
+64 -2

No files matched your search

+12
View File
@@ -220,6 +220,18 @@ add_compile_options(
-fwrapv
-include "versioninfo.h"
)
# The decompiled game code has a class of latent fixed-size stack-buffer
# over/under-runs (e.g. stan.c sub_GAME_7F0B1DDC's tileStack, bondview2.c
# bondviewCalcIntroSwirlCamera's pointbuf[10] indexed [-3..11]) that write a
# few words into adjacent stack scratch. On the N64, and on the MinGW/Windows
# build (no default stack protector), this is harmless in practice — verified
# stable over thousands of frames. Only Ubuntu's gcc default
# (-fstack-protector-strong) turns these into a fatal __stack_chk_fail at
# stage load / first frames. Match the N64 and MinGW builds so the same code
# runs the same way; the genuinely understood cases are still fixed properly
# under #ifdef PORT (D189). A hardened Linux build would need the whole class
# swept first — tracked in docs/dev/findings.md.
add_compile_options(-fno-stack-protector)
# Keep frame pointers: dbghelp's StackWalk64 (crash handler + thread dumps)
# cannot read DWARF unwind info on MinGW, so the EBP chain is the only way to
# get a real backtrace of a hung/crashed game thread.