libfreemkv: v1.0 hardening — codec/EBML/TS robustness + DTS parser fixes

Audit-driven fixes (rounds 1–3):
- hevc: correct hvcC profile/level SPS offsets (HEVC has a 2-byte NAL header)
- mkv: map all DTS variants to the registered A_DTS codec id; force a new
  cluster before the i16 cluster-relative timestamp can overflow
- ebml/mkvstream: bound untrusted EBML sizes (no multi-GB allocs); reject
  uint>8 (was an OOB panic) and non-{0,4,8} float widths (were a desync)
- ts: skip PES-header bytes that span a TS packet boundary; add the PMT
  section_len/prog_info_len bounds the PAT parser already had
- ac3: preserve a 0x0B77 syncword split across a PES boundary; cap buffer
- dts: validate each next-core boundary by decoded core size (a 0x7FFE8001
  pattern inside XLL payload no longer false-splits/drops the lossless
  extension); reject sub-minimum core frames; fix forced-emit PTS base
- lpcm: DVD program-stream PCM no longer double-strips the BD LPCM header
- vc1/mpeg2: do not emit a parameter-set-only PES as a standalone frame
- pgs/truehd: cap the pending reassembly buffer (parity with ac3/dts)
- aacs: ts_syncs_intact uses the exact packet count
- prefetched: capacity-guard the recycled-buffer set_len
- Cargo.toml: exclude project docs from the published crate

Convergence: a third independent audit pass found no remaining material
(CRITICAL/HIGH/MEDIUM) issues. Full precommit (fmt + clippy -D + tests,
Rust 1.86) green.
This commit is contained in:
MattJackson
2026-06-05 16:23:39 -07:00
parent e2aa9abd6d
commit 6be5198886
18 changed files with 1201 additions and 119 deletions
+53 -3
View File
@@ -44,6 +44,7 @@ impl CodecParser for Vc1Parser {
// Use DTS when available (monotonic for B-frame content), fall back to PTS
let ts_ns = pes.dts.or(pes.pts).map(pts_to_ns).unwrap_or(0);
let mut has_seq_header = false;
let mut has_entry_point = false;
let mut frame_start: Option<usize> = None;
// Scan for start codes (00 00 01 XX)
@@ -67,6 +68,7 @@ impl CodecParser for Vc1Parser {
SC_ENTRY_POINT => {
let end = find_next_sc(data, i + 4).unwrap_or(data.len());
self.entry_point = Some(data[i..end].to_vec());
has_entry_point = true;
}
SC_FRAME => {
// Frame data starts at this start code
@@ -85,11 +87,23 @@ impl CodecParser for Vc1Parser {
// Keyframe = this PES contains a sequence header (I-frame indicator in BD)
let keyframe = has_seq_header;
// Strip sequence header + entry point from frame data — those are in codecPrivate.
// Only include data from the frame start code onwards.
// Strip sequence header + entry point from frame data — those are in
// codecPrivate, not coded-picture data. Only include data from the
// frame start code onwards.
let frame_data = match frame_start {
Some(start) => &data[start..],
None => data, // no frame start code found, pass through entire PES
None => {
// No frame start code. If this PES carried only parameter sets
// (sequence header / entry point, captured above into
// codecPrivate), there is no coded picture to emit — drop it
// rather than passing parameter bytes through as a bogus
// keyframe. Mirrors how the H.264/HEVC parsers skip
// parameter-set-only access units.
if has_seq_header || has_entry_point {
return Vec::new();
}
data // genuine picture payload with no leading 0x0D — pass through
}
};
vec![Frame {
@@ -343,6 +357,42 @@ mod tests {
assert_eq!(&frames[0].data[0..4], &[0x00, 0x00, 0x01, SC_FRAME]);
}
// --- parameter-set-only PES (seq header + entry point, no frame SC) ---
#[test]
fn param_set_only_pes_emits_no_frame() {
let mut parser = Vc1Parser::new();
// Sequence header + entry point, but NO frame start code (0x0D).
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01, SC_SEQUENCE_HEADER]);
data.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
data.extend_from_slice(&[0x00, 0x00, 0x01, SC_ENTRY_POINT]);
data.extend_from_slice(&[0x11, 0x22, 0x33, 0x44]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
// No coded picture → no frame emitted (parameter bytes must not be
// passed through as a bogus keyframe).
assert!(
frames.is_empty(),
"parameter-set-only PES should not emit a frame"
);
// But codecPrivate is still captured.
assert!(parser.seq_header.is_some());
assert!(parser.entry_point.is_some());
assert!(parser.codec_private().is_some());
// A following frame-bearing PES still emits its picture.
let mut data2 = Vec::new();
data2.extend_from_slice(&[0x00, 0x00, 0x01, SC_FRAME]);
data2.extend_from_slice(&[0x55, 0x66, 0x77]);
let frames2 = parser.parse(&make_pes(data2, Some(180000)));
assert_eq!(frames2.len(), 1);
assert_eq!(&frames2[0].data[0..4], &[0x00, 0x00, 0x01, SC_FRAME]);
}
// --- empty PES ---
#[test]