diff --git a/src/read/read_cache.rs b/src/read/read_cache.rs index d1377f1f..5fa04cf0 100644 --- a/src/read/read_cache.rs +++ b/src/read/read_cache.rs @@ -27,8 +27,33 @@ struct ReadCacheInternal { read: R, bufs: HashMap<(u64, u64), Box<[u8]>>, strings: HashMap<(u64, u8), Box<[u8]>>, + len: Option, } +impl ReadCacheInternal { + /// Ensures this range is contained in the len of the file + fn range_in_bounds(&mut self, range: &Range) -> Result<(), ()> { + if range.start <= range.end && range.end <= self.len()? { + Ok(()) + } else { + Err(()) + } + } + + /// The length of the underlying read, memoized + fn len(&mut self) -> Result { + match self.len { + Some(len) => Ok(len), + None => { + let len = self.read.seek(SeekFrom::End(0)).map_err(|_| ())?; + self.len = Some(len); + Ok(len) + } + } + } +} + + impl ReadCache { /// Create an empty `ReadCache` for the given stream. pub fn new(read: R) -> Self { @@ -37,6 +62,7 @@ impl ReadCache { read, bufs: HashMap::new(), strings: HashMap::new(), + len: None, }), } } @@ -64,8 +90,7 @@ impl ReadCache { impl<'a, R: Read + Seek> ReadRef<'a> for &'a ReadCache { fn len(self) -> Result { - let cache = &mut *self.cache.borrow_mut(); - cache.read.seek(SeekFrom::End(0)).map_err(|_| ()) + self.cache.borrow_mut().len() } fn read_bytes_at(self, offset: u64, size: u64) -> Result<&'a [u8], ()> { @@ -73,6 +98,7 @@ impl<'a, R: Read + Seek> ReadRef<'a> for &'a ReadCache { return Ok(&[]); } let cache = &mut *self.cache.borrow_mut(); + cache.range_in_bounds(&(offset..(offset.saturating_add(size))))?; let buf = match cache.bufs.entry((offset, size)) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { @@ -90,6 +116,7 @@ impl<'a, R: Read + Seek> ReadRef<'a> for &'a ReadCache { fn read_bytes_at_until(self, range: Range, delimiter: u8) -> Result<&'a [u8], ()> { let cache = &mut *self.cache.borrow_mut(); + cache.range_in_bounds(&range)?; let buf = match cache.strings.entry((range.start, delimiter)) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { diff --git a/testfiles b/testfiles index 298af7d2..d01e8f37 160000 --- a/testfiles +++ b/testfiles @@ -1 +1 @@ -Subproject commit 298af7d2d7d5a45400a26f6c61320116231f6f74 +Subproject commit d01e8f373ec9e33c9d8e23d4364fe8a1de9b2ad6 diff --git a/tests/read/elf.rs b/tests/read/elf.rs new file mode 100644 index 00000000..0edd520f --- /dev/null +++ b/tests/read/elf.rs @@ -0,0 +1,44 @@ +use object::Object; +use std::path::Path; +use std::path::PathBuf; + +fn get_buildid(path: &Path) -> Result>, object::read::Error> { + let file = std::fs::File::open(path).unwrap(); + let reader = object::read::ReadCache::new(file); + let object = object::read::File::parse(&reader)?; + object + .build_id() + .map(|option| option.map(ToOwned::to_owned)) +} + +#[test] +/// Regression test: used to attempt to allocate 5644418395173552131 bytes +fn get_builid_bad_elf() { + let path: PathBuf = [ + "testfiles", + "elf", + "yara-fuzzing", + "crash-7dc27920ae1cb85333e7f2735a45014488134673", + ] + .iter() + .collect(); + let _ = get_buildid(&path); +} + +#[test] +fn get_buildid_less_bad_elf() { + let path: PathBuf = [ + "testfiles", + "elf", + "yara-fuzzing", + "crash-f1fd008da535b110853885221ebfaac3f262a1c1e280f10929f7b353c44996c8", + ] + .iter() + .collect(); + let buildid = get_buildid(&path).unwrap().unwrap(); + // ground truth obtained from GNU binutils's readelf + assert_eq!( + buildid, + b"\xf9\xc0\xc6\x05\xd3\x76\xbb\xa5\x7e\x02\xf5\x74\x50\x9d\x16\xcc\xe9\x9c\x1b\xf1" + ); +} diff --git a/tests/read/mod.rs b/tests/read/mod.rs index d60d1933..ef402104 100644 --- a/tests/read/mod.rs +++ b/tests/read/mod.rs @@ -1,3 +1,4 @@ #![cfg(feature = "read")] mod coff; +mod elf;