Skip to content

Commit 15c1aed

Browse files
committed
MP4: Allow invalid atom sizes with ParsingMode::Relaxed
1 parent 9b4cf83 commit 15c1aed

File tree

3 files changed

+46
-16
lines changed

3 files changed

+46
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222

2323
### Changed
2424
- **Timestamp**: `Timestamp::parse` with empty inputs will return `None` when not using `ParsingMode::Strict` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/416))
25+
- **MP4**: Atoms with sizes greater than the remaining file size will be ignored with `ParsingMode::Relaxed` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/433))
2526

2627
### Fixed
2728
- **Fuzzing** (Thanks [@qarmin](https://github.com/qarmin)!) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/TODO)):

lofty/src/mp4/atom_info.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,14 @@ impl AtomInfo {
180180

181181
// `len` includes itself and the identifier
182182
if (len - ATOM_HEADER_LEN) > reader_size {
183-
data.seek(SeekFrom::Current(-4))?;
183+
log::warn!("Encountered an atom with an invalid length, stopping");
184+
185+
if parse_mode == ParsingMode::Relaxed {
186+
// Seek to the end, as we cannot gather anything else from the file
187+
data.seek(SeekFrom::End(0))?;
188+
return Ok(None);
189+
}
190+
184191
err!(SizeMismatch);
185192
}
186193

@@ -204,6 +211,14 @@ impl AtomInfo {
204211
ident: atom_ident,
205212
}))
206213
}
214+
215+
pub(crate) fn header_size(&self) -> u64 {
216+
if !self.extended {
217+
return ATOM_HEADER_LEN;
218+
}
219+
220+
ATOM_HEADER_LEN + 8
221+
}
207222
}
208223

209224
fn parse_freeform<R>(

lofty/src/mp4/write.rs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use crate::config::ParsingMode;
22
use crate::error::{LoftyError, Result};
3+
use crate::io::{FileLike, Length, Truncate};
4+
use crate::macros::err;
35
use crate::mp4::atom_info::{AtomIdent, AtomInfo, IDENTIFIER_LEN};
4-
use crate::mp4::read::skip_unneeded;
6+
use crate::mp4::read::{meta_is_full, skip_unneeded};
57

68
use std::cell::{RefCell, RefMut};
79
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
810
use std::ops::RangeBounds;
911

10-
use crate::io::{FileLike, Length, Truncate};
1112
use byteorder::{BigEndian, WriteBytesExt};
1213

1314
/// A wrapper around [`AtomInfo`] that allows us to track all of the children of containers we deem important
@@ -17,8 +18,10 @@ pub(super) struct ContextualAtom {
1718
pub(crate) children: Vec<ContextualAtom>,
1819
}
1920

21+
const META_ATOM_IDENT: AtomIdent<'_> = AtomIdent::Fourcc(*b"meta");
22+
2023
#[rustfmt::skip]
21-
const IMPORTANT_CONTAINERS: [[u8; 4]; 7] = [
24+
const IMPORTANT_CONTAINERS: &[[u8; 4]] = &[
2225
*b"moov",
2326
*b"udta",
2427
*b"moof",
@@ -44,26 +47,37 @@ impl ContextualAtom {
4447
return Ok(None);
4548
};
4649

47-
let mut children = Vec::new();
48-
49-
let AtomIdent::Fourcc(fourcc) = info.ident else {
50-
*reader_len = reader_len.saturating_sub(info.len);
51-
return Ok(Some(ContextualAtom { info, children }));
52-
};
50+
match info.ident {
51+
AtomIdent::Fourcc(ident) if IMPORTANT_CONTAINERS.contains(&ident) => {},
52+
_ => {
53+
*reader_len = reader_len.saturating_sub(info.len);
54+
55+
// We don't care about the atom's contents
56+
skip_unneeded(reader, info.extended, info.len)?;
57+
return Ok(Some(ContextualAtom {
58+
info,
59+
children: Vec::new(),
60+
}));
61+
},
62+
}
5363

54-
if !IMPORTANT_CONTAINERS.contains(&fourcc) {
55-
*reader_len = reader_len.saturating_sub(info.len);
64+
let mut len = info.len - info.header_size();
65+
let mut children = Vec::new();
5666

57-
// We don't care about the atom's contents
58-
skip_unneeded(reader, info.extended, info.len)?;
59-
return Ok(Some(ContextualAtom { info, children }));
67+
// See meta_is_full for details
68+
if info.ident == META_ATOM_IDENT && meta_is_full(reader)? {
69+
len -= 4;
6070
}
6171

62-
let mut len = info.len - 8;
6372
while let Some(child) = Self::read(reader, &mut len, parse_mode)? {
6473
children.push(child);
6574
}
6675

76+
if len != 0 {
77+
// TODO: Print the container ident
78+
err!(BadAtom("Unable to read entire container"));
79+
}
80+
6781
*reader_len = reader_len.saturating_sub(info.len);
6882
// reader.seek(SeekFrom::Current(*reader_len as i64))?; // Skip any remaining bytes
6983
Ok(Some(ContextualAtom { info, children }))

0 commit comments

Comments
 (0)