Repository: mongodb/bson-rust (language: rust)

Issue:
OOM on huge allocation on malformed input (found through fuzzing)
Minimised base64 input: `CgAAAAQAAAAAAA==`

Stack trace:

```
==3694748==ERROR: AddressSanitizer: requested allocation size 0xfffffffffffffffc (0x800 after adjustments for alignment, red zones etc.) exceeds maximum supported size of 0x10000000000 (thread T0)
    #0 0x55e5687daf12 in calloc /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:154:3
    #1 0x55e56881c330 in bson::de::ensure_read_exactly::hf5185d965cc60fad (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x21f330)
    #2 0x55e568819981 in bson::de::deserialize_array::ha4650ca0cb324222 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x21c981)
    #3 0x55e56881e3da in bson::de::deserialize_bson_kvp::hec5fdee461eb2b1b (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x2213da)
    #4 0x55e568828b98 in bson::document::Document::decode::_$u7b$$u7b$closure$u7d$$u7d$::hf6f570c9035f9cf2 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x22bb98)
    #5 0x55e56881bcab in bson::de::ensure_read_exactly::h6a68518f15fd1165 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x21ecab)
    #6 0x55e568828653 in bson::document::Document::decode::hab1f24b5dff83160 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x22b653)
    #7 0x55e56884eb2c in rust_fuzzer_test_input (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x251b2c)
    #8 0x55e568c35280 in __rust_try (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x638280)
    #9 0x55e568c34edf in LLVMFuzzerTestOneInput (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x637edf)
    #10 0x55e568c2c104 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x62f104)
    #11 0x55e568c2c882 in fuzzer::Fuzzer::MinimizeCrashLoop(std::vector<unsigned char, fuzzer::fuzzer_allocator<unsigned char> > const&) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x62f882)
    #12 0x55e568c1b0ef in fuzzer::MinimizeCrashInputInternalStep(fuzzer::Fuzzer*, fuzzer::InputCorpus*) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x61e0ef)
    #13 0x55e568c2500d in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x62800d)
    #14 0x55e56875f142 in main (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/deserialize+0x162142)
    #15 0x7f4586739b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)
```


Discussion / hints:
Minimal test program. Must be run with `--release` (or otherwise debug assertions off), or you will get an "attempted to subtract with overflow"

```rust
fn main() {
    let crash = b"\n\x00\x00\x00\x04\x00\x00\x00\x00\x00";
    let mut reader = std::io::Cursor::new(crash);
    let _ = bson::Document::from_reader(&mut reader);
}
```

Tested this both on the latest git version as well as the latest version from crates.io, and it seems to happen on both.

Fix patch:
diff --git a/src/de/mod.rs b/src/de/mod.rs
index 0fe3a9aa..401dc432 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -176,6 +176,17 @@ fn deserialize_array<R: Read + ?Sized>(reader: &mut R, utf8_lossy: bool) -> Resu
     let mut arr = Array::new();
     let length = read_i32(reader)?;
 
+    if !(MIN_BSON_DOCUMENT_SIZE..=MAX_BSON_SIZE).contains(&length) {
+        return Err(Error::invalid_length(
+            length as usize,
+            &format!(
+                "array length must be between {} and {}",
+                MIN_BSON_DOCUMENT_SIZE, MAX_BSON_SIZE
+            )
+            .as_str(),
+        ));
+    }
+
     ensure_read_exactly(
         reader,
         (length as usize) - 4,
@@ -224,6 +235,14 @@ pub(crate) fn deserialize_bson_kvp<R: Read + ?Sized>(
             if let BinarySubtype::BinaryOld = subtype {
                 let data_len = read_i32(reader)?;
 
+                if !(0..=(MAX_BSON_SIZE - 4)).contains(&data_len) {
+                    return Err(Error::invalid_length(
+                        data_len as usize,
+                        &format!("0x02 length must be between 0 and {}", MAX_BSON_SIZE - 4)
+                            .as_str(),
+                    ));
+                }
+
                 if data_len + 4 != len {
                     return Err(Error::invalid_length(
                         data_len as usize,


Test patch:
diff --git a/src/tests/modules/serializer_deserializer.rs b/src/tests/modules/serializer_deserializer.rs
index cbc87fcd..33e6f9c8 100644
--- a/src/tests/modules/serializer_deserializer.rs
+++ b/src/tests/modules/serializer_deserializer.rs
@@ -540,3 +540,25 @@ fn test_serialize_deserialize_document() {
     let bad_point: Result<Point, crate::de::Error> = from_document(bad_point);
     assert!(bad_point.is_err());
 }
+
+/// [RUST-713](https://jira.mongodb.org/browse/RUST-713)
+#[test]
+fn test_deserialize_invalid_array_length() {
+    let _guard = LOCK.run_concurrently();
+    let buffer = b"\n\x00\x00\x00\x04\x00\x00\x00\x00\x00";
+    Document::from_reader(&mut std::io::Cursor::new(buffer))
+        .expect_err("expected deserialization to fail");
+}
+
+/// [RUST-713](https://jira.mongodb.org/browse/RUST-713)
+#[test]
+fn test_deserialize_invalid_old_binary_length() {
+    let _guard = LOCK.run_concurrently();
+    let buffer = b"\x0F\x00\x00\x00\x05\x00\x00\x00\x00\x00\x02\xFC\xFF\xFF\xFF";
+    Document::from_reader(&mut std::io::Cursor::new(buffer))
+        .expect_err("expected deserialization to fail");
+
+    let buffer = b".\x00\x00\x00\x05\x01\x00\x00\x00\x00\x00\x02\xfc\xff\xff\xff\xff\xff\xff\xff\x00\x00*\x00h\x0e\x10++\x00h\x0e++\x00\x00\t\x00\x00\x00\x00\x00*\x0e\x10++";
+    Document::from_reader(&mut std::io::Cursor::new(buffer))
+        .expect_err("expected deserialization to fail");
+}