Skip to content

contracts: parse a schedule reply without slicing off a character boundary (ibx#258) - #364

Open
userFRM wants to merge 1 commit into
deepentropy:mainfrom
userFRM:fix/schedule-slice-panic
Open

contracts: parse a schedule reply without slicing off a character boundary (ibx#258)#364
userFRM wants to merge 1 commit into
deepentropy:mainfrom
userFRM:fix/schedule-slice-panic

Conversation

@userFRM

@userFRM userFRM commented Jul 30, 2026

Copy link
Copy Markdown

Summary

trim_session_endpoint validated byte positions and then sliced the string. The guard establishes what sits at bytes 8 and 11 and says nothing about 12 to 14 — and slicing a &str off a character boundary is a panic, not an error. Two nearby date slices had the same shape, guarded on length alone.

Reaching it takes one invalid byte in a trading-hours field. Frame bodies are decoded with from_utf8_lossy, so that byte becomes a three-byte replacement character and every position after it shifts: a cut that was ASCII in the intended payload now lands mid-character.

This runs on the hot loop, so it is an engine-down on malformed input rather than a dropped message.

The fix

The format is YYYYMMDD-HH:MM:SS, ASCII by definition, and requiring that outright is what makes the byte arithmetic sound rather than incidentally correct. The two date slices ask for the range instead of assuming it, so a short or non-ASCII field yields a degraded date rather than a panic.

The is_ascii guard refuses nothing legitimate: the two fields it covers are populated only from the start- and end-time tags, which are timestamps. The timezone name — the one plausible non-ASCII carrier — is a separate field that never reaches this function. The guard only rejects strings that would previously have panicked, or that already returned unchanged.

Degrading costs a wrong date field on one contract; a reply this malformed has no recoverable date in it. The output is carried verbatim to the caller as trading_hours/liquid_hours and nothing in the tree parses it back, so an ugly string on already-corrupt input is the right trade against a dead engine.

Not reproduced against a live gateway: it needs the gateway to send a malformed schedule.

Closes #258.

Test plan

  • cargo test --offline --lib — 804 passed. The 2 failures are config::expiry_tests::{named_zone_converts_with_dst, instant_round_trips_to_wire}, which fail on the base commit too (fixed separately in config: resolve the legacy timezone names IB states its times in (ibx#335) #336).
  • cargo check --offline clean on every target: --lib, --features python, --bins, --examples, and each of the eight tests/*.rs targets individually.
  • tests/ib_paper_compat compared against a clean checkout of the base commit — line-identical sorted error sets.
  • Mutation check: dropping the is_ascii guard, and reverting the date slices to a length check, each fail a_schedule_that_is_not_ascii_does_not_panic by name — so both sites were genuinely reachable.
  • Positive control: a_well_formed_schedule_is_still_trimmed asserts the exact compact output, so the guard cannot pass by refusing everything.
  • The hostile cases place the replacement character at each boundary the parser cuts on, plus the short and empty shapes.

🤖 Generated with Claude Code

…ndary

The schedule parser validated byte positions and then sliced the `&str` at them. These are gateway strings decoded with `from_utf8_lossy`, so one invalid byte becomes a three-byte replacement character and every position after it shifts — a cut that was ASCII in the intended payload lands mid-character, and slicing a `&str` there is a panic. This runs on the hot loop, so it is an engine-down on malformed input rather than a dropped message.

The format is `YYYYMMDD-HH:MM:SS`, ASCII by definition, and requiring that outright is what makes the byte arithmetic sound rather than incidentally correct: the guard establishes what is at bytes 8 and 11 and says nothing about 12 to 14, where a multi-byte character would panic the slice.

The two date cuts ask for the range instead of assuming it. What replaces the panic is a degraded field, not a correct one — a reply this malformed has no recoverable date in it. The output is carried verbatim to the caller as `trading_hours`/`liquid_hours` and nothing in the tree parses it back, so a wrong string on already-corrupt input is the right trade against a dead engine.

Closes deepentropy#258.
@userFRM
userFRM force-pushed the fix/schedule-slice-panic branch from 186b237 to 84949c5 Compare July 30, 2026 16:44
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.

contracts: byte-guarded string slicing panics on a non-ASCII schedule reply, taking down the hot loop

1 participant