cargo / serde / audit
cargo : serde @ 1.0.228
PE Patrick Elsen signed 2026-05-28 published 2026-05-28

Claims

build-exec-deterministicbuild-exec-minimalbuild-exec-no-networkbuild-exec-no-write-outbuild-exec-safeenvironment-safehas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignunsafe-documentedunsafe-minimalunsafe-safeunsafe-testeduses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

1.0.228 is a re-export facade over serde_core, exposing Serialize, Deserialize, and related traits. The build.rs probes rustc version and writes one generated file to OUT_DIR. Two unsafe blocks both call str::from_utf8_unchecked on buffers proven to contain only valid UTF-8; both are documented and sound. No findings.

Report

Subject

serde 1.0.228 is the facade crate for the serde serialization/deserialization framework. Starting with the 1.0.228 generation of the codebase, the implementation was split: all trait definitions, impls, macros, and private helpers now live in the separately-versioned serde_core crate, while this crate re-exports those items at its own root. The public API surface is Serialize, Serializer, Deserialize, Deserializer, the de and ser modules, and forward_to_deserialize_any!. An optional derive feature re-exports serde_derive's proc-macro derives.

Methodology

The published crate contents were compared against the upstream Git repository at the commit recorded in .cargo_vcs_info.json using diff. Source files were surveyed with grep for unsafe, FFI, network, filesystem, process, and environment access. The build.rs script was read in full. All source files under src/ were read, covering lib.rs, integer128.rs, src/private/mod.rs, src/private/de.rs (3502 lines, read in full), src/private/ser.rs (1383 lines, read in full), src/core/format.rs, and the surrounding context of both unsafe blocks in src/core/format.rs:16 and src/core/ser/impls.rs:821. The tool used was openvet 0.6.0.

Results

The diff between contents and VCS shows only the expected cargo-normalised Cargo.toml and the standard Cargo.toml.orig/.cargo_vcs_info.json/Cargo.lock artefacts. No source files differ; no unexplained binaries are present. This justifies has-binaries and is-benign.

The build.rs script reads three standard Cargo environment variables (OUT_DIR, CARGO_PKG_VERSION_PATCH, RUSTC), generates a versioned private.rs written exclusively to OUT_DIR, and conditionally sets rustc cfg flags by probing $RUSTC --version. It performs no network requests and writes no files outside OUT_DIR. This justifies has-build-exec, build-exec-safe, build-exec-no-network, build-exec-no-write-out, build-exec-minimal, build-exec-deterministic. The environment reads are all documented Cargo standard variables; justifies uses-environment and environment-safe.

Two unsafe blocks exist in the codebase, both in src/core/ (part of the docs.rs-inlined copy of serde_core). The first is in src/core/format.rs:16: str::from_utf8_unchecked on a buffer that is populated exclusively by copying bytes from &str arguments via fmt::Write::write_str, guaranteeing valid UTF-8 content. The second is in src/core/ser/impls.rs:821: str::from_utf8_unchecked on a buffer pre-filled with b'.' and digit bytes written by format_u8; both are valid single-byte ASCII code units. The comment at the call site states the invariant explicitly, and the test_format_u8 unit test at line 789 exhaustively validates all 256 u8 values. Both unsafe blocks are documented and minimal. Justifies uses-unsafe, unsafe-safe, unsafe-documented, unsafe-minimal.

No cryptographic operations, network calls, filesystem I/O, process spawning, or concurrency primitives are present in this crate's own code. The src/core/crate_root.rs re-exports std::net and std::sync::{Arc, Mutex, RwLock} for use in serde_core's serialization impls, but serde itself neither opens sockets nor spawns threads. This justifies uses-crypto, uses-exec, uses-jit, uses-interpreter, uses-network, uses-filesystem, uses-concurrency. The crate neither implements cryptographic algorithms, parsers, interpreters, JIT, protocols, data structures, concurrency primitives, nor algorithms of its own; justifies impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm, impl-concurrency.

