cargo / zerocopy / audit
cargo : zerocopy @ 0.8.50
PE Patrick Elsen signed 2026-06-02 published 2026-06-02

Claims

build-exec-deterministicbuild-exec-minimalbuild-exec-no-networkbuild-exec-no-write-outbuild-exec-safeenvironment-safeexec-safefilesystem-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

Audit of zerocopy 0.8.50, Google's Rust library for safe byte/typed-value transmutation. Matches upstream Git byte-for-byte. Capability surface outside unsafe is small: a build.rs reads Cargo.toml and runs rustc --version; no network, no runtime FS/exec/env, no crypto. ~1249 unsafe occurrences governed by a documented soundness policy, lint-enforced safety comments, and Kani proofs. Four low-severity findings: test-only unsoundness, documentation gap, scope statement, build-script note.

Report

Subject

zerocopy is a Rust library, maintained by Google, that provides safe, zero-cost primitives for treating arbitrary bytes as typed values and vice versa. Its public API centres on six unsafe-but-safely-derivable traits — FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout, and TryFromBytes — together with the smart-pointer types Ref<B, T> and Ptr<'a, T, I> (the latter tracking aliasing, alignment, and validity invariants in its type parameter I), free functions for value/reference transmutation (transmute, try_transmute, transmute_ref, transmute_mut, plus the try_ variants), zero-copy parsing helpers (ref_from_bytes, ref_from_prefix, ref_from_suffix, read_from_*), and the byte-order-tagged integer wrappers U16<E>/U32<E>/U64<E>. Optional features pull in the zerocopy-derive proc-macro crate (derive), enable the alloc/std allocator-dependent API surface, and turn on SIMD-type trait impls for x86, x86_64, AArch64, and PowerPC.

Methodology

The published crate contents were compared against the upstream Git repository at the commit recorded in .cargo_vcs_info.json using diff -rq; the src/ and tests/ trees match byte-for-byte. find and wc -l were used to size the codebase (~28K LOC across 24 Rust files under src/; tests/ adds further files). grep enumerated the capability surface: std::net, std::fs, std::process, std::sync (atomics, Mutex), std::thread — none appear in the runtime source other than doc examples; Command::new appears only in build.rs; env::var_os appears only in build.rs. grep -c produced raw counts of the unsafe token (~1249 occurrences) and SAFETY: markers (~696). #[cfg(kani)] blocks were enumerated by grep — seven proof modules in src/byte_slice.rs, src/byteorder.rs, src/layout.rs, and src/util/mod.rs. The crate's documented soundness policy in POLICIES.md and the lint posture at src/lib.rs:220-287 were read. The build.rs file was read in full and its capability surface analysed. An Explore sub-agent was dispatched to produce a survey-level capability map; its findings were spot-checked against the source by re-grepping for the cited identifiers (impl_or_verify!, test_is_bit_valid_shared, FIXME(#429), FIXME(#899)). The code was not built or executed locally and a full soundness review of the unsafe surface was not undertaken; see FINDING-3 for the explicit scope statement.

Results

The published crate matches its upstream Git tree byte-for-byte in src/ and tests/. The differences are confined to cargo-generated metadata (.cargo_vcs_info.json, Cargo.lock, Cargo.toml.orig, normalised Cargo.toml), upstream-only repo plumbing (.github/, .gemini/, .cargo/), and the upstream sibling crates / workspace members (zerocopy-derive, anneal, hermes, testutil, tools, vendor) which are excluded from the published crate. There are no binary artefacts in the published crate (justifying has-binaries) and no install hooks (justifying has-install-exec).

A build.rs is present (justifying has-build-exec). Its capability surface is small and entirely documented in FINDING-4: it reads Cargo.toml from the source tree, invokes rustc --version via Command::new using the cargo-provided RUSTC environment variable, and emits cargo:rustc-cfg=no_zerocopy_*_X_Y_Z directives gating MSRV-sensitive features (core::error::Error impls, AVX-512 SIMD types, AArch64 SIMD, target_has_atomics, generic bounds in const fn, panic-in-const-fn). No filesystem writes occur outside cargo's directed output, no network access takes place, and the rustc invocation uses argv form with no shell interpolation. Justifies uses-environment, environment-safe, uses-filesystem, filesystem-safe, uses-exec, exec-safe, build-exec-safe, build-exec-deterministic, build-exec-no-network, build-exec-no-write-out, and build-exec-minimal.

