Skip to content

Fix SIGTRAP: bounds-check chart.serum()/antigen() + skip stale serum-coverage maps - #36

Open
drserajames wants to merge 2 commits into
mainfrom
fix-serum-coverage-sigtrap
Open

Fix SIGTRAP: bounds-check chart.serum()/antigen() + skip stale serum-coverage maps#36
drserajames wants to merge 2 commits into
mainfrom
fix-serum-coverage-sigtrap

Conversation

@drserajames

@drserajames drserajames commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Fix SIGTRAP: bounds-check chart.serum()/antigen() + skip stale serum-coverage maps

A full report run crashed with SIGTRAP (exit 133) during one lab's serum_coverage_export. Root cause was a latent out-of-bounds access, not the renderer:

  • serum-coverage/ dirs accumulate map PDFs across runs and are never cleaned. h3-hi-guinea-pig-cdc held sc-000…045 from an earlier 46-serum chart, but the current chart has 9 sera.
  • serum_coverage_webpage globs every *.pdf and serum_title calls chart.serum(int(stem.split("-")[1])) with no bounds check → chart.serum(45) on a 9-serum chart.
  • chart.serum()/chart.antigen() did an unchecked sera()[serum_index{n}]. Under build-py314's -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST, the OOB access calls __builtin_trap() → SIGTRAP. On the non-hardened build-arm64 it silently reads OOB memory — a real bug either way.

Fix (two complementary parts)

  1. cc/py/chart-v3.cc — bounds-check chart.serum() / chart.antigen() and raise a catchable pybind11::index_error (Python IndexError) on out-of-range, matching the existing SeqdbSelected accessor pattern. Closes the UB for every caller.
  2. py/ae/report/commander.pyserum_coverage_webpage skips maps whose serum index isn't present in the current chart (graceful degrade — stale/foreign files name sera that don't exist here), so the page reflects only this chart's sera and never indexes out of range.

Proof

  • h3-hi-guinea-pig-cdc serum_coverage_export: exit 0 (was 133), page lists exactly its 9 sera, skips the stale maps.
  • Regression: h3-hi-guinea-pig-vidrl (26 sera, no stale files) — exit 0, 26 pages, nothing skipped (unchanged for a normal lab).
  • chart.serum(45)IndexError: serum index 45 out of range (chart has 9 sera).

Other labs' serum-coverage/ dirs carry the same stale accumulation, so they had the same latent crash — this covers them all. WHO-data gate clean; 2 code files, no data.

Update: clean the serum-coverage/ dir (not just skip)

serum_coverage_export is now authoritative for its output dir: before rendering it removes any existing sc-*.pdf not in the current run's job set, plus the regenerated gridage-*.json/index-*.html/coverage.html. This physically clears the stale accumulation (h1-cdc's 274, h3-hi-guinea-pig-cdc's 74) rather than only skipping it at webpage time — a shrunk chart or a serum_selector (e.g. h1-cdc's single-serum export) no longer leaves orphaned maps. Unrelated files are untouched. The webpage skip + the C++ bounds-check remain as defence-in-depth. Verified: a simulated 46→9-serum dir collapses to exactly the 18 current maps, unrelated files preserved.

drserajames and others added 2 commits July 18, 2026 21:27
The report's serum_coverage_export crashed with SIGTRAP (exit 133) on
h3-hi-guinea-pig-cdc. The crash was NOT in the native map renderer
(all sc-* maps render cleanly); it was in serum_coverage_webpage.

serum_coverage/ accumulates map PDFs across runs and is never cleaned.
This lab's dir held maps sc-000..sc-045 from an earlier run of a larger
chart, but the current chart has only 9 sera. serum_coverage_webpage
globs every *.pdf and serum_title() does chart.serum(int(stem)) with no
bounds check, so it called chart.serum(45) on a 9-serum chart. The
pybind chart.serum()/chart.antigen() accessors did an unchecked
sera()[serum_index{n}], which under libc++ FAST hardening (build-py314)
calls __builtin_trap() -> SIGTRAP (and silently reads OOB memory on the
non-hardened build-arm64). This also made the crash look non-renderer
and heap-nondeterministic: rendering completes, the trap fires only in
the post-render webpage step.

Two fixes:
- cc/py/chart-v3.cc: bounds-check chart.serum()/chart.antigen(); raise a
  catchable pybind11::index_error (Python IndexError) on out-of-range
  instead of trapping / reading OOB. Matches the existing SeqdbSelected
  accessor pattern.
- py/ae/report/commander.py: serum_coverage_webpage skips maps whose
  serum index is not present in the current chart (stale/foreign files),
  so the page reflects only this chart's sera and never indexes out of
  range. Labs whose output matches the chart are unaffected.

Verified: h3-hi-guinea-pig-cdc serum_coverage_export now exits 0 (was
133), page lists exactly its 9 sera, skips the 74 stale maps; a normal
lab (vidrl, 26 sera) is unchanged (26 pages, nothing skipped);
chart.serum(45) now raises IndexError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
serum-coverage/ PDFs accumulated across runs — a shrunk chart or a
serum_selector (e.g. h1-cdc's single-serum export) left maps for sera the run
won't regenerate (h1-cdc had 274 stale; h3-hi-guinea-pig-cdc 74). Beyond
clutter, those stale files name sera outside the current chart, which is what
drove chart.serum() out of range.

Make serum_coverage_export authoritative for the dir: before rendering, remove
any existing sc-*.pdf not in this run's job set, plus the regenerated
gridage/index/coverage web artifacts. Unrelated files are left untouched. The
webpage stale-skip + the chart.serum()/antigen() bounds-check remain as
defence-in-depth.

Co-Authored-By: Claude Opus 4.8 (1M context) <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