The crate ships no integration tests, fuzz tests, or property tests; justifies has-integration-tests, has-fuzz-tests, has-property-tests. There is no installation hook; justifies has-install-exec. The single unit test in the crate (test_format_u8) directly validates the input domain of the format_u8 helper used by the second unsafe block. Justifies has-unit-tests. The test_format_u8 test exhaustively checks all 256 u8 values for the helper whose output feeds into the unsafe from_utf8_unchecked call; unsafe-tested is satisfied by this coverage.

Conclusion

The crate's surface area is narrow: it is primarily a re-export facade over serde_core, with a build.rs that writes one generated file to OUT_DIR and probes the rustc version. The two unsafe blocks are both str::from_utf8_unchecked calls whose UTF-8 invariants are structurally guaranteed by the code that fills the respective buffers. No findings were recorded.

Findings

No findings.

Annotations(4)

build.rs

The build script does four things: emits cargo:rerun-if-changed=build.rs; emits a rustc-cfg flag (if_docsrs_then_no_serde_core); generates a small private.rs file written exclusively to OUT_DIR; and probes the rustc minor version by running $RUSTC --version to set compatibility cfg flags. No network access, no reads outside the crate tree, no writes outside OUT_DIR. Justifies has-build-exec, build-exec-safe, build-exec-no-network, build-exec-no-write-out, build-exec-minimal.

The env::var_os("OUT_DIR"), env::var("CARGO_PKG_VERSION_PATCH"), and env::var_os("RUSTC") reads are all standard Cargo-supplied variables. Justifies uses-environment and environment-safe. The build-exec output is a pure function of the rustc version and patch number; justifies build-exec-deterministic.

src/core/format.rs

src/core/format.rs, line 14-17

    pub fn as_str(&self) -> &str {
        let slice = &self.bytes[..self.offset];
        unsafe { str::from_utf8_unchecked(slice) }
    }

The Buf::as_str method calls str::from_utf8_unchecked on self.bytes[..self.offset]. The only way bytes are written into self.bytes is through Buf's Write impl, which accepts a &str argument and copies its bytes via copy_from_slice. Since &str is always valid UTF-8, the slice at [..self.offset] contains only valid UTF-8 bytes when as_str is called. The invariant is upheld. Justifies uses-unsafe, unsafe-safe, unsafe-documented, unsafe-minimal.

src/core/ser/impls.rs

src/core/ser/impls.rs, line 818-822

                written += format_u8(*oct, &mut buf[written + 1..]) + 1;
            }
            // Safety: We've only written ASCII bytes to the buffer, so it is valid UTF-8
            let buf = unsafe { str::from_utf8_unchecked(&buf[..written]) };
            serializer.serialize_str(buf)

The from_utf8_unchecked call operates on a buffer pre-filled with b'.' (ASCII 46) and then populated by format_u8, which writes only ASCII digit bytes (0x30–0x39). Both dot and digit bytes are valid single-byte UTF-8 code units. The comment above the unsafe line ("We've only written ASCII bytes to the buffer, so it is valid UTF-8") states the invariant explicitly. The test_format_u8 test at line 789 exhaustively validates all 256 u8 inputs against the stdlib to_string() output, confirming only ASCII digits are produced. Justifies unsafe-safe, unsafe-documented, unsafe-minimal.

src/lib.rs

This is the main entry point for the serde 1.0.228 facade crate. Under non-docs.rs builds, it re-exports serde_core's de, ser, Deserialize, Deserializer, Serialize, and Serializer items via a pub use serde_core::... statement. A generated private.rs file (written to OUT_DIR by build.rs) wires serde_core::__private into serde::__private. When the derive feature is enabled, serde_derive::Deserialize and serde_derive::Serialize are re-exported as top-level items. The crate_root! macro also inlines a lib module aliasing std/core/alloc types, and the tri! macro for error propagation without ?. No malicious code, no I/O, no network access. Justifies is-benign.