The runtime crate makes no network calls (justifying uses-network), no cryptographic operations (justifying uses-crypto, impl-crypto), no JIT (justifying uses-jit, impl-jit), no embedded interpreter (justifying uses-interpreter, impl-interpreter), no concurrency primitives (justifying uses-concurrency, impl-concurrency), no protocol or data-structure or general-purpose-algorithm implementation in the senses the audit taxonomy uses those terms (justifying impl-protocol, impl-datastructure, impl-algorithm), and no parser in the data-format-or-language sense (justifying impl-parser — the byte-to-typed-value conversion the crate implements is transmutation, not parsing). The atomic-type trait impls in src/impls.rs are guarded behind cfg(target_has_atomics = ...) predicates and add no concurrency surface to zerocopy itself; the doc-example uses of std::thread::spawn and std::fs::File in src/error.rs and src/lib.rs appear inside /// comment blocks and are not compiled into the library.

The crate contains a large volume of unsafe code (justifying uses-unsafe) — ~1249 token occurrences across ~28K LOC, the bulk of which is the central trait machinery in src/util/, the layout reasoning in src/layout.rs, the pointer-with-invariants type in src/pointer/, the per-type trait impls in src/impls.rs, the wrappers in src/wrappers.rs, and the byte-slice operations in src/byte_slice.rs and src/split_at.rs. Each is the explicit purpose of the crate: zerocopy exists so that its consumers do not have to write the unsafe code themselves. Unsafe discipline is enforced by #![deny(unsafe_op_in_unsafe_fn)], #![deny(clippy::undocumented_unsafe_blocks)], #![deny(clippy::missing_safety_doc)], and #![deny(clippy::multiple_unsafe_ops_per_block)] at src/lib.rs:220-287, with a documented soundness policy in POLICIES.md requiring every SAFETY: comment to constitute an informal proof citing the Rust Reference or stdlib documentation and to hold under every Rust version from the MSRV forward. Five #[cfg(kani)] modules (src/byte_slice.rs, src/byteorder.rs, src/layout.rs, src/util/mod.rs) hold formal-verification proofs for the layout-arithmetic, byte-slice, and byteorder code paths; the tests/ directory carries codegen.rs, include.rs, and ui.rs integration suites (justifying has-integration-tests) and many in-tree #[test] modules (justifying has-unit-tests, unsafe-tested). No QuickCheck/proptest-style property tests are present (justifying has-property-tests) and no libfuzzer / cargo-fuzz harnesses ship with the published crate (justifying has-fuzz-tests — upstream Git does not appear to ship a fuzz/ workspace member for this crate at this commit either). The unsafe is minimal in the sense that each block has a defined purpose tied to the crate's transmutation contract (justifying unsafe-minimal), and the documentation discipline that supports unsafe-documented is the lint-enforced clippy::undocumented_unsafe_blocks plus the ~696 explicit SAFETY: markers.

Four low-severity findings were recorded. FINDING-1 documents a small, isolated, author-acknowledged unsoundness in test-only code: the impl_or_verify! macro in src/impls.rs (lines 1796 and 1807) constructs a Ptr via assume_initialized even though the inner type is not statically known to be IntoBytes. The flaw is confined to test-time differential verification of trait impls, is tracked under upstream issue #899, and is not reachable from any non-test API. FINDING-2 records the small population of pre-existing uncommented unsafe blocks tracked under upstream issue #429 — a documentation-hygiene gap, not a soundness defect, with the linter preventing regression. FINDING-3 is an explicit scope statement: a complete soundness audit of zerocopy's unsafe surface was not performed here, and the unsafe-safe assertion rests on the upstream project's review process, lint posture, Kani proofs, and ecosystem deployment rather than on an end-to-end proof performed in this audit. FINDING-4 documents the build script's capability surface (file reads in source tree, rustc --version invocation, cargo cfg emission) and supports the build-exec audit claims.

