cargo / blake3 / audit
cargo : blake3 @ 1.8.5
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-safecrypto-impl-correctcrypto-impl-safecrypto-impl-testedcrypto-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

blake3 1.8.5 is the official Rust BLAKE3 implementation with SSE2, SSE4.1, AVX2, AVX-512, NEON, and WASM SIMD backends. No issues found. Unsafe is pervasive in the SIMD paths but systematically documented and bounded by runtime feature detection; all backends are cross-checked against a reference implementation via randomized tests and a Miri smoketest in CI. Hash equality is constant-time. The build script writes outside OUT_DIR on Windows MinGW (clang-cl cleanup), reflected in build-exec-no-write-out.

Report

Subject

blake3 is the official Rust implementation of the BLAKE3 cryptographic hash function, as specified in the BLAKE3 paper by O'Connor, Neves, Aumasson, and Wilcox-O'Hearn and in the C2SP community specification. It implements a Merkle-tree hash with BLAKE2-derived compression, supporting a 256-bit default output, arbitrary-length extended output (XOF), a keyed-hash (MAC) variant, and a key-derivation variant. Multiple SIMD backends are included: a portable Rust implementation, Rust intrinsics for SSE2/SSE4.1/AVX2/WASM SIMD, hand-written assembly for SSE2/SSE4.1/AVX2/AVX-512 on x86-64, and a C intrinsics backend for NEON. CPU feature detection is performed at runtime on x86/x86_64 via the cpufeatures crate. The public API exposes hash, keyed_hash, derive_key, Hasher (incremental), and OutputReader (seekable XOF), plus a hazmat module for direct tree manipulation used by applications like Bao.

Methodology

The published crate was compared against the upstream Git repository at the commit recorded in .cargo_vcs_info.json using diff -rq. All 18 .rs files in src/ were read in full (~8268 LOC total), along with build.rs, the c/ directory listing, and Cargo.toml.orig. The source was surveyed with grep for network, filesystem, exec, environment-variable, concurrency, and RNG usage patterns before reading in detail. CI configuration was examined in vcs/.github/workflows/ci.yml. Tool versions used: openvet 0.6.0, diff (macOS), grep (BSD), file (macOS).

Results

The diff -rq comparison shows that source files in contents/src/ match the VCS byte-for-byte. The only differences are: Cargo.toml (cargo normalisation), Cargo.toml.orig and Cargo.lock present only in the published crate, and directories (b3sum/, reference_impl/, test_vectors/, tools/) present only in the VCS root and not in scope for this audit.

No binary artifacts are present in the published crate (has-binaries). The media/ directory contains SVG files only. The c/ directory holds C and assembly source files compiled by build.rs at build time; these are text files.

The build script compiles C and assembly SIMD backends via cc::Build and emits cargo:rustc-cfg=blake3_* directives to select which Rust module is compiled. It makes no network requests (build-exec-no-network). It reads environment variables in the standard CARGO_* / CC / CFLAGS set (build-exec-deterministic). One write outside OUT_DIR occurs: on Windows MinGW targets, lines 396-400 call std::fs::remove_file on four .asm artifacts generated in the crate root by clang-cl. The deletions are best-effort (let _ = remove_file(...)) and are a documented cleanup for a known clang-cl quirk. This is the basis for build-exec-no-write-out being false. The unsafe { env::set_var("CFLAGS", flags) } at build.rs:313 modifies the environment to append -fno-lto when CFLAGS is set by the caller; a SAFETY comment documents that the build script is single-threaded.

Approximately 220 occurrences of unsafe were found across the Rust source (excluding test.rs). They are concentrated in four areas: (1) platform.rs, which calls into each SIMD module after verifying feature support at runtime, with inline comments documenting the precondition; (2) the FFI wrappers (ffi_sse2.rs, ffi_sse41.rs, ffi_avx2.rs, ffi_avx512.rs, ffi_neon.rs), which each add an explicit assert! on the output buffer length before passing raw pointers to C; (3) the Rust intrinsics modules (rust_sse2.rs, rust_sse41.rs, rust_avx2.rs, wasm32_simd.rs), which use SIMD intrinsics and transmute for the CVWords→CVBytes conversion, annotated with the x86 little-endian invariant; and (4) io.rs, which wraps memmap2::Mmap::map. All four areas carry safety comments that document the relied-upon invariants. The absence of SAFETY comments does not indicate any block was overlooked: the FFI modules use a function-level "Unsafe because this may only be called on platforms supporting X" convention, and the platform dispatch arms carry per-call comments. All unsafe is necessary for its purpose: SIMD intrinsics, FFI, and mmap. Justifies uses-unsafe, unsafe-safe, unsafe-documented, unsafe-minimal, and unsafe-tested (Miri smoketest in CI; randomized tests cross-check all backends against the portable implementation).

