Skip to content

Commit f1a0ec9

Browse files
authored
read: check that sizes are smaller than the file length in ReadCache (#630)
This avoids large allocations due to invalid sizes in corrupted files.
1 parent e676d8b commit f1a0ec9

File tree

4 files changed

+77
-3
lines changed

4 files changed

+77
-3
lines changed

src/read/read_cache.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ struct ReadCacheInternal<R: Read + Seek> {
2727
read: R,
2828
bufs: HashMap<(u64, u64), Box<[u8]>>,
2929
strings: HashMap<(u64, u8), Box<[u8]>>,
30+
len: Option<u64>,
31+
}
32+
33+
impl<R: Read + Seek> ReadCacheInternal<R> {
34+
/// Ensures this range is contained in the len of the file
35+
fn range_in_bounds(&mut self, range: &Range<u64>) -> Result<(), ()> {
36+
if range.start <= range.end && range.end <= self.len()? {
37+
Ok(())
38+
} else {
39+
Err(())
40+
}
41+
}
42+
43+
/// The length of the underlying read, memoized
44+
fn len(&mut self) -> Result<u64, ()> {
45+
match self.len {
46+
Some(len) => Ok(len),
47+
None => {
48+
let len = self.read.seek(SeekFrom::End(0)).map_err(|_| ())?;
49+
self.len = Some(len);
50+
Ok(len)
51+
}
52+
}
53+
}
3054
}
3155

3256
impl<R: Read + Seek> ReadCache<R> {
@@ -37,6 +61,7 @@ impl<R: Read + Seek> ReadCache<R> {
3761
read,
3862
bufs: HashMap::new(),
3963
strings: HashMap::new(),
64+
len: None,
4065
}),
4166
}
4267
}
@@ -64,15 +89,15 @@ impl<R: Read + Seek> ReadCache<R> {
6489

6590
impl<'a, R: Read + Seek> ReadRef<'a> for &'a ReadCache<R> {
6691
fn len(self) -> Result<u64, ()> {
67-
let cache = &mut *self.cache.borrow_mut();
68-
cache.read.seek(SeekFrom::End(0)).map_err(|_| ())
92+
self.cache.borrow_mut().len()
6993
}
7094

7195
fn read_bytes_at(self, offset: u64, size: u64) -> Result<&'a [u8], ()> {
7296
if size == 0 {
7397
return Ok(&[]);
7498
}
7599
let cache = &mut *self.cache.borrow_mut();
100+
cache.range_in_bounds(&(offset..(offset.saturating_add(size))))?;
76101
let buf = match cache.bufs.entry((offset, size)) {
77102
Entry::Occupied(entry) => entry.into_mut(),
78103
Entry::Vacant(entry) => {
@@ -90,6 +115,7 @@ impl<'a, R: Read + Seek> ReadRef<'a> for &'a ReadCache<R> {
90115

91116
fn read_bytes_at_until(self, range: Range<u64>, delimiter: u8) -> Result<&'a [u8], ()> {
92117
let cache = &mut *self.cache.borrow_mut();
118+
cache.range_in_bounds(&range)?;
93119
let buf = match cache.strings.entry((range.start, delimiter)) {
94120
Entry::Occupied(entry) => entry.into_mut(),
95121
Entry::Vacant(entry) => {

tests/read/elf.rs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use object::Object;
2+
use std::path::Path;
3+
use std::path::PathBuf;
4+
5+
#[cfg(feature = "std")]
6+
fn get_buildid(path: &Path) -> Result<Option<Vec<u8>>, object::read::Error> {
7+
let file = std::fs::File::open(path).unwrap();
8+
let reader = object::read::ReadCache::new(file);
9+
let object = object::read::File::parse(&reader)?;
10+
object
11+
.build_id()
12+
.map(|option| option.map(ToOwned::to_owned))
13+
}
14+
15+
#[cfg(feature = "std")]
16+
#[test]
17+
/// Regression test: used to attempt to allocate 5644418395173552131 bytes
18+
fn get_buildid_bad_elf() {
19+
let path: PathBuf = [
20+
"testfiles",
21+
"elf",
22+
"yara-fuzzing",
23+
"crash-7dc27920ae1cb85333e7f2735a45014488134673",
24+
]
25+
.iter()
26+
.collect();
27+
let _ = get_buildid(&path);
28+
}
29+
30+
#[cfg(feature = "std")]
31+
#[test]
32+
fn get_buildid_less_bad_elf() {
33+
let path: PathBuf = [
34+
"testfiles",
35+
"elf",
36+
"yara-fuzzing",
37+
"crash-f1fd008da535b110853885221ebfaac3f262a1c1e280f10929f7b353c44996c8",
38+
]
39+
.iter()
40+
.collect();
41+
let buildid = get_buildid(&path).unwrap().unwrap();
42+
// ground truth obtained from GNU binutils's readelf
43+
assert_eq!(
44+
buildid,
45+
b"\xf9\xc0\xc6\x05\xd3\x76\xbb\xa5\x7e\x02\xf5\x74\x50\x9d\x16\xcc\xe9\x9c\x1b\xf1"
46+
);
47+
}

tests/read/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#![cfg(feature = "read")]
22

33
mod coff;
4+
mod elf;

0 commit comments

Comments
 (0)