The crate carries no malicious calls — no telemetry, no data exfiltration, no obfuscated payloads, no targeted cfg branches (the per-arch cfg(target_arch = ...) blocks gate SIMD trait impls only). Supports is-benign.

Conclusion

zerocopy 0.8.50 is a large, well-documented, Google-maintained crate whose explicit purpose is to provide a safe surface over Rust transmutation. The published crate matches upstream Git byte-for-byte. The capability surface outside unsafe is small and well-bounded: a build script that reads Cargo.toml and invokes rustc --version for MSRV-conscious cfg gating, no network, no filesystem outside the build script, no exec outside the build script, no cryptographic operations, no parser/interpreter/JIT. The unsafe surface is large by design and is governed by a documented soundness policy, lint-enforced safety comments, Kani proofs on the core layout/pointer machinery, and an extensive in-tree and integration test suite. The audit recorded one isolated test-only unsoundness (FINDING-1, upstream-tracked as #899), an outstanding documentation-coverage gap (FINDING-2, upstream-tracked as #429), an explicit scope statement (FINDING-3), and a documented build-time capability note (FINDING-4). The crate's claim that it lets its users avoid writing unsafe rests on the soundness of its internal unsafe; the discipline visible from a survey-level review is consistent with that claim, but consumers with strict soundness requirements should rely on Kani, Miri, or an independent expert audit rather than treating this OpenVet review as proof.

Findings(4)

FINDING-1 safety low

Test-only unsoundness in `impl_or_verify!` verification path (#899)

The impl_or_verify! macro in src/impls.rs (lines 1796 and 1807) constructs a Ptr over the value being verified and calls assume_initialized() even though $ty is not statically known to be IntoBytes:

let c = Ptr::from_ref(&*val);
let c = c.forget_aligned();
// SAFETY: FIXME(#899): This is unsound. $ty is not
// necessarily IntoBytes, but that's the corner we've
// backed ourselves into by using Ptr::from_ref.
let c = unsafe { c.assume_initialized() };

The author flags it as unsound in the SAFETY comment itself and tracks the fix under upstream issue #899. This code lives inside test-time verification machinery (test_is_bit_valid_shared) used to cross-check that hand-written trait implementations agree with what the derive macros would produce. It is not reachable from the published, non-test API surface of the crate; consumers of zerocopy do not run impl_or_verify! themselves. The unsoundness is therefore confined to the crate's own test suite, where the worst-case effect is a test failure or a spurious test pass — never a memory-safety hazard for downstream callers.

FINDING-2 quality low

Incomplete `SAFETY:` comment coverage tracked in #429

POLICIES.md states the project's goal of 100% SAFETY: comment coverage on every unsafe block, enforced going forward by #![deny(clippy::undocumented_unsafe_blocks)] (declared at src/lib.rs line 282). A small population of pre-existing uncommented unsafe blocks is grandfathered in and tracked under upstream issue #429.

Grep finds ~696 SAFETY: markers against ~1249 unsafe token occurrences across src/ (the unsafe count includes unsafe fn signatures, unsafe impl, unsafe trait, and unsafe { ... } blocks — only blocks would carry a SAFETY comment, so the ratio is much closer to 1:1 than those raw numbers suggest). Five FIXME(#429) markers remain in src/lib.rs and a handful more scattered through src/byte_slice.rs, src/util/*, src/impls.rs, and src/pointer/inner.rs. This is documentation hygiene, not a soundness defect; the lint blocks regressions while #429 closes the remaining gaps.

FINDING-3 safety low

Soundness review of all unsafe blocks not performed in this audit

Zerocopy contains a large volume of unsafe code (~1249 token occurrences across ~28K LOC of Rust) implementing the crate's core promise of safe transmutation primitives. A complete soundness audit — verifying that every unsafe block's invariants hold under all callers, that trait impls of FromBytes / IntoBytes / Unaligned / Immutable / KnownLayout / TryFromBytes are correct for every type, and that the layout and pointer-manipulation primitives in src/pointer/, src/util/, and src/impls.rs are sound under the Rust Reference's documented guarantees — was not performed as part of this audit.

This audit instead reviewed:

  • The published surface against upstream Git (byte-for-byte match in src/ and tests/).
  • The crate's documented soundness policy in POLICIES.md.
  • The lint posture (#![deny(unsafe_op_in_unsafe_fn)], #![deny(clippy::undocumented_unsafe_blocks)], #![deny(clippy::missing_safety_doc)], plus tight clippy::indexing_slicing, clippy::arithmetic_side_effects, clippy::unwrap_used, etc., at src/lib.rs lines 256-287).
  • The test infrastructure: in-tree #[test] blocks, the tests/ directory (codegen, include, ui), and the seven #[cfg(kani)] formal-verification blocks in src/byte_slice.rs, src/byteorder.rs, src/layout.rs, and src/util/mod.rs.
  • The build script and its limited capability surface (FINDING-4).
  • The two documented soundness exceptions (FINDING-1, FINDING-2).

The unsafe-safe claim asserted in this audit therefore rests on the upstream project's discipline and review process (Google-maintained, well-documented soundness contract, lint-enforced safety comments, Kani proofs on the core layout/pointer machinery, broad ecosystem deployment), not on an end-to-end soundness proof performed here. Downstream consumers who require formal soundness guarantees should rely on Kani, Miri, or an independent expert review rather than treating this OpenVet audit as one.

FINDING-4 security low

Build script parses Cargo.toml and invokes `rustc --version` (expected, scoped)

build.rs performs three observable actions:

  1. Reads Cargo.toml from the crate's own source tree via fs::read_to_string("Cargo.toml").
  2. Spawns the rust compiler via Command::new(env::var_os("RUSTC").expect(...)) with the single argument --version and captures stdout. The RUSTC env var is provided by cargo.
  3. Emits cargo:rustc-cfg=no_zerocopy_*_X_Y_Z and cargo:rustc-check-cfg=... directives to stdout, gating crate features (notably SIMD types, core::error::Error impls, atomic-target detection) on the detected toolchain version.

No filesystem writes outside cargo's directed output. No network access. No reading of files outside the crate source tree. No shell interpolation — Command::new uses argv form. The build script is documented at the top of build.rs and the metadata it consumes is in [package.metadata.build-rs] of Cargo.toml. This is the documented and expected mechanism for MSRV-conscious feature gating; recording the finding to make the build-time surface visible (justifies uses-environment, uses-filesystem, uses-exec, exec-safe, filesystem-safe, environment-safe, build-exec-safe, build-exec-deterministic, build-exec-no-network, build-exec-no-write-out, build-exec-minimal).

Annotations(8)

POLICIES.md

Documents the project's soundness contract: every unsafe block's SAFETY: comment must constitute an informal proof citing the Rust Reference or stdlib documentation; soundness must hold across all Rust versions from MSRV forward; rare exceptions (lines 50-77) require an official Rust-team commitment and an active formalisation effort. The 100% safety-comment coverage goal is tracked in #429 — see FINDING-2.

build.rs

Build script. Reads Cargo.toml, parses [package.metadata.build-rs], runs rustc --version, emits --cfg directives gating MSRV-sensitive features. See FINDING-4.

src/byte_slice.rs

Byte-slice conversion primitives. Contains one of the #[cfg(kani)] formal-verification blocks: split/bounds operations are spot-checked with bounded Kani proofs (kani::proof / kani::any / kani::assume), supporting unsafe-tested for the byte-slice conversion surface.

src/impls.rs

Unsafe trait impls for every standard type the crate supports: primitives, NonZero*, Option<...>, tuples, arrays, function-pointer types (fn and extern "C" fn), and (under feature = "simd") the x86/x86_64/AArch64/PowerPC SIMD types. The function-pointer impls reference extern "C" fn(...) types but do not call across FFI — the type is the subject of the impl, not a target of invocation. Source of FINDING-1 (the impl_or_verify!-generated test code at lines 1796 and 1807).

src/layout.rs

DstLayout and SizeInfo types describing the layout of (possibly-DST) types for the safe-transmutation machinery. Carries the most substantial Kani-proof block in the crate (#[cfg(kani)] mod near the bottom) — kani::any/kani::assume/kani::proof for layout-arithmetic invariants. Supports unsafe-tested for the layout-reasoning surface.

src/lib.rs

src/lib.rs, line 220-287

// Sometimes we want to use lints which were added after our MSRV.
// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
// this attribute, any unknown lint would cause a CI failure when testing with
// our MSRV.
#![allow(unknown_lints, non_local_definitions, unreachable_patterns)]
#![deny(renamed_and_removed_lints)]
#![deny(
    anonymous_parameters,
    deprecated_in_future,
    late_bound_lifetime_arguments,
    missing_copy_implementations,
    missing_debug_implementations,
    missing_docs,
    path_statements,
    patterns_in_fns_without_body,
    rust_2018_idioms,
    trivial_numeric_casts,
    unreachable_pub,
    unsafe_op_in_unsafe_fn,
    unused_extern_crates,
    // We intentionally choose not to deny `unused_qualifications`. When items
    // are added to the prelude (e.g., `core::mem::size_of`), this has the
    // consequence of making some uses trigger this lint on the latest toolchain
    // (e.g., `mem::size_of`), but fixing it (e.g. by replacing with `size_of`)
    // does not work on older toolchains.
    //
    // We tested a more complicated fix in #1413, but ultimately decided that,
    // since this lint is just a minor style lint, the complexity isn't worth it
    // - it's fine to occasionally have unused qualifications slip through,
    // especially since these do not affect our user-facing API in any way.
    variant_size_differences
)]
#![cfg_attr(
    __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS,
    deny(fuzzy_provenance_casts, lossy_provenance_casts)
)]
#![deny(
    clippy::all,
    clippy::alloc_instead_of_core,
    clippy::arithmetic_side_effects,
    clippy::as_underscore,
    clippy::assertions_on_result_states,
    clippy::as_conversions,
    clippy::correctness,
    clippy::dbg_macro,
    clippy::decimal_literal_representation,
    clippy::double_must_use,
    clippy::get_unwrap,
    clippy::indexing_slicing,
    clippy::missing_inline_in_public_items,
    clippy::missing_safety_doc,
    clippy::multiple_unsafe_ops_per_block,
    clippy::must_use_candidate,
    clippy::must_use_unit,
    clippy::obfuscated_if_else,
    clippy::perf,
    clippy::print_stdout,
    clippy::return_self_not_must_use,
    clippy::std_instead_of_core,
    clippy::style,
    clippy::suspicious,
    clippy::todo,
    clippy::undocumented_unsafe_blocks,
    clippy::unimplemented,
    clippy::unnested_or_patterns,
    clippy::unwrap_used,
    clippy::use_debug
)]

