Skip to content

fix(fpss): bound per-session delta-decode state against non-conformant peers#889

Merged
userFRM merged 1 commit into
mainfrom
fix/round12-core
Jun 17, 2026
Merged

fix(fpss): bound per-session delta-decode state against non-conformant peers#889
userFRM merged 1 commit into
mainfrom
fix/round12-core

Conversation

@userFRM

@userFRM userFRM commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

Two correctness-hardening fixes in the core crate from an adversarial decoder audit.

Bound per-session delta-decode state

The FIT delta-decode maps (prev, ohlcvc, field_counts) are keyed by raw wire contract id and only reset at START/STOP/RESTART boundaries and on reconnect. There is no per-session entry cap, so a non-conformant peer that streams ever-incrementing contract ids within a single no-STOP session would grow all three maps without limit. The connection is SPKI-pinned to the vendor host, so this is not reachable in practice, but an institutional-grade decoder should still bound it.

A generous per-session distinct-contract cap now guards the insert path. On overflow the maps are cleared once with a single warn and re-seeded from the current tick, which decodes as a fresh absolute baseline, so no value is corrupted. A conformant session resets at every market boundary and never approaches the cap, so it retains every live contract.

The recorded field width is left fixed at the first absolute row, now with a comment explaining why this is correct: the trade-format width is a server-wide session constant, not a per-row property, and later FIT delta rows carry only the changed-field count, so they cannot re-derive the width. The decode buffer is always full width with unused slots zero-filled, so a stale width can only under-report which trailing fields a caller reads, never read out of bounds.

Stop decoding flat-file INDEX entries with the option layout

The INDEX walker decoded SecType::Index with the option contract layout, which expects a right byte. The flat-file service publishes no INDEX dataset, so the request layer rejects every Index pair before any blob is fetched and the walker is never reached with an INDEX section. This was dead defensive code that would have misparsed a real index layout if it differed from options. No vendor-confirmed INDEX entry layout exists, so the walker now returns a typed unsupported error rather than borrowing the option layout.

Tests

  • delta_state_bounds_unbounded_distinct_contract_ids feeds well past the cap and asserts every map stays bounded.
  • delta_state_normal_session_retains_all_contracts asserts a normal session keeps every distinct contract.
  • index_sec_type_is_rejected_as_unsupported asserts the typed error on an INDEX section.

cargo test -p thetadatadx --lib fpss and --lib flatfiles both pass; cargo fmt --all -- --check clean; cargo clippy -p thetadatadx --lib clean.

🤖 Generated with Claude Code

…t peers

The delta-decode maps (prev, ohlcvc, field_counts) are keyed by raw wire contract id and only reset at START/STOP/RESTART boundaries and on reconnect. A non-conformant peer that streams ever-incrementing contract ids within a single no-STOP session would grow all three maps without limit. Add a generous per-session distinct-contract cap: on overflow the maps are cleared once with a single warn and re-seeded from the current tick, which decodes as a fresh absolute baseline, so no value is corrupted. A conformant session never approaches the cap and retains every live contract.

While here, document why the recorded field width is fixed at the first absolute row: the trade-format width is a server-wide session constant, not a per-row property, and later FIT delta rows carry only the changed-field count, so they cannot re-derive the width. The decode buffer is always full width with unused slots zero-filled, so a stale width can only under-report which trailing fields a caller reads, never read out of bounds.

Also stop decoding flat-file INDEX entries with the option contract layout. The flat-file service publishes no INDEX dataset, so the request layer rejects every Index pair before any blob is fetched and the walker is never reached with an INDEX section. No vendor-confirmed INDEX entry layout exists, so the walker now returns a typed unsupported error rather than borrowing the option layout and risking a misparse if a future format differs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@userFRM userFRM merged commit 2e61208 into main Jun 17, 2026
22 checks passed
@userFRM userFRM deleted the fix/round12-core branch June 17, 2026 21:51
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.

2 participants