The Hash type implements PartialEq via constant_time_eq::constant_time_eq_32, documented "This implementation is constant-time." Hash deliberately omits Deref and AsRef to prevent implicit coercions that would bypass constant-time comparison. This is correct practice for a MAC output type. Justifies uses-crypto, crypto-safe, impl-crypto, and crypto-impl-safe.

The compression function in portable.rs implements the BLAKE3 G function and round schedule directly from the specification constants. All three modes (regular hash, keyed hash, derive-key) are domain-separated with distinct flag bits. The test suite in src/test.rs cross-checks every backend against the reference implementation (reference_impl dev-dep, a clean re-implementation) for 46 different input lengths from 0 to 100 KiB. The test_fuzz_hasher and test_fuzz_xof tests use a fixed-seed ChaCha8Rng to perform 10,000 randomized update-pattern tests against the reference. A Miri smoketest runs in CI. Justifies crypto-impl-correct and crypto-impl-tested.

The crate uses the filesystem only via the mmap feature (update_mmap, update_mmap_rayon), which opens a path provided by the caller. No path manipulation is performed on the path argument; the OS handles it. Justifies uses-filesystem and filesystem-safe.

No networking, exec, JIT, interpreter, concurrency primitives, or environment-variable access is present in the library code. The rayon feature delegates parallelism entirely to rayon-core::join, which is not implemented by this crate. The crate does not implement a parser, protocol, data structure, algorithm (in the non-crypto sense), JIT, or interpreter. Justifies uses-network, uses-exec, uses-jit, uses-interpreter, uses-concurrency, uses-environment, impl-concurrency, impl-parser, impl-protocol, impl-datastructure, impl-algorithm, impl-interpreter, impl-jit.

There are no install-time hooks (has-install-exec). The package ships no integration tests, property tests, or fuzz test targets (has-integration-tests, has-property-tests, has-fuzz-tests); the randomized tests in test.rs are unit tests run with cargo test, not dedicated fuzz targets.

No malicious code patterns were identified: no obfuscated code, base64 blobs, suspicious network endpoints, telemetry, or time-based behaviour. Justifies is-benign.

No findings were recorded.

Conclusion

The crate's unsafe surface is large due to the SIMD and FFI backends, but it is methodically structured: every unsafe call site has a documented precondition, the preconditions are enforced either by runtime feature detection or by explicit assertions, and the randomized test suite cross-checks all backends against a common reference. The Hash type's constant-time equality is correctly implemented and deliberately protected from accidental bypass. The one area that cannot be fully validated without a hardware AVX-512 target is the assembly implementation in c/, which is authored by the algorithm's designers and is shared with the C library. The build-exec-no-write-out claim is false due to the clang-cl cleanup on Windows; this is a minor correctness trade-off with no security impact.

Findings

No findings.

Annotations(8)

build.rs

Build script that selects and compiles the appropriate SIMD backend (SSE2, SSE4.1, AVX2, AVX-512, NEON) based on target architecture, compiler capabilities, and feature flags. All reads are from standard CARGO_* and target-description environment variables. No network access. Output is limited to the compiled static libraries (linked via cc::Build::compile) and cargo:rustc-cfg= directives passed to the Rust compiler. The unsafe { env::set_var("CFLAGS", flags) } at line 313 modifies the environment only when CFLAGS is already set by the caller, in order to append -fno-lto; a SAFETY comment acknowledges the build script is single-threaded. The deletion of .asm files at lines 396-400 writes outside OUT_DIR on Windows with MinGW, which is the basis for build-exec-no-write-out being false, but the deletions are best-effort (let _ = std::fs::remove_file(...)) and only clean up artifacts left by clang-cl. Justifies has-build-exec, build-exec-safe, build-exec-no-network, build-exec-deterministic, build-exec-minimal.

src/ffi_avx2.rs

