Skip to content

fix(lociss): harden the v4 colblock reader against crafted input#71

Merged
balwierz merged 1 commit into
mainfrom
fix/lociss-v4-hardening
Jul 1, 2026
Merged

fix(lociss): harden the v4 colblock reader against crafted input#71
balwierz merged 1 commit into
mainfrom
fix/lociss-v4-hardening

Conversation

@balwierz

@balwierz balwierz commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

A fresh three-axis analysis of the repo (bugs / responsiveness / functionality) surfaced memory-safety bugs in the LociSSD v4 "colblock" reader shipped in #68 — reachable by simply opening a crafted .lociss file, and not covered by the earlier audit. This PR is Tier 0 of that roadmap: harden the reader.

The decoders trusted offsets read straight from the (untrusted) on-disk payload:

  • DICT (codec 3) — OOB read. dict[k].assign(blob + o0, o1 - o0) with no o0 ≤ o1 ≤ blob_len check; a non-monotone offset underflows the length to ~4 GiB. Also n_dict + 1 was computed in uint32_t (wrap at UINT32_MAX). Now validated + widened.
  • ARENA (codec 5) — OOB read. Same pattern; the blob length was never validated. Now bounds-checked. (FRONTCODE was already safe.)
  • Region query without Start/End — OOB. A -r query on a file whose stored columns omit Start/End made decode_col(b, -1, …) index col_offset_ with a wrapped negative column. build_region now errors cleanly; decode_col guards the column index.
  • zstd decompression bomb — OOM. zstd_inflate had no output ceiling; a tiny chunk could inflate to gigabytes. Now capped by a per-block bound.
  • Corrupt chunk exit code. read_chunk failures are now sticky (read_status_), so corrupt data makes the CLI exit non-zero instead of warning + exit 0.

Verification

  • New crafted-input fixtures — tiny.v4.{baddict,badarena,nocoord,zbomb}.lociss — each exits 1 cleanly, never crashes (≥128) or hangs; asserted via a new assert_exit_code helper. The nocoord file still reads fine without -r.
  • Valid v4 (tiny.v4.lociss, DICT+FRONTCODE) and v4.1 (tiny.v41.lociss) fixtures decode unchanged. Suite 344 passed, 0 failed.
  • The fix is a precondition check before the OOB assign, so the vulnerable path is unreachable for bad offsets. Local ASan is entangled with the statically-linked mimalloc (masks heap redzones); the ASan/UBSan CI job + libFuzzer over the parsers (already tracked in TODO.md → Quality/signal) is the systemic follow-up that catches this whole class automatically.

From the broader analysis: next tiers are a per-source decoded-block LRU (the v4 reader decodes Start twice and the region count-pass re-decodes blocks), and features led by reference-aware pileup --pileup -f ref.fa.

🤖 Generated with Claude Code

The v4 "colblock" decoders trusted offsets read straight from the (untrusted)
file, so opening a crafted .lociss could read out of bounds or OOM — memory-
safety bugs in the reader shipped in #68, not covered by the earlier audit.

- DICT (codec 3): validate each dictionary offset `o0 <= o1 <= blob_len` before
  `assign(blob + o0, o1 - o0)` — a non-monotone offset underflowed the length to
  ~4 GiB → OOB read. Also widen `n_dict + 1` to size_t before the multiply so
  `n_dict == UINT32_MAX` can't wrap the length check to 0.
- ARENA (codec 5): validate each offset against the actual blob length.
  (FRONTCODE was already safe — it need()-checks every step.)
- Region query on a file whose stored columns omit Start/End: build_region now
  errors cleanly instead of decode_col indexing col_offset_ with a wrapped
  negative column index; decode_col also guards `c` in range.
- zstd_inflate takes a max_out ceiling (derived from the block row count) so a
  tiny compressed chunk can't inflate to gigabytes (decompression bomb).
- read_chunk failures are now sticky (read_status_) so a corrupt chunk makes the
  CLI exit non-zero instead of warning and continuing with exit 0.

Crafted-input fixtures (tiny.v4.baddict/badarena/nocoord/zbomb.lociss) assert
each path exits 1 cleanly — never a crash or hang. Valid v4/v4.1 files and the
suite are unchanged. Suite 344. Systemic follow-up: an ASan/UBSan CI job +
libFuzzer over the parsers (already tracked) catches this whole class.

Co-Authored-By: Claude Opus 4.8 <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