mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-29 21:16:47 -06:00
Audio import: regression test for oversized Matroska element sizes (#878)
With symphonia 0.5, the Matroska reader allocated whatever size an element claimed before reading it, so a 30-byte file whose EBML header claims 2^48 bytes aborted the process on import, drag and drop or session open. Since the bump to symphonia 0.6 such reads grow with the data actually read (safe_read_into_boxed_slice), so the abort is gone without a SoundCraft-side check. Lock that in: the reported file, a track whose CodecPrivate claims 2^46 bytes and a SimpleBlock that claims 2^47 bytes must give an error (or, for the block, a best-effort read of what is there) under every hint, never an abort. The test passes with memory capped at 1 GB.
This commit is contained in:
@@ -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<u8> {
|
||||
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<u8> {
|
||||
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<u8>| [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<u8> = (0..480i16).flat_map(|i| (i * 50).to_be_bytes()).collect();
|
||||
|
||||
Reference in New Issue
Block a user