Skip to content

Commit

Permalink
Merge pull request #41 from nearprotocol/fix-zero-size
Browse files Browse the repository at this point in the history
fix zero size element vector deserialize security issue
  • Loading branch information
ailisp committed Nov 13, 2019
2 parents 98297b1 + a59d71a commit b2e95a6
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 7 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Why do we need yet another serialization format? Borsh is the first serializer t
* Consistent means there is a bijective mapping between objects and their binary representations. There is no two binary representations that deserialize
into the same object. This is extremely useful for applications that use binary representation to compute hash;
* Borsh comes with a full specification that can be used for implementations in other languages;
* Safe. Borsh implementations use safe coding practices. In Rust, Borsh uses only safe code;
* Safe. Borsh implementations use safe coding practices. In Rust, Borsh uses almost only safe code, with one exception usage of `unsafe` to avoid an exhaustion attack;
* Speed. In Rust, Borsh achieves high performance by opting out from [Serde](https://serde.rs) which makes it faster
than [bincode](https://github.com/servo/bincode) in some cases; which also reduces the code size.

Expand Down
23 changes: 18 additions & 5 deletions borsh-rs/borsh/src/de/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::io::{Cursor, Error, Read};
use std::mem::size_of;
use std::mem::{forget, size_of};

mod hint;

Expand Down Expand Up @@ -127,12 +127,25 @@ where
#[inline]
fn deserialize<R: Read>(reader: &mut R) -> Result<Self, Error> {
let len = u32::deserialize(reader)?;
// TODO(16): return capacity allocation when we can safely do that.
let mut result = Vec::with_capacity(hint::cautious::<T>(len));
for _ in 0..len {
if size_of::<T>() == 0 {
let mut result = Vec::new();
result.push(T::deserialize(reader)?);

let p = result.as_mut_ptr();
unsafe {
forget(result);
let len = len as usize;
let result = Vec::from_raw_parts(p, len, len);
Ok(result)
}
} else {
// TODO(16): return capacity allocation when we can safely do that.
let mut result = Vec::with_capacity(hint::cautious::<T>(len));
for _ in 0..len {
result.push(T::deserialize(reader)?);
}
Ok(result)
}
Ok(result)
}
}

Expand Down
11 changes: 11 additions & 0 deletions borsh-rs/borsh/tests/test_zero_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use borsh::{BorshDeserialize, BorshSerialize};

#[derive(BorshDeserialize, PartialEq, Debug)]
struct A;

#[test]
fn test_deserialize_vector_to_many_zero_size_struct() {
let v = [0u8, 0u8, 0u8, 64u8];
let a = Vec::<A>::try_from_slice(&v).unwrap();
assert_eq!(A {}, a[usize::pow(2, 30) - 1])
}
3 changes: 2 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ <h4>Borsh, binary serializer for security-critical projects.</h4>

<dt class="col-sm-3">Safety</dt>
<dd class="col-sm-9">
Borsh implementations use safe coding practices. In Rust, Borsh uses only safe code;
Borsh implementations use safe coding practices. In Rust, Borsh uses almost only safe code,
with one exception to avoid exhaustion attack;
</dd>

<dt class="col-sm-3">Specification</dt>
Expand Down

0 comments on commit b2e95a6

Please sign in to comment.