AIFF: Stop relying on file's provided size (Fix OOM)

This commit is contained in:
Serial 2022-07-10 18:15:38 -04:00
parent 8ee268b188
commit 8a70a77387
No known key found for this signature in database
GPG key ID: DA95198DC17C4568
20 changed files with 92 additions and 23 deletions

View file

@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
**AIFF**: Stop relying on the file-provided size when reading (Fixes OOM)
## [0.7.1] - 2022-07-08 ## [0.7.1] - 2022-07-08
### Added ### Added

View file

@ -16,7 +16,7 @@ where
let mut id3v2_chunk = (None, None); let mut id3v2_chunk = (None, None);
let mut chunks = Chunks::<B>::new(file_size); let mut chunks = Chunks::<B>::new(u64::from(file_size)); // TODO
while chunks.next(data).is_ok() { while chunks.next(data).is_ok() {
if &chunks.fourcc == b"ID3 " || &chunks.fourcc == b"id3 " { if &chunks.fourcc == b"ID3 " || &chunks.fourcc == b"id3 " {

View file

@ -8,13 +8,13 @@ use crate::id3::v2::tag::ID3v2Tag;
use crate::iff::chunk::Chunks; use crate::iff::chunk::Chunks;
use crate::properties::FileProperties; use crate::properties::FileProperties;
use std::io::{Read, Seek}; use std::io::{Read, Seek, SeekFrom};
use byteorder::BigEndian; use byteorder::BigEndian;
#[cfg(feature = "aiff_text_chunks")] #[cfg(feature = "aiff_text_chunks")]
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
pub(in crate::iff) fn verify_aiff<R>(data: &mut R) -> Result<u32> pub(in crate::iff) fn verify_aiff<R>(data: &mut R) -> Result<()>
where where
R: Read + Seek, R: Read + Seek,
{ {
@ -25,16 +25,21 @@ where
return Err(LoftyError::new(ErrorKind::UnknownFormat)); return Err(LoftyError::new(ErrorKind::UnknownFormat));
} }
Ok(u32::from_be_bytes( Ok(())
id[4..8].try_into().unwrap(), // Infallible
))
} }
pub(crate) fn read_from<R>(data: &mut R, read_properties: bool) -> Result<AiffFile> pub(crate) fn read_from<R>(data: &mut R, read_properties: bool) -> Result<AiffFile>
where where
R: Read + Seek, R: Read + Seek,
{ {
let file_size = verify_aiff(data)?; // TODO: Maybe one day the `Seek` bound can be removed?
// let file_size = verify_aiff(data)?;
verify_aiff(data)?;
let current_pos = data.stream_position()?;
let file_len = data.seek(SeekFrom::End(0))?;
data.seek(SeekFrom::Start(current_pos))?;
let mut comm = None; let mut comm = None;
let mut stream_len = 0; let mut stream_len = 0;
@ -49,7 +54,7 @@ where
#[cfg(feature = "id3v2")] #[cfg(feature = "id3v2")]
let mut id3v2_tag: Option<ID3v2Tag> = None; let mut id3v2_tag: Option<ID3v2Tag> = None;
let mut chunks = Chunks::<BigEndian>::new(file_size); let mut chunks = Chunks::<BigEndian>::new(file_len);
while chunks.next(data).is_ok() { while chunks.next(data).is_ok() {
match &chunks.fourcc { match &chunks.fourcc {

View file

@ -357,13 +357,14 @@ where
} }
fn write_to_inner(data: &mut File, mut tag: AiffTextChunksRef<'_, T, AI>) -> Result<()> { fn write_to_inner(data: &mut File, mut tag: AiffTextChunksRef<'_, T, AI>) -> Result<()> {
let file_size = super::read::verify_aiff(data)?; super::read::verify_aiff(data)?;
let file_len = data.metadata()?.len();
let text_chunks = Self::create_text_chunks(&mut tag)?; let text_chunks = Self::create_text_chunks(&mut tag)?;
let mut chunks_remove = Vec::new(); let mut chunks_remove = Vec::new();
let mut chunks = Chunks::<BigEndian>::new(file_size); let mut chunks = Chunks::<BigEndian>::new(file_len);
while chunks.next(data).is_ok() { while chunks.next(data).is_ok() {
match &chunks.fourcc { match &chunks.fourcc {

View file

@ -14,16 +14,16 @@ where
{ {
pub fourcc: [u8; 4], pub fourcc: [u8; 4],
pub size: u32, pub size: u32,
file_size: u32, remaining_size: u64,
_phantom: PhantomData<B>, _phantom: PhantomData<B>,
} }
impl<B: ByteOrder> Chunks<B> { impl<B: ByteOrder> Chunks<B> {
pub fn new(file_size: u32) -> Self { pub fn new(file_size: u64) -> Self {
Self { Self {
fourcc: [0; 4], fourcc: [0; 4],
size: 0, size: 0,
file_size, remaining_size: file_size,
_phantom: PhantomData, _phantom: PhantomData,
} }
} }
@ -35,6 +35,8 @@ impl<B: ByteOrder> Chunks<B> {
data.read_exact(&mut self.fourcc)?; data.read_exact(&mut self.fourcc)?;
self.size = data.read_u32::<B>()?; self.size = data.read_u32::<B>()?;
self.remaining_size = self.remaining_size.saturating_sub(8);
Ok(()) Ok(())
} }
@ -56,7 +58,7 @@ impl<B: ByteOrder> Chunks<B> {
R: Read + Seek, R: Read + Seek,
{ {
let cont = if let Some(size) = size { let cont = if let Some(size) = size {
self.read(data, size)? self.read(data, u64::from(size))?
} else { } else {
self.content(data)? self.content(data)?
}; };
@ -72,20 +74,21 @@ impl<B: ByteOrder> Chunks<B> {
where where
R: Read, R: Read,
{ {
self.read(data, self.size) self.read(data, u64::from(self.size))
} }
fn read<R>(&self, data: &mut R, size: u32) -> Result<Vec<u8>> fn read<R>(&mut self, data: &mut R, size: u64) -> Result<Vec<u8>>
where where
R: Read, R: Read,
{ {
if size + 4 > self.file_size { if size > self.remaining_size {
return Err(LoftyError::new(ErrorKind::TooMuchData)); return Err(LoftyError::new(ErrorKind::TooMuchData));
} }
let mut content = try_vec![0; size as usize]; let mut content = try_vec![0; size as usize];
data.read_exact(&mut content)?; data.read_exact(&mut content)?;
self.remaining_size = self.remaining_size.saturating_sub(size);
Ok(content) Ok(content)
} }
@ -122,6 +125,8 @@ impl<B: ByteOrder> Chunks<B> {
data.seek(SeekFrom::Current(i64::from(self.size)))?; data.seek(SeekFrom::Current(i64::from(self.size)))?;
self.correct_position(data)?; self.correct_position(data)?;
self.remaining_size = self.remaining_size.saturating_sub(u64::from(self.size));
Ok(()) Ok(())
} }
@ -134,6 +139,7 @@ impl<B: ByteOrder> Chunks<B> {
// and it is NOT included in the chunk's size // and it is NOT included in the chunk's size
if self.size % 2 != 0 { if self.size % 2 != 0 {
data.seek(SeekFrom::Current(1))?; data.seek(SeekFrom::Current(1))?;
self.remaining_size = self.remaining_size.saturating_sub(1);
} }
Ok(()) Ok(())

View file

@ -51,7 +51,7 @@ where
#[cfg(feature = "id3v2")] #[cfg(feature = "id3v2")]
let mut id3v2_tag: Option<ID3v2Tag> = None; let mut id3v2_tag: Option<ID3v2Tag> = None;
let mut chunks = Chunks::<LittleEndian>::new(file_size); let mut chunks = Chunks::<LittleEndian>::new(u64::from(file_size));
while chunks.next(data).is_ok() { while chunks.next(data).is_ok() {
match &chunks.fourcc { match &chunks.fourcc {

View file

@ -277,7 +277,7 @@ mod tests {
super::read::parse_riff_info( super::read::parse_riff_info(
&mut Cursor::new(&tag[..]), &mut Cursor::new(&tag[..]),
&mut Chunks::<LittleEndian>::new(tag.len() as u32), &mut Chunks::<LittleEndian>::new(tag.len() as u64),
(tag.len() - 1) as u64, (tag.len() - 1) as u64,
&mut parsed_tag, &mut parsed_tag,
) )
@ -293,7 +293,7 @@ mod tests {
super::read::parse_riff_info( super::read::parse_riff_info(
&mut Cursor::new(&tag[..]), &mut Cursor::new(&tag[..]),
&mut Chunks::<LittleEndian>::new(tag.len() as u32), &mut Chunks::<LittleEndian>::new(tag.len() as u64),
(tag.len() - 1) as u64, (tag.len() - 1) as u64,
&mut parsed_tag, &mut parsed_tag,
) )
@ -307,7 +307,7 @@ mod tests {
// Remove the LIST....INFO from the tag // Remove the LIST....INFO from the tag
super::read::parse_riff_info( super::read::parse_riff_info(
&mut Cursor::new(&writer[12..]), &mut Cursor::new(&writer[12..]),
&mut Chunks::<LittleEndian>::new(tag.len() as u32), &mut Chunks::<LittleEndian>::new(tag.len() as u64),
(tag.len() - 13) as u64, (tag.len() - 13) as u64,
&mut temp_parsed_tag, &mut temp_parsed_tag,
) )
@ -325,7 +325,7 @@ mod tests {
super::read::parse_riff_info( super::read::parse_riff_info(
&mut reader, &mut reader,
&mut Chunks::<LittleEndian>::new(tag_bytes.len() as u32), &mut Chunks::<LittleEndian>::new(tag_bytes.len() as u64),
(tag_bytes.len() - 1) as u64, (tag_bytes.len() - 1) as u64,
&mut riff_info, &mut riff_info,
) )

View file

@ -57,7 +57,7 @@ where
{ {
let mut info = None; let mut info = None;
let mut chunks = Chunks::<LittleEndian>::new(file_size); let mut chunks = Chunks::<LittleEndian>::new(u64::from(file_size));
while chunks.next(data).is_ok() { while chunks.next(data).is_ok() {
if &chunks.fourcc == b"LIST" { if &chunks.fourcc == b"LIST" {

View file

@ -0,0 +1,7 @@
use crate::oom_test;
use lofty::iff::AiffFile;
#[test]
fn oom1() {
oom_test::<AiffFile>("aifffile_read_from/oom-88065007d35ee271d5812fd723a3b458488813ea");
}

View file

@ -0,0 +1 @@

37
tests/fuzz/main.rs Normal file
View file

@ -0,0 +1,37 @@
use lofty::AudioFile;
use std::io::Cursor;
use std::path::Path;
use std::thread;
use std::time::Instant;
mod aifffile_read_from;
mod flacfile_read_from;
mod mp3file_read_from;
mod mp4file_read_from;
mod opusfile_read_from;
mod pictureinformation_from_jpeg;
mod pictureinformation_from_png;
mod speexfile_read_from;
mod vorbisfile_read_from;
mod wavfile_read_from;
mod wavpackfile_read_from;
pub fn get_reader(path: &str) -> Cursor<Vec<u8>> {
let path = Path::new("tests/fuzz/assets").join(path);
let b = std::fs::read(path).unwrap();
Cursor::new(b)
}
pub fn oom_test<A: AudioFile>(path: &'static str) {
let instant = Instant::now();
let thread = thread::spawn(|| {
<A as AudioFile>::read_from(&mut get_reader(path), true).unwrap();
});
while instant.elapsed().as_secs() < 2 {}
if !thread.is_finished() {
panic!("Failed to run test");
}
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@