Crate lint posture: deny(unsafe_op_in_unsafe_fn), deny(clippy::undocumented_unsafe_blocks), deny(clippy::missing_safety_doc), deny(clippy::multiple_unsafe_ops_per_block), plus a tight clippy correctness/style set (indexing_slicing, arithmetic_side_effects, unwrap_used, as_conversions, alloc_instead_of_core, std_instead_of_core, ...). Test code relaxes panic-related lints via cfg_attr(any(test, kani), allow(...)). The lint set is the mechanical backstop behind the soundness discipline documented in POLICIES.md.

src/pointer

Core pointer-with-invariants machinery. Ptr<'a, T, I> (in ptr.rs) carries compile-time aliasing/alignment/validity invariants in the type parameter I; inner.rs holds the unsafe constructors and the invariant lattice; transmute.rs implements layout-based cast operations; invariant.rs defines the marker types. This is where the bulk of zerocopy's soundness reasoning lives. Indirectly supports impl-datastructure being false: Ptr is a typed wrapper, not a container.

src/util

Macro-driven unsafe-impl generators. src/util/macros.rs defines unsafe_impl! (the central unsafe trait-impl macro, ~22 arms enforcing a __unsafe() token that anchors safety documentation), impl_for_transmute_from! (bounds-based soundness via transmutation equivalence), unsafe_impl_for_power_set! (combinatorial impls for tuple/fn types), and impl_or_verify! (test-time differential verification — the source of FINDING-1). src/util/macro_util.rs houses the lower-level transmutation primitives used by the macros.