Skip to content
This repository was archived by the owner on Aug 15, 2025. It is now read-only.
This repository was archived by the owner on Aug 15, 2025. It is now read-only.

bincode 2 overallocates on untrusted inputs #764

@udoprog

Description

@udoprog

I discovered this when fuzzing bincode using musli tests:

RUST_BACKTRACE=1 cargo run --features bincode-derive -p tests --bin fuzz -- --random

Basically what this harness does is feed the decoder random bytes.

What you see is something like this:

Panic due to overallocating
thread 'main' panicked at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/impl_alloc.rs:269:27:
capacity overflow
stack backtrace:
   0: rust_begin_unwind
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/std/src/panicking.rs:692:5
   1: core::panicking::panic_fmt
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:75:14
   2: alloc::raw_vec::capacity_overflow
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/alloc/src/raw_vec.rs:25:5
   3: alloc::raw_vec::handle_error
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/alloc/src/raw_vec.rs:791:29
   4: alloc::raw_vec::RawVecInner<A>::with_capacity_zeroed_in
             at /home/udoprog/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/raw_vec.rs:447:25
   5: alloc::raw_vec::RawVec<T,A>::with_capacity_zeroed_in
             at /home/udoprog/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/raw_vec.rs:214:20
   6: <u8 as alloc::vec::spec_from_elem::SpecFromElem>::from_elem
             at /home/udoprog/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/vec/spec_from_elem.rs:55:31
   7: alloc::vec::from_elem
             at /home/udoprog/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3190:5
   8: bincode::features::impl_alloc::<impl bincode::de::Decode<Context> for alloc::vec::Vec<T>>::decode
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/impl_alloc.rs:269:27
   9: bincode::features::impl_alloc::<impl bincode::de::Decode<Context> for alloc::string::String>::decode
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/impl_alloc.rs:341:21
  10: <bincode::features::serde::de_borrowed::SerdeDecoder<DE> as serde::de::Deserializer>::deserialize_string
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/serde/de_borrowed.rs:220:30
  11: serde::de::impls::<impl serde::de::Deserialize for alloc::string::String>::deserialize
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-1.0.219/src/de/impls.rs:704:9
  12: <core::marker::PhantomData<T> as serde::de::DeserializeSeed>::deserialize
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-1.0.219/src/de/mod.rs:800:9
  13: <<bincode::features::serde::de_borrowed::SerdeDecoder<DE> as serde::de::Deserializer>::deserialize_tuple::Access<DE> as serde::de::SeqAccess>::next_element_seed
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/serde/de_borrowed.rs:314:33
  14: serde::de::SeqAccess::next_element
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-1.0.219/src/de/mod.rs:1734:9
  15: <tests::models::allocated::_::<impl serde::de::Deserialize for tests::models::allocated::Allocated>::deserialize::__Visitor as serde::de::Visitor>::visit_seq
             at ./tests/src/models/allocated.rs:18:56
  16: <bincode::features::serde::de_borrowed::SerdeDecoder<DE> as serde::de::Deserializer>::deserialize_tuple
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/serde/de_borrowed.rs:332:9
  17: <bincode::features::serde::de_borrowed::SerdeDecoder<DE> as serde::de::Deserializer>::deserialize_struct
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/serde/de_borrowed.rs:417:9
  18: tests::models::allocated::_::<impl serde::de::Deserialize for tests::models::allocated::Allocated>::deserialize
             at ./tests/src/models/allocated.rs:18:56
  19: bincode::features::serde::de_borrowed::borrow_decode_from_slice
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/serde/de_borrowed.rs:60:18
  20: bincode::features::serde::de_owned::decode_from_slice
             at /home/udoprog/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bincode-2.0.1/src/features/serde/de_owned.rs:66:5
  21: tests::utils::full::bincode_serde::decode::decode
             at ./tests/src/utils/full.rs:68:26
  22: tests::utils::full::bincode_serde::decode
             at ./tests/src/utils/full.rs:36:1
  23: fuzz::main
             at ./tests/src/bin/fuzz.rs:361:5
  24: core::ops::function::FnOnce::call_once
             at /home/udoprog/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

The culprit is the decode implementation for containers like Vec<T>, where we decode the length that is expected to be read, and call Vec::with_capacity with the argument. This results in a vector of the provided length being allocated eagerly - even if there is no data to fill it. Meaning as little as an 8 byte payload or less of untrusted or corrupted payload can crash a server using bincode that is not configured with a limit*.

The broadly accepted solution is to limit the allocated capacity and decode the vector piecemeal, like here where I limit the allocated capacity in musli or the equivalent code in serde (which is more defensive taking the in-memory element size into account).

The thinking here is that legitimately large arrays will be filled with data from whatever buffer is being decoded. In order to allow the array to be filled, the buffer being decoded itself has to be large, which means that the source legitimately has to store or transmit that amount of data for it to be accepted which prevents at least small payloads from exhausting the resources of a server. Larger payloads would have to be explicitly accepted first before being decoded.

Note this only happened when I bumped to bincode 2.x. I did not see it in bincode 1.x. I think this was prevented by serde then.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions