diff --git a/crates/audio-io/tests/it/malformed.rs b/crates/audio-io/tests/it/malformed.rs index 6d00bcdc..4b2889b3 100644 --- a/crates/audio-io/tests/it/malformed.rs +++ b/crates/audio-io/tests/it/malformed.rs @@ -208,6 +208,62 @@ fn caf_sizes_beyond_the_file_are_errors_not_aborts() { assert!(matches!(decode(&constant, None), Err(AudioError::Malformed(m)) if m.contains("lists packets"))); } +/// An EBML element: `id`, then `size` as an 8-byte vint (so any size up to 2^56 − 2 fits, and +/// `u64::MAX` writes the reserved "unknown size"), then `body`. +fn ebml(id: &[u8], size: u64, body: &[u8]) -> Vec { + let size = if size == u64::MAX { 0x01FF_FFFF_FFFF_FFFF } else { 0x0100_0000_0000_0000 | size }; + [id, &size.to_be_bytes(), body].concat() +} + +/// The EBML header of a Matroska file. +fn ebml_header() -> Vec { + let doc_type = ebml(&[0x42, 0x82], 8, b"matroska"); + ebml(&[0x1A, 0x45, 0xDF, 0xA3], doc_type.len() as u64, &doc_type) +} + +/// #878: symphonia's Matroska reader takes element sizes from the file. With symphonia 0.5 a size +/// far beyond the file was allocated up front and aborted the process (no `catch_unwind` can catch +/// an allocation failure); since 0.6 such reads grow with the data actually read. Keep it that way +/// for the header, for codec data and for block frames. +#[test] +fn matroska_sizes_beyond_the_file_are_errors_not_aborts() { + let huge = 1u64 << 47; + // The 30-byte file from the report: EBML header and DocType sizes of 2^48 − 1 and 2^47. + let reported = [ + 0x1A, 0x45, 0xDF, 0xA3, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x42, 0x82, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, + 0x61, 0x74, 0x72, 0x6F, 0x73, 0x6B, 0x61, + ]; + let segment = |body: Vec| [ebml_header(), ebml(&[0x18, 0x53, 0x80, 0x67], u64::MAX, &body)].concat(); + // A track whose CodecPrivate claims 2^46 bytes, inside parents that claim even more. + let codec_private = ebml(&[0x63, 0xA2], huge >> 1, &[0; 16]); + let track = ebml(&[0xAE], huge, &[ebml(&[0xD7], 1, &[1]), ebml(&[0x86], 6, b"A_OPUS"), codec_private].concat()); + let tracks = segment(ebml(&[0x16, 0x54, 0xAE, 0x6B], huge, &track)); + // A valid 16-bit PCM track, then a cluster whose SimpleBlock claims 2^47 bytes of frame data. + let audio = [ebml(&[0xB5], 8, &48_000f64.to_be_bytes()), ebml(&[0x9F], 1, &[1]), ebml(&[0x62, 0x64], 1, &[16])].concat(); + let pcm = [ + ebml(&[0xD7], 1, &[1]), + ebml(&[0x73, 0xC5], 1, &[1]), + ebml(&[0x83], 1, &[2]), + ebml(&[0x86], 13, b"A_PCM/INT/LIT"), + ebml(&[0xE1], audio.len() as u64, &audio), + ] + .concat(); + let pcm = ebml(&[0xAE], pcm.len() as u64, &pcm); + let block = ebml(&[0x1F, 0x43, 0xB6, 0x75], u64::MAX, &[ebml(&[0xE7], 1, &[0]), ebml(&[0xA3], huge, &[0x81, 0, 0, 0x80])].concat()); + let info = [ebml(&[0x2A, 0xD7, 0xB1], 3, &[0x0F, 0x42, 0x40]), ebml(&[0x4D, 0x80], 4, b"test"), ebml(&[0x57, 0x41], 4, b"test")].concat(); + let info = ebml(&[0x15, 0x49, 0xA9, 0x66], info.len() as u64, &info); + let cluster = segment([info, ebml(&[0x16, 0x54, 0xAE, 0x6B], pcm.len() as u64, &pcm), block].concat()); + for hint in [None, Some("mkv"), Some("wav")] { + for (name, f) in [("reported", &reported[..]), ("codec data", &tracks)] { + assert!(probe(f, hint).is_err(), "{name}, {hint:?}"); + assert!(decode(f, hint).is_err(), "{name}, {hint:?}"); + } + // The block reader stops at the end of the file: a best-effort read of what is there. + let _ = probe(&cluster, hint); + assert!(decode(&cluster, hint).map_or(0, |(_, b)| b.frames()) < 1_000, "{hint:?}"); + } +} + #[test] fn well_formed_caf_still_reads() { let pcm: Vec = (0..480i16).flat_map(|i| (i * 50).to_be_bytes()).collect();