cargo : zerocopy-derive @ 0.8.48
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

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

zerocopy-derive 0.8.48 is a procedural-macro crate emitting derives for zerocopy traits. The proc-macro runtime is a pure AST-to-TokenStream function with a single documented unsafe fn whose body is safe Rust. No findings; safe to deploy.

Report

Subject

zerocopy-derive is a procedural-macro crate that provides custom derives for traits defined in zerocopy (KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned, ByteHash, ByteEq, SplitAt, plus the deprecated FromZeroes/AsBytes aliases). It contains no runtime functionality; everything it produces is Rust source code that is compiled into the downstream consumer's crate.

Methodology

The published crate contents were compared against the upstream Git repository (commit recorded in .cargo_vcs_info.json) using diff -r. All Rust files under src/ (lib.rs, util.rs, repr.rs, and the five modules in src/derive/) were read in full. The test surface was surveyed (33 integration test files under tests/, a UI/trybuild test harness, and ~960 lines of in-source expansion tests under src/output_tests/ covering 34 expected-output files). The source was searched for uses of unsafe, std::, network, filesystem, process, environment, and other capabilities. Dependency declarations in Cargo.toml were reviewed. No code was compiled or executed during the audit.

Results

The published crate matches the upstream repository: source files and tests are byte-identical except for tests/enum_from_bytes.rs, which is deliberately excluded via the manifest's exclude list. Cargo's normal additions (.cargo_vcs_info.json, Cargo.lock, the normalised Cargo.toml, and Cargo.toml.orig) are present and Cargo.toml.orig matches the upstream Cargo.toml byte-for-byte.

The crate ships no binary artefacts (justifying has-binaries) and no build.rs. The [lib] proc-macro = true declaration alone justifies has-build-exec: proc macros execute on every downstream consumer's machine at compile time. No Command::new, network client, or filesystem write reachable from a proc-macro entry point was found, supporting build-exec-no-network and build-exec-no-write-out; the proc-macro logic is a pure function from a DeriveInput to a TokenStream, justifying build-exec-deterministic. The only side-effecting code (std::env::var("ZEROCOPY_BLESS") and std::fs::write) lives in src/output_tests/mod.rs, which is #[cfg(test)]-gated and not reachable from the proc-macro entry points, justifying uses-filesystem, uses-environment, and uses-exec.

Runtime dependencies are limited to proc-macro2, quote, and syn — the standard procedural-macro stack — which justifies uses-network, uses-concurrency, uses-crypto, and the implementation claims (impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm, impl-concurrency): all parsing is delegated to syn, and the crate's logic walks the resulting AST and emits token streams.

Inside the proc-macro's runtime code there is exactly one unsafe { ... } block (src/derive/try_from_bytes.rs:683), which calls the unsafe fn gen_trivial_is_bit_valid_unchecked. That function's body is itself safe Rust — it returns a TokenStream. The unsafe keyword expresses a soundness contract on the emitted code, namely that the surrounding type is in fact FromBytes; the caller establishes this via the could_be_from_bytes check immediately above. The contract is documented with a # Safety doc comment, supporting unsafe-safe, unsafe-documented and unsafe-minimal. The crate also emits a large quantity of unsafe impl and unsafe { ... } tokens for the downstream consumer to compile; the correctness of those is exercised by the 33 integration tests and the in-source expansion tests, which compare the generated token streams against golden files (justifying unsafe-tested). No behaviour suggesting malicious intent — typosquatting, payload obfuscation, target-dependent payloads, data exfiltration — was observed, justifying is-benign.

No findings were recorded.

The crate ships an integration test directory under tests/ (justifying has-integration-tests); in-source #[cfg(test)] modules cover the macro internals (justifying has-unit-tests). There is no proptest/quickcheck use and no fuzz/ harness in the published archive, justifying has-property-tests and has-fuzz-tests as false.

The macro expansion runs in the proc-macro sandbox with no I/O, no environment access, and no FFI, justifying build-exec-minimal, build-exec-safe, uses-jit, uses-interpreter, and has-install-exec.

