Skip to content

Commit d431291

Browse files
authored
feat(deserialize): prevent unbound allocation (#129)
With a crafted input, a malformed data of just four bytes may lead to allocation of 4 GiB. This may lead to OOM on constrained environments (e.g. inside of a container with memory limits set or on 32-bit machines). (This once again shows Rust desperately needs full specialisation. We may dream of time that it ever comes…)
1 parent d915ab5 commit d431291

File tree

1 file changed

+23
-4
lines changed

1 file changed

+23
-4
lines changed

borsh/src/de/mod.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,29 @@ impl BorshDeserialize for u8 {
144144
#[doc(hidden)]
145145
fn vec_from_reader<R: Read>(len: u32, reader: &mut R) -> Result<Option<Vec<Self>>> {
146146
let len: usize = len.try_into().map_err(|_| ErrorKind::InvalidInput)?;
147-
let mut vec = vec![0u8; len];
148-
reader
149-
.read_exact(vec.as_mut_slice())
150-
.map_err(unexpected_eof_to_unexpected_length_of_input)?;
147+
// Avoid OOM by limiting the size of allocation. This makes the read
148+
// less efficient (since we need to loop and reallocate) but it protects
149+
// us from someone sending us [0xff, 0xff, 0xff, 0xff] and forcing us to
150+
// allocate 4GiB of memory.
151+
let mut vec = vec![0u8; len.min(1024 * 1024)];
152+
let mut pos = 0;
153+
while pos < len {
154+
if pos == vec.len() {
155+
vec.resize(vec.len().saturating_mul(2).min(len), 0)
156+
}
157+
// TODO(mina86): Convert this to read_buf once that stabilises.
158+
match reader.read(&mut vec.as_mut_slice()[pos..])? {
159+
0 => {
160+
return Err(Error::new(
161+
ErrorKind::InvalidInput,
162+
ERROR_UNEXPECTED_LENGTH_OF_INPUT,
163+
))
164+
}
165+
read => {
166+
pos += read;
167+
}
168+
}
169+
}
151170
Ok(Some(vec))
152171
}
153172

0 commit comments

Comments
 (0)