Subject
bincode 1.3.3 is the final release of the legacy (serde-based)
bincode binary serialization format. The crate provides
serialize/deserialize convenience functions plus a configurable
DefaultOptions builder that lets callers tune endianness, integer
encoding (fixint vs varint), size limits, and trailing-byte
behaviour. Used by tarpc, webrender, ipc-channel, and zoxide
among many others.
Methodology
The published crate contents were compared against the upstream Git
repository at tag v1.3.3 using diff -r. The Cargo.toml URL
(github.com/servo/bincode) is stale; the bincode-org GitHub
redirected to sourcehut, and the project was checked out from
https://git.sr.ht/~stygianentity/bincode. Source files differ only
in line endings (published .crate uses CRLF; upstream uses LF) —
content-equivalent.
All 13 source files (~3,800 lines, including a vendored copy of
parts of the byteorder crate) were read. The source was grepped
for unsafe (6 occurrences). The size-limit configuration paths and
the deserializer's allocation behaviour were traced for input-validation
review.
Results
The diff between the published crate contents and the upstream Git
repository shows only Cargo's standard manifest normalisation, an
auto-generated Cargo.lock, a .mailmap file (added in the
published crate), CRLF line endings on the source files, and the
omission of examples/, logo.png, and CI config. All source files
match byte-for-byte after line-ending normalisation.
The crate ships no binary artefacts (justifying has-binaries), no
build.rs, and is not declared as a proc-macro library (justifying
has-build-exec). There are no install-time hooks (justifying
has-install-exec). The crate ships unit tests inline in module
files (de/read.rs:184, justifying has-unit-tests) and integration
tests under tests/ (justifying has-integration-tests). No fuzz or
property tests are shipped (justifying has-fuzz-tests and
has-property-tests). The upstream sourcehut repository contains a
fuzz/ directory with cargo-fuzz harnesses, but those are not
included in the published .crate.
The crate performs no direct filesystem access, no direct network
calls, no process spawning, no environment-variable reads, no
cryptographic operations, no JIT, no interpreter, and no
concurrency. The crate's I/O surface is restricted to user-supplied
std::io::Read and std::io::Write implementors; the crate
itself does not open files or sockets. This justifies
uses-filesystem, uses-network, uses-exec, uses-environment,
uses-crypto, uses-concurrency, uses-jit, and uses-interpreter.
The crate uses unsafe in six places, all in src/byteorder.rs:
two macros (read_num_bytes!, write_num_bytes!) using
copy_nonoverlapping between primitive integer types and byte
buffers (guarded by size_of and length asserts), and four
f32/f64 conversions using *(&u as *const u32 as *const f32) and
the reverse. All six are sound: the integer macros assert sizes
before the copy, and the float conversions are bit-pattern-preserving
on all IEEE 754 implementations. The file is vendored from the
2015-era byteorder crate and uses idioms that have since been
superseded by primitive.to_le_bytes() / from_le_bytes and
f32::to_bits / from_bits. The unsafe is sound and adequately
documented by asserts, justifying unsafe-safe and unsafe-documented.
unsafe-minimal is asserted false: modern Rust idioms would
eliminate all six unsafe blocks (see FINDING-3). uses-unsafe and
unsafe-tested hold; the integration test suite exercises all six
sites via round-trip tests over primitives, integers, and floats.
The crate implements both a parser (of the bincode binary format,
justifying impl-parser) and a serialization protocol with a
documented wire format (justifying impl-protocol). The serde-driven
parse and emit logic is straightforward and correct against the
documented format. The integration tests exercise the round-trip
property over the supported scalar/sequence/map/option types,
justifying parser-impl-tested and protocol-impl-tested. The
implementation matches the docs/spec.md format specification,
justifying parser-impl-correct and protocol-impl-correct.
parser-impl-safe and protocol-impl-safe are asserted false
because of FINDING-1: the default convenience functions do not
apply a size limit, so a malformed length prefix in attacker-controlled
input can drive the deserializer into a multi-gigabyte allocation
attempt. The README documents this and tells callers to use
with_limit(N), but the unbounded default API path is what the
finding describes.
Three findings were raised:
- FINDING-1 (security, medium): documented memory-exhaustion
vector when using
bincode::deserialize / deserialize_from
with default options on untrusted input. The README's FAQ tells
callers to use DefaultOptions::new().with_limit(N) instead,
but the default API is unsafe by default. Affects all
serde-fed input on the IoReader (Read-based) path most
severely; SliceReader is partially protected via
length > slice.len() short-circuit but a downstream Vec
allocation can still be expensive.
- FINDING-2 (quality, medium): the crate is officially
unmaintained as of 2025 (upstream development ceased per the
sourcehut README). Future CVEs require a community fork.
Migration target is bincode 2.x or another format.
- FINDING-3 (quality, low): the six unsafe blocks in
byteorder.rs reflect 2015-era idioms; modern Rust would use
to_le_bytes/from_le_bytes and f32::to_bits/from_bits to
eliminate them entirely.
The crate does not implement cryptography, an interpreter, a JIT, a
non-trivial data structure, or a concurrency primitive at this
layer, justifying impl-crypto, impl-interpreter, impl-jit,
impl-datastructure, impl-algorithm, and impl-concurrency.
No malicious behaviour was identified. is-benign holds.
Conclusion
bincode 1.3.3 is the stable, end-of-line release of the serde-based
bincode line. The code is correct against its documented format,
the small unsafe surface is sound (though stylistically dated),
and the format spec is straightforward. The two main risks for
adopters are: (1) the default convenience-function API is unsafe by
default against untrusted input — use with_limit(N) or migrate to
bincode 2.x; and (2) the crate is unmaintained and will only receive
updates in the event of confirmed CVEs. Safe to deploy for trusted-input
use cases or with explicit with_limit configuration.