Skip to content

Commit 8b929f4

Browse files
author
rhoopr
committed
saiz/saio/hvcc: bound Vec::with_capacity on attacker-controlled counts
Three more `Vec::with_capacity` sites pass a count read straight from input as the capacity argument, with no upper bound: - `saiz` `sample_count` (`Vec<u8>`, `u32`): worst case ~4 GiB upfront. - `saio` `entry_count` (`Vec<u64>`, `u32`): worst case ~34 GiB upfront for v1 (8 bytes/entry). - `hvcc` `num_nalus` (`Vec<Vec<u8>>`, `u16`): worst case ~1.5 MiB upfront (24 bytes per `Vec<u8>` slot). Same defensive idiom as #157: pre-flight reject counts that cannot possibly fit in the remaining buffer (`count > buf.remaining() / N`, where N is the per-element minimum), then cap the upfront reservation. - saiz/saio: cap at 4096 to match `trun.rs` — covers typical CMAF segments (hundreds to low thousands of samples) without reallocating. - hvcc: cap at 64 — real HEVC arrays hold a handful of VPS/SPS/PPS/SEI entries, so 4096 would be well past anything realistic. Adds regression tests pinned to `Error::OverDecode` (the wrapper that `Atom::decode_maybe` produces from `Error::OutOfBounds`). Closes #156.
1 parent 3366b08 commit 8b929f4

2 files changed

Lines changed: 77 additions & 3 deletions

File tree

src/moov/trak/mdia/minf/stbl/saiz.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ impl AtomExt for Saiz {
5151
let default_sample_info_size = u8::decode(buf)?;
5252
let sample_count = u32::decode(buf)?;
5353
if default_sample_info_size == 0 {
54-
let mut sample_info_size = Vec::with_capacity(sample_count as usize);
54+
// Each entry is a single byte; reject counts that cannot
55+
// possibly fit in the remaining buffer before allocating.
56+
if sample_count as usize > buf.remaining() {
57+
return Err(Error::OutOfBounds);
58+
}
59+
let mut sample_info_size = Vec::with_capacity((sample_count as usize).min(4096));
5560
for _ in 0..sample_count {
5661
sample_info_size.push(u8::decode(buf)?);
5762
}
@@ -115,7 +120,13 @@ impl AtomExt for Saio {
115120
});
116121
}
117122
let entry_count = u32::decode(buf)?;
118-
let mut offsets = Vec::with_capacity(entry_count as usize);
123+
// Entries are 4 bytes (v0) or 8 bytes (v1); reject counts that
124+
// cannot possibly fit in the remaining buffer before allocating.
125+
let per_entry = if ext.version == SaioVersion::V1 { 8 } else { 4 };
126+
if entry_count as usize > buf.remaining() / per_entry {
127+
return Err(Error::OutOfBounds);
128+
}
129+
let mut offsets = Vec::with_capacity((entry_count as usize).min(4096));
119130
for _ in 0..entry_count {
120131
if ext.version == SaioVersion::V0 {
121132
let offset = u32::decode(buf)? as u64;
@@ -427,6 +438,32 @@ mod tests {
427438
);
428439
}
429440

441+
// Regression for issue #156: a u32::MAX sample_count must fail
442+
// cleanly without attempting a ~4 GiB upfront allocation.
443+
const ENCODED_SAIZ_HUGE_COUNT: &[u8] = &[
444+
0x00, 0x00, 0x00, 0x11, 0x73, 0x61, 0x69, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
445+
0xFF, 0xFF,
446+
];
447+
448+
#[test]
449+
fn test_saiz_huge_count() {
450+
let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_SAIZ_HUGE_COUNT);
451+
assert!(matches!(Saiz::decode(buf), Err(Error::OverDecode(_))));
452+
}
453+
454+
// Regression for issue #156: a u32::MAX entry_count must fail
455+
// cleanly without attempting a multi-GiB upfront allocation.
456+
const ENCODED_SAIO_HUGE_COUNT: &[u8] = &[
457+
0x00, 0x00, 0x00, 0x10, 0x73, 0x61, 0x69, 0x6f, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
458+
0xFF,
459+
];
460+
461+
#[test]
462+
fn test_saio_huge_count() {
463+
let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_SAIO_HUGE_COUNT);
464+
assert!(matches!(Saio::decode(buf), Err(Error::OverDecode(_))));
465+
}
466+
430467
#[test]
431468
fn test_saio_encode_cenc() {
432469
let saio = Saio {

src/moov/trak/mdia/minf/stbl/stsd/hevc/hvcc.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,14 @@ impl Atom for Hvcc {
7272
for _ in 0..num_of_arrays {
7373
let params = u8::decode(buf)?;
7474
let num_nalus = u16::decode(buf)?;
75-
let mut nalus = Vec::with_capacity(num_nalus as usize);
75+
// Each NALU has at least a u16 length prefix (2 bytes); reject
76+
// counts that cannot possibly fit in the remaining buffer before
77+
// allocating. Real HEVC arrays hold a handful of VPS/SPS/PPS/SEI
78+
// entries, so cap the upfront reservation at 64.
79+
if num_nalus as usize > buf.remaining() / 2 {
80+
return Err(Error::OutOfBounds);
81+
}
82+
let mut nalus = Vec::with_capacity((num_nalus as usize).min(64));
7683

7784
for _ in 0..num_nalus {
7885
let size = u16::decode(buf)? as usize;
@@ -420,6 +427,36 @@ mod tests {
420427
assert_eq!(decoded, hvcc);
421428
}
422429

430+
// Regression for issue #156: a u16::MAX num_nalus must fail cleanly
431+
// without attempting a ~1.5 MiB upfront allocation for empty input.
432+
const ENCODED_HVCC_HUGE_NALU_COUNT: &[u8] = &[
433+
0x00, 0x00, 0x00, 0x22, // size = 34
434+
0x68, 0x76, 0x63, 0x43, // "hvcC"
435+
// 22-byte configuration record up through length_size_minus_one:
436+
0x01, // configuration_version
437+
0x00, // profile_space|tier|profile_idc
438+
0x00, 0x00, 0x00, 0x00, // general_profile_compatibility_flags
439+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // general_constraint_indicator_flags
440+
0x00, // general_level_idc
441+
0x00, 0x00, // min_spatial_segmentation_idc
442+
0x00, // parallelism_type
443+
0x00, // chroma_format_idc
444+
0x00, // bit_depth_luma_minus8
445+
0x00, // bit_depth_chroma_minus8
446+
0x00, 0x00, // avg_frame_rate
447+
0x00, // constant_frame_rate|num_temporal_layers|nested|length_size
448+
0x01, // num_of_arrays = 1
449+
0x00, // first array params byte
450+
0xFF, 0xFF, // num_nalus = u16::MAX
451+
];
452+
453+
#[test]
454+
fn test_hvcc_huge_nalu_count() {
455+
let buf: &mut std::io::Cursor<&&[u8]> =
456+
&mut std::io::Cursor::new(&ENCODED_HVCC_HUGE_NALU_COUNT);
457+
assert!(matches!(Hvcc::decode(buf), Err(Error::OverDecode(_))));
458+
}
459+
423460
#[test]
424461
fn test_hvcc_mpeg_encode() {
425462
let hvcc = Hvcc {

0 commit comments

Comments
 (0)