Conclusion

zerocopy-derive is a focused procedural-macro crate. Its proc-macro runtime is essentially a pure function from a parsed AST to a token stream; the single real unsafe usage is trivial and documented. The audit surface is the emitted code, which is well covered by the package's expansion tests and by the downstream zerocopy test suite. No security, safety, correctness, or quality issues warranting a finding were identified at this version.

Findings

No findings.

Annotations(4)

Cargo.toml

Cargo-normalised manifest. Compared against Cargo.toml.orig which matches the upstream repository byte-for-byte.

Cargo.toml, line 36-39

[lib]
name = "zerocopy_derive"
path = "src/lib.rs"
proc-macro = true

[lib] proc-macro = true — this crate is purely a procedural-macro library.

src/derive/try_from_bytes.rs

src/derive/try_from_bytes.rs, line 683-683

        (None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(ctx) },

Sole unsafe { ... } block in the proc-macro's runtime code. It invokes gen_trivial_is_bit_valid_unchecked, an unsafe fn whose body is itself safe Rust (it returns a token stream). The unsafe contract is a soundness obligation on the emitted code: it may only be invoked when the type would already satisfy FromBytes, which is enforced upstream by could_be_from_bytes. Justifies uses-unsafe.

src/derive/try_from_bytes.rs, line 744-763

/// # Safety
///
/// All initialized bit patterns must be valid for `Self`.
unsafe fn gen_trivial_is_bit_valid_unchecked(ctx: &Ctx) -> proc_macro2::TokenStream {
    let zerocopy_crate = &ctx.zerocopy_crate;
    let core = ctx.core_path();
    quote!(
        // SAFETY: The caller of `gen_trivial_is_bit_valid_unchecked` has
        // promised that all initialized bit patterns are valid for `Self`.
        #[inline(always)]
        fn is_bit_valid<___ZcAlignment>(
            _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
        ) -> #core::primitive::bool
        where
            ___ZcAlignment: #zerocopy_crate::invariant::Alignment,
        {
            true
        }
    )
}

unsafe fn gen_trivial_is_bit_valid_unchecked carries a # Safety doc explaining its invariant ("All initialized bit patterns must be valid for Self"). The function body is safe; the unsafety is a contract on emitted code soundness. Supports unsafe-documented.

src/lib.rs

src/lib.rs, line 81-98

        #[proc_macro_derive($trait, attributes(zerocopy))]
        pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
            let ast = syn::parse_macro_input!(ts as DeriveInput);
            let ctx = match Ctx::try_from_derive_input(ast) {
                Ok(ctx) => ctx,
                Err(e) => return e.into_compile_error().into(),
            };
            let ts = $inner(&ctx, Trait::$trait).into_ts();
            // We wrap in `const_block` as a backstop in case any derive fails
            // to wrap its output in `const_block` (and thus fails to annotate)
            // with the full set of `#[allow(...)]` attributes).
            let ts = const_block([Some(ts)]);
            #[cfg(test)]
            crate::util::testutil::check_hygiene(ts.clone());
            ts.into()
        }
    };
}

Proc-macro entry: each derive! invocation produces a #[proc_macro_derive] function that parses the input via syn::parse_macro_input!, dispatches to an inner derive routine, and wraps the result in const_block to apply lint suppressions to generated code. Justifies has-build-exec (proc macros run on every downstream consumer's machine at compile time).

src/output_tests/mod.rs

src/output_tests/mod.rs, line 81-86

            if std::env::var("ZEROCOPY_BLESS").is_ok() {
                let path = Path::new(env!("CARGO_MANIFEST_DIR"))
                    .join("src/output_tests")
                    .join($path);
                std::fs::write(&path, &actual).expect("failed to bless output");
            } else {

Filesystem and environment access live inside the #[cfg(test)]-gated output_tests module: when ZEROCOPY_BLESS is set, expected-output files are overwritten. This does not ship to consumers and runs only in the package's own test harness.