#1429: MPEG-1 program stream cut inside a pack header no longer panics

Problem: packet_len accepted an MPEG-1 pack header (b[4]>>4==2) with
only 6 bytes available, but scan reads the SCR from head[4..9]. A file
ending inside the last pack header (00 00 01 BA 21 00 00) panicked with
"range end index 9 out of range".

Fix: packet_len requires the full 12-byte MPEG-1 pack header; a shorter
tail is not a packet and takes the normal resync path. The MPEG-2 branch
already needs 14 bytes (b.get(13)) before scan reads 10, so it is fine.
Tests: truncate a valid MPEG-1 and MPEG-2 program stream at every byte
length, plus the minimal 7-byte input; both panicked before the fix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76 2026-10-12 22:07:25 +02:00
parent 71438c627b
commit cf9ad66732
2 changed files with 47 additions and 0 deletions

View File

@ -94,6 +94,9 @@ fn packet_len(b: &[u8]) -> Option<usize> {
let stuff = (*b.get(13)? & 7) as usize;
Some(14 + stuff)
} else if b[4] >> 4 == 2 {
// MPEG-1 pack header: start code, 5-byte SCR, 3-byte mux rate. A file cut inside
// it is not a pack header yet (`scan` reads the SCR from the first 9 bytes).
b.get(..12)?;
Some(12)
} else {
None

View File

@ -276,6 +276,50 @@ fn truncation_and_mutation_never_panic() {
}
}
/// Opens `data` and reads every unit; errors are fine, panics are not.
fn open_and_read_all(data: &[u8]) {
let data = data.to_vec();
if let Ok(f) = open(&data) {
for (s, st) in f.streams.iter().enumerate() {
for i in 0..st.units.len() {
let _ = f.read_unit(&data, s, i);
}
}
}
}
#[test]
fn program_stream_cut_inside_a_pack_header_never_panics() {
// MPEG-1 pack header cut after the 4-byte start code + 3 bytes (once panicked in `scan`).
open_and_read_all(&[0, 0, 1, 0xBA, 0x21, 0, 0]);
for n in 4..=12 {
let mpeg1 = [0u8, 0, 1, 0xBA, 0x21, 0, 1, 0, 1, 0x80, 0, 1];
open_and_read_all(&mpeg1[..n]);
}
}
#[test]
fn program_stream_truncation_at_every_length_never_panics() {
let frames: Vec<Vec<u8>> = (0..3).map(|k| mpeg2_frame(if k == 0 { 1 } else { 2 }, k as u16, false, 300)).collect();
let es: Vec<u8> = frames.concat();
for mpeg1 in [false, true] {
let mut out = Vec::new();
for (k, chunk) in es.chunks(500).enumerate() {
if mpeg1 {
out.extend([0, 0, 1, 0xBA, 0x21, 0, 1, 0, 1, 0x80, 0, 1]);
} else {
out.extend([0, 0, 1, 0xBA, 0x44, 0, 4, 0, 4, 1, 1, 0x89, 0xC3, 0xF8]);
}
out.extend(pes(0xE0, Some(3600 * k as i64), None, chunk, true));
}
out.extend([0, 0, 1, 0xB9]);
assert!(open(&out).is_ok(), "the untruncated stream opens (mpeg1: {mpeg1})");
for cut in 0..=out.len() {
open_and_read_all(&out[..cut]);
}
}
}
#[test]
fn timestamps_unwrap_across_the_33_bit_wrap() {
let mut u = Unwrap::default();