mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-13 22:12:12 +00:00
craft-cfb, craft-media: hostile-input hammer for CFB, ISO BMFF and Matroska (#1066)
The hammer over two compound files (version 3 and 4, and their first 2 KiB), the synthetic ISO BMFF files of the fuzz test, and a synthetic Matroska file plus four muxed ones (no lacing, Xiph, EBML, fixed-or-EBML), each opened and read through the whole API. No panic found. Ported from storytold/filmcraft#1794 (crates/cfb/src/tests/hostile.rs, crates/isobmff/tests/fuzz.rs, crates/matroska/src/synthetic_tests/hostile.rs). Co-authored-by: Logan Jennings <loganwjennings@gmail.com>
This commit is contained in:
1 parent
49dfd6ddc0
commit
06e949f30b
7 files changed
+123
No files matched your search
Generated
+3
@@ -974,6 +974,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-cfb"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"craft-media-testkit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-cms"
|
||||
|
||||
@@ -11,3 +11,6 @@ repository.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
craft-media-testkit = { workspace = true }
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
mod hostile;
|
||||
mod ported;
|
||||
|
||||
fn pattern(n: usize, seed: u8) -> Vec<u8> {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//! Hostile-input hammer: truncation, bit flips and extreme field values must never panic or hang.
|
||||
|
||||
use super::*;
|
||||
|
||||
fn exercise(d: &[u8]) {
|
||||
let Ok(f) = CompoundFile::open(d) else { return };
|
||||
let _ = f.root();
|
||||
let _ = f.sector_size();
|
||||
for id in 0..2000 {
|
||||
if f.entry(id).is_none() {
|
||||
break;
|
||||
}
|
||||
let _ = f.read(id);
|
||||
let _ = f.child(id, "s0");
|
||||
}
|
||||
let _ = f.read(usize::MAX);
|
||||
let _ = f.find("Header-2/Content-3b03/s4");
|
||||
let _ = f.read_path("Mobs-19010/properties");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_input_never_panics() {
|
||||
for (i, v) in [Version::V3, Version::V4].into_iter().enumerate() {
|
||||
let (bytes, _) = sample(v, 5);
|
||||
craft_media_testkit::hostile::hammer(&format!("cfb seed {i}"), &bytes, 15_000, exercise);
|
||||
// The header and directory are where the structure lives: hammer a prefix harder.
|
||||
let head = &bytes[..bytes.len().min(2048)];
|
||||
craft_media_testkit::hostile::hammer(&format!("cfb head {i}"), head, 15_000, exercise);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ use crate::matroska::mux::{el, el_float, el_str, el_uint};
|
||||
use crate::matroska::track::ContentEncoding;
|
||||
use crate::matroska::{ByteSource, Demuxer, MkvFile, OpenOptions, Packet, open_with};
|
||||
|
||||
mod hostile;
|
||||
|
||||
fn block(track: u8, rel: i16, flags: u8, data: &[u8]) -> Vec<u8> {
|
||||
let mut b = vec![0x80 | track];
|
||||
b.extend_from_slice(&rel.to_be_bytes());
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
//! Hostile-input hammer: truncation, bit flips and extreme field values must never panic or hang.
|
||||
|
||||
use super::*;
|
||||
use crate::matroska::mux::{LacingMode, MkvWriter, MuxOptions, TrackSpec};
|
||||
use crate::matroska::{MkvFile, TrackKind};
|
||||
use std::io::Cursor;
|
||||
|
||||
fn muxed(lacing: LacingMode) -> Vec<u8> {
|
||||
let mut v = TrackSpec::new(TrackKind::Video, "V_MJPEG");
|
||||
v.video_size = Some((16, 16));
|
||||
v.default_duration_ns = Some(40_000_000);
|
||||
let mut a = TrackSpec::new(TrackKind::Audio, "A_PCM/INT/LIT");
|
||||
a.audio = Some((48000.0, 1, Some(16)));
|
||||
let mut s = TrackSpec::new(TrackKind::Subtitle, "S_TEXT/UTF8");
|
||||
s.language = Some("fre".into());
|
||||
let opts = MuxOptions { lacing, cluster_duration_ns: 100_000_000, ..Default::default() };
|
||||
let mut m = MkvWriter::new(Cursor::new(Vec::new()), vec![v, a, s], opts).unwrap();
|
||||
for i in 0..12i64 {
|
||||
m.write_frame(0, i * 40_000_000, i % 4 == 0, &vec![i as u8; 30 + i as usize], None).unwrap();
|
||||
for k in 0..3 {
|
||||
m.write_frame(1, (i * 40 + k * 10) * 1_000_000, true, &[k as u8; 48], None).unwrap();
|
||||
}
|
||||
if i % 5 == 0 {
|
||||
m.write_frame(2, i * 40_000_000, true, b"hello", Some(500_000_000)).unwrap();
|
||||
}
|
||||
}
|
||||
m.finish().unwrap().into_inner()
|
||||
}
|
||||
|
||||
fn walk(f: &MkvFile, d: &[u8]) {
|
||||
for t in 0..f.tracks.len() {
|
||||
let n = f.tracks[t].samples.len();
|
||||
for k in [0, n / 2, n.saturating_sub(1), n, usize::MAX] {
|
||||
let _ = f.read_sample(d, t, k);
|
||||
let _ = f.read_sample_addition(d, t, k);
|
||||
}
|
||||
let _ = f.keyframe_before(t, i64::MIN);
|
||||
let _ = f.keyframe_before(t, i64::MAX);
|
||||
}
|
||||
let _ = f.duration_ns();
|
||||
}
|
||||
|
||||
fn exercise(d: &[u8]) {
|
||||
for opts in
|
||||
[OpenOptions { index: true, verify_crc: true, ..Default::default() }, OpenOptions { index: false, verify_crc: false, ..Default::default() }]
|
||||
{
|
||||
if let Ok(f) = open_with(d, &opts) {
|
||||
walk(&f, d);
|
||||
}
|
||||
if let Ok(mut dm) = Demuxer::with_options(d, opts) {
|
||||
let mut n = 0;
|
||||
while let Ok(Some(_)) = dm.next_packet() {
|
||||
n += 1;
|
||||
if n > 100_000 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for t in 0..dm.tracks().len() {
|
||||
let _ = dm.seek(t, 20_000_000);
|
||||
let _ = dm.seek(t, i64::MAX);
|
||||
let _ = dm.seek(t, i64::MIN);
|
||||
let _ = dm.keyframe_index(t);
|
||||
}
|
||||
let _ = dm.build_index();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_input_never_panics() {
|
||||
let seeds = [synthetic(), muxed(LacingMode::None), muxed(LacingMode::Xiph), muxed(LacingMode::Ebml), muxed(LacingMode::FixedOrEbml)];
|
||||
for (i, s) in seeds.iter().enumerate() {
|
||||
craft_media_testkit::hostile::hammer(&format!("matroska seed {i}"), s, 20_000, exercise);
|
||||
}
|
||||
}
|
||||
@@ -175,6 +175,15 @@ fn mutated_ffmpeg_fixtures_never_panic() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_hammer_never_panics() {
|
||||
for (i, f) in synthetic_files().iter().enumerate() {
|
||||
craft_media_testkit::hostile::hammer(&format!("isobmff seed {i}"), f, 20_000, |d| {
|
||||
exercise(d);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncations_never_panic() {
|
||||
for f in synthetic_files() {
|
||||
|
||||
Reference in new issue
Block a user