Files
libfreemkv/docs/paramount.md
T
Matthew Jackson 6e14fc0c0c libfreemkv: 1.7.0 groundwork — sector-recycle soundness + CI hardening
Security
- Remove the unsafe Vec::set_len over recycled sector buffers — UB reachable
  from safe code (GHSA-j8ww-f5fg-9pmh, low). Uses Vec::resize; pooling unchanged.

CI / tooling
- clippy on stable with the MSRV single-sourced from Cargo.toml; cargo-deny
  dependency-audit gate with first-party git sources allowed (#15); audience-based
  comment-guard (#17).

Docs
- README (keydb ownership, BD-J parser count), drive-access transport/reset
  notes, mux timeline module-doc links.
2026-09-02 12:40:55 -07:00

11 KiB

Paramount/onQ playlists.xml — derivation notes

/BDMV/JAR/ is application-defined space, so this format has no spec to look up. Every field meaning below was derived by measuring real discs and cross-checking against per-display-set content. Treat an unfamiliar value as unknown rather than guessing — the disc's own forced_on_flag is the only authoritative forced signal.

ForcedSub — the forced_sub CSV cell values

The attribute reads like a boolean and was once parsed as one (cell == "1" → forced). It is not. Every image in the corpus carrying this vendor's playlists.xml — seven distinct discs — uses four values, and decoding three of those discs' feature subtitle tracks and counting every PGS display set separates them into two populations two orders of magnitude apart:

  • 0 — a subtitle track with no forced-narrative content. On the two discs measured that use the flag at all, not one 0 track carried a single forced_on_flag display set.
  • 1 — a FULL DIALOGUE track that additionally contains some forced-narrative signs. On one measured disc, all nine 1 cells are full tracks of 949-1411 display sets, eight of them carrying 5-14 flagged sets and the ninth none; that disc has no dedicated forced track at all. On another, all seven 1 cells are full tracks of 1602-1651 display sets carrying 0-31 flagged sets. Reading 1 as forced is what made one language present as two identical full subtitle tracks with one of them flagged forced.
  • 2 and 3 — a DEDICATED forced-narrative track. These take their own trailing STN slots, one per localized language, duplicating a language that already holds a full track earlier in the list. Measured: the two 2 slots on one disc are 15 and 10 display sets, EVERY one flagged forced, against ~1600 on that disc's full tracks; the four 3 slots on another are 7, 14, 23 and 59 display sets against 1216-2655. What distinguishes 2 from 3 the corpus does not reveal — both sit in the same trailing position, both measure the same shape, and one disc uses each for a different language — so both map alike.

So the old reading was wrong in BOTH directions: it flagged full dialogue tracks forced, and it discarded the cells that name the real forced tracks.

The 1 case is deliberately NOT carried through as a weaker "contains forced segments" hint. There is no qualifier for that, and the asymmetry argues against inventing one here: a wrong forced flag on a 30 MB dialogue track is the user-visible defect, while a missing hint costs nothing.

Nothing downstream can undo a wrong forced label on the discs that need it most: mux::codec::pgs::demotable may only clear a vendor forced label where some track on the disc demonstrably sets forced_on_flag, and measured discs using this label format never set it.

MAX_COM_INDICES — the CSV addressability bound

The highest CSV cell position that can ever be addressed. The labelling loops number cells 1-based into a u16 and break at u16::try_from(i + 1), so cell MAX_COM_INDICES and everything past it is never visited. Two different things are measured against that bound, and they are not the same:

  • A VALUE at or beyond it cannot match any cell. This caps the set: values are filtered before insertion, so at most MAX_COM_INDICES distinct entries can ever be stored, however long the attribute is. The HashSet that replaced a linear scan fixed the LOOKUP cost; this fixes the ALLOCATION, so a disc declaring half a billion indices no longer costs half a billion entries.
  • A POSITION at or beyond it describes nothing new. This caps the WORK, not the memory — the value filter already made the set small, but without it every one of those half a billion cells is still split and parsed. It is an early exit at the first position whose contents provably cannot matter, and it is why forced_sub — which holds no set at all, and so gets no protection from the value rule — is bounded too.

Real authoring is nowhere near either limit: the BD STN table admits at most 32 streams per playlist, so nothing legitimate is lost.

com_indices / forced_subs — why they are separate functions

Extracted so each bound is independently observable in a unit test. Asserting the bound through labels_from_feature is not possible in either case: for com_indices, a HashSet collapses repeated values and an out-of-range index changes no label either way, so a label-level test passes whether or not the cap exists. For forced_subs, the Vec is read only as forced.get(i) from a loop that stops at MAX_COM_INDICES, so cells past that are unreachable by construction and, again, unobservable through the labels. Returning the raw set/Vec lets a test hand in tens of thousands of distinct unaddressable entries and see them refused.

labels_from_feature

Split out from parse so the per-type numbering and commentary/forced- index logic is unit-testable without a SectorSource/UdfFs.

Test: a_playlists_stream_list_cannot_run_into_the_next_playlist

Immunity pin, section-boundary half. The pixelogic parser walks a flat string sequence and recognises its feature section's END by marker alone, so a section with no marker behind it runs off into whatever follows and counts it as more STN slots. Nothing here can do that: the stream list is one attribute of one XML element, so its length is the CSV's own cell count and its scope is the element's byte range that xml::find_element returns. Text after the element — including the next playlist's own aud — is not reachable from it.

And when the boundary is MISSING the failure is closed, not open: xml::find_element needs a matching close tag and yields None without one, so an unterminated element ends the walk rather than swallowing the rest of the document.

Mutation: hand labels_from_feature the document instead of the element, or let an unterminated element run to EOF → the bonus playlist's languages join the feature's stream list.

Test: bounding_the_parse_does_not_change_a_legitimate_playlist

sub_com1_idx is parsed straight out of the disc's playlists.xml, which is attacker-controlled and has no length bound of its own.

This replaces a WALL-CLOCK test. That one built a 200 000 x 1 000 001 fixture and failed if it took over 10 s, to prove the membership test was a set rather than a linear scan. Measured on the machine that wrote this: 1.62 s alone, and OVER 10 s — a real failure — when the suite's other 3 347 tests were running concurrently. A 6x margin against a shared CPU is not a margin; it is a CI failure that looks like a flake and gets re-run until it passes.

It also measured the wrong thing. Making the lookup O(1) bounded the QUERY, not the PARSE: the set was still built from every entry the disc declared, so a hostile playlist could still force an unbounded allocation before any lookup happened. MAX_COM_INDICES bounds that.

What this test guards is that bounding did not change what a legitimate playlist MEANS: it goes red if the bound is set too LOW (verified at 2 — the real indices 0,2,4 stop resolving and the purposes change). It does NOT go red if the bound is deleted entirely, because the out-of-range filler is unobservable at the label level and a HashSet collapses the repeats. Enforcement is proven separately, by distinct_unaddressable_indices_are_refused_not_stored, which reads the set itself. Two tests, two properties; neither pretends to the other's job.

Test: distinct_unaddressable_indices_are_refused_not_stored

The set REFUSES unaddressable indices, so a hostile playlist cannot inflate it. DISTINCT values on purpose: a HashSet collapses repeats, so a million copies of one index costs one entry and would prove nothing. Fifty thousand distinct out-of-range indices cost fifty thousand entries without the filter, and none with it — so this test goes red if the bound is removed, which the label-level assertions elsewhere cannot do.

Test: forced_sub_cells_past_the_last_addressable_one_are_not_parsed

forced_sub is bounded too — the third CSV in the same function, and the one that had no value filter to hide behind. Read through forced_subs rather than through the labels for the same reason the two tests above read the set: the subtitle loop stops at MAX_COM_INDICES, so a label-level assertion cannot tell a bounded parse from an unbounded one.

Test: an_index_that_cannot_address_any_cell_is_not_retained

Asserted through com_indices, not through the labels: the labelling loop never queries a cell position that high, so at the label level retaining the value is unobservable and the assertion could not fail. Reading the set is what makes the claim checkable.

Test: empty_csv_slot_still_occupies_its_stn_slot

The aud / sub CSVs are the vendor's STN-ordered stream lists: one slot per stream, and aud_com1_idx / forced_sub are indexed against those same slot positions. A slot whose language cell is empty carries nothing to label but still OCCUPIES its slot, so it must not renumber the slots behind it.

Numbering only the slots that carry a language collapsed every later label one position forward per empty cell, which is how a forced marker authored for one STN slot lands on the full-subtitle track in front of it.

Test: a_contains_forced_segments_cell_is_not_a_forced_track

forced_sub is an enumeration, and 1 is its "full dialogue track that also carries forced signs" value — NOT "this track is forced". Measured on a disc whose feature declares nine 1 cells among 32 subtitle slots: all nine are full dialogue tracks of 949-1411 display sets, and the disc has no dedicated forced track at all. Reading 1 as forced is what produced two identical full subtitle tracks for one language with one of them flagged forced. See "ForcedSub — the forced_sub CSV cell values" above for the full corpus measurement, including why nothing downstream can undo this.

Test: a_dedicated_forced_narrative_cell_is_a_forced_track

2 and 3 are the cells that DO name a dedicated forced-narrative track, and the old boolean reading discarded both. Measured: these cells occupy their own trailing STN slots, one per localized language, duplicating a language that already holds a full track earlier in the list. On one measured disc the four 3 slots carry 7, 14, 23 and 59 display sets against 1216-2655 on the full tracks they duplicate — and not one display set anywhere on that disc carries forced_on_flag, so neither the scan probe nor the muxer can promote them from content. The vendor cell is the only evidence there is.

Test: an_unrecognised_forced_sub_cell_is_not_forced

An unrecognised cell must fall to NOT forced. Asserting forced is the expensive mistake (a full dialogue track a player then burns on screen), so an unknown value from a future authoring revision must not be able to make that claim.

Test: find_feature_first_wins_on_audio_count_tie

On a tie in audio-slot count, the FIRST playlist encountered wins (consistent with select_result's first-wins tiebreak elsewhere in the registry) — later playlists only displace the current best on a STRICTLY greater count.