FFI wrapper for the AVX2 assembly implementation of blake3_hash_many_avx2. The function is unsafe and only called after avx2_detected() in platform.rs. An explicit assert! on line 21 verifies that out is large enough before passing it as a raw pointer to C, compensating for the absence of bounds checking on the C side. Supports uses-unsafe and unsafe-safe.

src/ffi_neon.rs

NEON FFI wrapper. The function blake3_compress_in_place_portable at line 38 is exported with #[unsafe(no_mangle)] for use by blake3_neon.c as a callback, and casts raw C pointers to Rust references. The cast cv as *mut [u32; 8] is valid because the C caller always passes an 8-element u32 array; the cast block as *const [u8; 64] is valid because BLAKE3 blocks are always 64 bytes. Both casts rely on the correctness of the C callers, which are the SSE/AVX/NEON implementations of the same codebase. Supports uses-unsafe and unsafe-safe.

src/io.rs

The maybe_mmap_file function creates a memory-mapped read-only view of a file (feature-gated behind mmap). The single unsafe call at line 61 is memmap2::Mmap::map(file), which is inherently unsound if a concurrent writer modifies the file — the comment at lines 28-48 acknowledges this and argues that within the crate-private boundary the worst outcome is computing an incorrect hash or receiving SIGBUS, not memory corruption. The copy_wide function reads in 65 KiB chunks (large enough for AVX-512) and handles ErrorKind::Interrupted correctly. Supports uses-filesystem, filesystem-safe, uses-unsafe, and unsafe-safe.

src/lib.rs

src/lib.rs, line 350-373

/// This implementation is constant-time.
impl PartialEq for Hash {
    #[inline]
    fn eq(&self, other: &Hash) -> bool {
        constant_time_eq::constant_time_eq_32(&self.0, &other.0)
    }
}

/// This implementation is constant-time.
impl PartialEq<[u8; OUT_LEN]> for Hash {
    #[inline]
    fn eq(&self, other: &[u8; OUT_LEN]) -> bool {
        constant_time_eq::constant_time_eq_32(&self.0, other)
    }
}

/// This implementation is constant-time if the target is 32 bytes long.
impl PartialEq<[u8]> for Hash {
    #[inline]
    fn eq(&self, other: &[u8]) -> bool {
        constant_time_eq::constant_time_eq(&self.0, other)
    }
}

Core public API: hash, keyed_hash, derive_key, Hasher, and OutputReader. Hash equality is implemented via constant_time_eq::constant_time_eq_32, documented with "This implementation is constant-time." This is important for the MAC (keyed_hash) use case. The Hash type deliberately does not impl Deref or AsRef to prevent accidental bypass of constant-time equality. Justifies impl-crypto and crypto-safe.

src/platform.rs

Platform dispatch layer. Platform::detect() selects the best SIMD backend at runtime using cpufeatures on x86/x86_64. All unsafe calls inside compress_in_place, compress_xof, hash_many, and xof_many are preceded by a comment ("Safe because detect() checked for platform support." or "Assumed to be safe if the feature is on.") confirming that the required CPU instruction set is present. This is the canonical call site for uses-unsafe; the pattern justifies unsafe-documented and unsafe-safe.

src/rust_sse2.rs

src/rust_sse2.rs, line 700-703

        slice = &slice[BLOCK_LEN..];
    }
    *out = unsafe { core::mem::transmute(cv) }; // x86 is little-endian
}

The transmute at line 702 reinterprets CVWords ([u32; 8]) as CVBytes ([u8; 32]). On x86 (little-endian), the byte layout of a [u32; 8] is identical to the little-endian byte encoding of those words, which is what BLAKE3 specifies. The comment "// x86 is little-endian" documents the invariant. The same pattern is used in rust_sse41.rs:691. Justifies unsafe-documented and unsafe-safe.

src/test.rs

Unit test module covering all public and internal APIs. Tests include: comparison against a reference implementation (reference_impl dev-dep) for all three modes (hash, keyed_hash, derive_key) across a sweep of input lengths (0 through 100*CHUNK_LEN); XOF seek/partial-fill correctness; platform-specific compress and hash_many functions tested against the portable implementation; fuzz-style randomized tests (test_fuzz_hasher, test_fuzz_xof) using a fixed-seed ChaCha8Rng for reproducibility; Miri smoketest run in CI. Justifies has-unit-tests, crypto-impl-tested, unsafe-tested, and crypto-impl-correct.