Skip to content

Commit

Permalink
fix(unreal): Allow to parse with limit (#447)
Browse files Browse the repository at this point in the history
To avoid unbounded allocation on bad input data, this PR introduces a new API
`Unreal4Crash::parse_with_limit` that allows specifying a maximum allocation
size.
  • Loading branch information
jan-auer authored Nov 11, 2021
1 parent e6c6760 commit b011df3
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 5 deletions.
58 changes: 53 additions & 5 deletions symbolic-unreal/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
#![warn(missing_docs)]

use std::fmt;
use std::io::Read;
use std::iter::FusedIterator;
use std::ops::Deref;

use bytes::Bytes;
use flate2::read::ZlibDecoder;
use flate2::bufread::ZlibDecoder;
use scroll::{ctx::TryFromCtx, Endian, Pread};

use crate::context::Unreal4Context;
Expand Down Expand Up @@ -145,15 +146,36 @@ impl Unreal4Crash {
}

/// Parses a UE4 crash dump from the original, compressed data.
pub fn parse(bytes: &[u8]) -> Result<Self, Unreal4Error> {
if bytes.is_empty() {
///
/// To prevent unbounded decompression, consider using
/// [`parse_with_limit`](Self::parse_with_limit) with an explicit limit, instead.
pub fn parse(slice: &[u8]) -> Result<Self, Unreal4Error> {
Self::parse_with_limit(slice, usize::MAX)
}

/// Parses a UE4 crash dump from the original, compressed data up to a maximum size limit.
///
/// If files contained within the UE4 crash exceed the given size `limit`, this function returns
/// `Err` with [`Unreal4ErrorKind::TooLarge`].
pub fn parse_with_limit(slice: &[u8], limit: usize) -> Result<Self, Unreal4Error> {
if slice.is_empty() {
return Err(Unreal4ErrorKind::Empty.into());
}

let mut decompressed = Vec::new();
std::io::copy(&mut ZlibDecoder::new(bytes), &mut decompressed)
let decoder = &mut ZlibDecoder::new(slice);

decoder
.take(limit as u64)
.read_to_end(&mut decompressed)
.map_err(|e| Unreal4Error::new(Unreal4ErrorKind::BadCompression, e))?;

// The decoder was not exhausted if there's still a byte to read. Given that we're decoding
// from a slice, it should be safe to ignore `ErrorKind::Interrupted` and the likes.
if !matches!(decoder.read(&mut [0; 1]), Ok(0)) {
return Err(Unreal4ErrorKind::TooLarge.into());
}

Self::from_bytes(decompressed.into())
}

Expand Down Expand Up @@ -354,8 +376,10 @@ impl ExactSizeIterator for Unreal4FileIterator<'_> {}

#[cfg(test)]
mod tests {
use std::{error::Error, fs::File};
use symbolic_testutils::fixture;

use super::*;
use std::error::Error;

#[test]
fn test_parse_empty_buffer() {
Expand All @@ -380,4 +404,28 @@ mod tests {
let source = error.source().expect("error source");
assert_eq!(source.to_string(), "corrupt deflate stream");
}

// The size of the unreal_crash fixture when decompressed.
const DECOMPRESSED_SIZE: usize = 440752;

#[test]
fn test_parse_too_large() {
let mut file = File::open(fixture("unreal/unreal_crash")).expect("example file opens");
let mut file_content = Vec::new();
file.read_to_end(&mut file_content).expect("fixture file");

let result = Unreal4Crash::parse_with_limit(&file_content, DECOMPRESSED_SIZE - 1);
let error = result.expect_err("too large");
assert_eq!(error.kind(), Unreal4ErrorKind::TooLarge);
}

#[test]
fn test_parse_fits_exact() {
let mut file = File::open(fixture("unreal/unreal_crash")).expect("example file opens");
let mut file_content = Vec::new();
file.read_to_end(&mut file_content).expect("fixture file");

Unreal4Crash::parse_with_limit(&file_content, DECOMPRESSED_SIZE)
.expect("file fits decompression buffer");
}
}
4 changes: 4 additions & 0 deletions symbolic-unreal/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub enum Unreal4ErrorKind {
/// The crash file contains unexpected trailing data after the footer.
TrailingData,

/// The crash file contents are too large during decompression.
TooLarge,

/// Can't process a log entry.
InvalidLogEntry,

Expand All @@ -33,6 +36,7 @@ impl fmt::Display for Unreal4ErrorKind {
Self::BadCompression => write!(f, "bad compression"),
Self::BadData => write!(f, "invalid crash file contents"),
Self::TrailingData => write!(f, "unexpected trailing data"),
Self::TooLarge => write!(f, "crash file contents are too large"),
Self::InvalidLogEntry => write!(f, "invalid log entry"),
Self::InvalidXml => write!(f, "invalid xml"),
}
Expand Down

0 comments on commit b011df3

Please sign in to comment.