cargo / ring / audit
cargo : ring @ 0.17.14
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-safehas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignuses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

ring 0.17.14 is the BoringSSL-backed crypto library used by rustls (AEAD, digests, HMAC/HKDF, ECDH, ECDSA, Ed25519, RSA). Scoped audit: supply-chain integrity confirmed against the tagged commit and the build script touches no network and writes only to OUT_DIR. One low-severity finding: 17 pre-assembled Windows object files ship in pregenerated/. Cryptographic correctness and constant-timeness of the vendored C/asm were out of scope.

Report

Subject

ring 0.17.14 is the foundational cryptography library used by rustls and much of the Rust TLS ecosystem. It exposes AEAD (AES-GCM, ChaCha20-Poly1305), digests (SHA-1/2), HMAC, HKDF, PBKDF2, ECDH (P-256, P-384, X25519), ECDSA, Ed25519, and RSA, plus a SystemRandom RNG. The public Rust API (aead, agreement, digest, hkdf, hmac, pbkdf2, rand, rsa, signature, error) is a thin, mostly no_std safe layer over primitives that are implemented in vendored C and perlasm-generated assembly derived from BoringSSL. The crate is ~25K lines of Rust over 153 files, with a 1044-line build.rs and a large crypto/, pregenerated/, include/, and third_party/fiat/ tree of C, headers, and assembly.

Methodology

Tools: openvet 0.6.0, git, diff, ripgrep, python3, the file utility. This is a scoped audit. I verified supply-chain integrity, read Cargo.toml, build.rs, and lib.rs in full, read the FFI boundary modules (aead/{aes,gcm,chacha}/ffi.rs, representative), and surveyed the rest of src by grep for capability markers (network, filesystem, exec, environment, concurrency, unsafe, crypto, RNG). The vendored C and assembly were enumerated and their provenance noted, but not read line by line.

VCS state: vcs/.git is checked out at 2723abbca9e83347d82b056d5b239c6604f786df (tag 0.17.14), matching .cargo_vcs_info.json (marked dirty, which is the normal cargo-package marker). diff -rq contents vcs shows the only content difference is Cargo.toml (cargo normalization); everything else is either present only in vcs (dev files excluded by the manifest include list: .github, bench, cavp, doc, mk, test-vector .txt files, some unused perlasm) or only in contents (pregenerated/, Cargo.lock, Cargo.toml.orig, .cargo_vcs_info.json). No published source file diverges from the tracked source.

Scope. The cryptographic correctness and constant-time behaviour of the BoringSSL-derived C and assembly require dedicated cryptographic review and were not evaluated. The following claims are therefore left unasserted and must not be read as either satisfied or violated: !crypto-impl-safe, !crypto-impl-correct, !unsafe-safe, !unsafe-documented, !unsafe-minimal, !parser-impl-safe, !algorithm-impl-correct. The 17 pre-assembled object files were identified and their build role traced, but not disassembled and not regenerated from source, so !binaries-safe and !binaries-reproducible are also unasserted. This audit covers supply-chain integrity, the capability surface, build-time execution, the shape of the Rust/asm FFI boundary, and dependency enumeration.

Results

The published tree is byte-identical to the tagged VCS source apart from the normalized Cargo.toml and the expected packaging additions, and contains no obfuscated payloads, embedded base64, telemetry, or suspicious endpoints; the one long string in src is a test-vector file. The build script reads only Cargo-provided environment, performs no network access, and writes only inside OUT_DIR, supporting is-benign, build-exec-safe, build-exec-no-write-out, build-exec-no-network, build-exec-deterministic, and build-exec-minimal.

build.rs compiles the vendored C and assembly with cc into static libraries, which is build-time code execution (has-build-exec); there is no install-time execution (has-install-exec). The packaged-build path invokes only the C compiler, while perl and nasm run only on the local .git-present developer path. The crate ships 17 pre-assembled COFF object files in pregenerated/ for the Windows x86/x86_64 targets, linked directly by build.rs on those targets; this is the basis for has-binaries and is detailed in the single finding (low, quality). The build does not access the network but it does spawn the C compiler; no exec capability exists at runtime (uses-exec).

The crate implements cryptographic primitives, so uses-crypto, impl-crypto, and impl-algorithm hold; the untrusted-based DER/PKCS#8 decoding makes it a parser implementer (impl-parser). It is not a protocol, interpreter, JIT, or concurrency-primitive implementer (impl-protocol, impl-interpreter, impl-jit, impl-concurrency, impl-datastructure). The Rust surface contains 198 unsafe occurrences and 37 extern "C" declarations concentrated at the FFI boundary, so uses-unsafe holds; the annotated aes/ffi.rs boundary shows the consistent marshalling pattern (length and block-alignment validated in Rust, nonzero block count derived, Overlapping-bounded pointers passed with a documented SAFETY contract). SystemRandom plus the default dev_urandom_fallback feature reads /dev/urandom, so uses-filesystem holds; the once_cell polyfill uses atomics for one-time initialization, supporting uses-concurrency. No network or environment access exists in the runtime code (uses-network, uses-environment), and there is no JIT or interpreter (uses-jit, uses-interpreter).

Testing: the crate has in-crate #[test] unit tests (has-unit-tests) and a tests/ directory of integration tests driven by checked-in test-vector files (has-integration-tests). There is no fuzz/ directory and no proptest/quickcheck usage in the published crate (has-fuzz-tests, has-property-tests). Dependencies are few: cc (build), cfg-if, getrandom, untrusted, and target-gated libc and windows-sys; each is described in the dependency list.

Conclusion

ring 0.17.14 is a no_std-capable cryptography library whose Rust layer wraps vendored BoringSSL-derived C and assembly. Supply-chain integrity checks out: the published source matches the tagged commit, the build script touches no network and writes only to OUT_DIR, and no malicious patterns were found. The audit produced one low-severity quality finding noting the 17 pre-assembled Windows object files shipped in pregenerated/. The cryptographic correctness and constant-time behaviour of the C and assembly, the soundness of the 198 unsafe blocks beyond the reviewed FFI-boundary pattern, and byte-equivalence of the prebuilt objects were out of scope and are explicitly left unasserted.

Findings(1)

FINDING-1 quality low

Ships 17 pre-assembled COFF object files for Windows x86/x86_64

pregenerated/ ships 17 pre-assembled object files alongside the assembly source they derive from:

pregenerated/aes-gcm-avx2-x86_64-nasm.o
pregenerated/aesni-gcm-x86_64-nasm.o
pregenerated/aesni-x86-win32n.o
pregenerated/aesni-x86_64-nasm.o
pregenerated/chacha-x86-win32n.o
pregenerated/chacha-x86_64-nasm.o
pregenerated/chacha20_poly1305_x86_64-nasm.o
pregenerated/ghash-x86-win32n.o
pregenerated/ghash-x86_64-nasm.o
pregenerated/p256-x86_64-asm-nasm.o
pregenerated/sha256-x86_64-nasm.o
pregenerated/sha512-x86_64-nasm.o
pregenerated/vpaes-x86-win32n.o
pregenerated/vpaes-x86_64-nasm.o
pregenerated/x86-mont-win32n.o
pregenerated/x86_64-mont-nasm.o
pregenerated/x86_64-mont5-nasm.o

file identifies these as intel x86-64 COFF object file (and Win32 COFF for the -win32n variants). They are NASM output for the Windows x86 and x86_64 targets, shipped so that a packaged build does not need an assembler installed. The matching *-nasm.asm / *-win32n.asm text sources are also present in pregenerated/, and build.rs regenerates the whole tree from the vendored perlasm when RING_PREGENERATE_ASM=1 is set (see pregenerate_asm_main and nasm, build.rs:371, 610).

On Windows x86/x86_64 targets build.rs links these objects directly via cc::Build::object rather than assembling (build_c_code selects the obj_srcs branch when asm_target.use_nasm(), build.rs:435-442; linked at build_library, build.rs:529-531). On all other targets the objects are unused and the C/assembly is compiled from source.

This documents has-binaries. Byte-for-byte reproduction of these objects from the perlasm requires running the perl and nasm toolchain and was not performed, so binaries-reproducible is left unasserted. The objects are machine code for the cryptographic primitives and were not disassembled; binaries-safe is therefore also left unasserted (see the Methodology scope note).

Annotations(2)

build.rs

build.rs (1044 lines) compiles the vendored C and assembly that implement ring's cryptographic primitives. fn main (build.rs:268) reads RING_PREGENERATE_ASM; absent that variable it calls ring_build_rs_main, the normal packaged-build path.

What it does:

  • Reads only Cargo-provided environment (OUT_DIR, CARGO_MANIFEST_DIR, CARGO_CFG_TARGET_*, CARGO_PKG_*, DEBUG) plus optional PERL_EXECUTABLE / RING_PREGENERATE_ASM. Every read goes through a local env module that emits cargo:rerun-if-env-changed. No other environment is consulted.
  • Selects per-arch C and asm sources from the static RING_SRCS table (build.rs:54-115) and compiles them with cc::Build into static libraries libring_core_*.a and libring_core_*_test.a written under OUT_DIR (build_library, build.rs:513-549; c.compile, build.rs:539).
  • For a packaged build (no .git) it uses the checked-in pregenerated/ assembly and objects and invokes no external tools beyond the C compiler. Only when .git is present (local development) does it run perl and nasm to regenerate assembly (generate_sources_and_preassemble, build.rs:382; perlasm, build.rs:712; nasm, build.rs:610).
  • Writes only inside OUT_DIR: the static libraries, and generated symbol-prefix headers under OUT_DIR/ring_core_generated/ (generate_prefix_symbols_header, build.rs:809-846, via fs::File::create at build.rs:820 and fs::create_dir_all at build.rs:817). The fs::create_dir(pregenerated) at build.rs:373 runs only under the RING_PREGENERATE_ASM developer path, not in a normal build.
  • Performs no network access of any kind. There is no HTTP client, socket, or download logic.

The two Command::new sites are perl (build.rs:639, developer path only) and the bundled ./target/tools/windows/nasm/nasm (build.rs:622, developer path only); the packaged build path spawns no processes other than the C compiler driven by cc.

Justifies has-build-exec, build-exec-safe, build-exec-no-write-out, build-exec-no-network, build-exec-deterministic, build-exec-minimal, uses-environment, uses-network, uses-exec, has-install-exec.

src/aead/aes/ffi.rs

src/aead/aes/ffi.rs, line 69-110

        let mut r = AES_KEY {
            rd_key: [0u32; 4 * (MAX_ROUNDS + 1)],
            rounds: 0,
        };
        unsafe { f(&mut r, src) };
        r
    }

    pub(super) fn rounds(&self) -> u32 {
        self.rounds
    }
}

// SAFETY:
//  * The function `$name` must read `bits` bits from `user_key`; `bits` will
//    always be a valid AES key length, i.e. a whole number of bytes.
//  * `$name` must set `key.rounds` to the value expected by the corresponding
//    encryption/decryption functions and return 0, or otherwise must return
//    non-zero to indicate failure.
//  * `$name` may inspect CPU features.
//
// In BoringSSL, the C prototypes for these are in
// crypto/fipsmodule/aes/internal.h.
macro_rules! set_encrypt_key {
    ( $name:ident, $key_bytes:expr $(,)? ) => {{
        use crate::bits::BitLength;
        use core::ffi::c_int;
        prefixed_extern! {
            fn $name(user_key: *const u8, bits: BitLength<c_int>, key: *mut AES_KEY) -> c_int;
        }
        $crate::aead::aes::ffi::AES_KEY::new($name, $key_bytes)
    }};
}

macro_rules! encrypt_block {
    ($name:ident, $block:expr, $key:expr) => {{
        use crate::aead::aes::{ffi::AES_KEY, Block};
        prefixed_extern! {
            fn $name(a: &Block, r: *mut Block, key: &AES_KEY);
        }
        $key.encrypt_block($name, $block)
    }};

Representative of the Rust-to-asm FFI boundary. ctr32_encrypt_within (aes/ffi.rs:69) declares the BoringSSL-derived aes_hw_ctr32_encrypt_blocks via prefixed_extern! and marshals an Overlapping<'_> slice into it. Before the call the Rust side reads in_out.len(), returns early on zero length, asserts block alignment with assert_eq!(in_out_len % BLOCK_LEN, 0), and derives a NonZeroUsize block count, so the c::NonZero_size_t argument is always nonzero. Source and destination pointers come from in_out.src() / in_out.dst(), whose ranges are bounded by the Overlapping abstraction. A // SAFETY: comment records the caller obligations (key initialized via set_encrypt_key!, both buffers at least in_out_len bytes).

This is the consistent shape across the FFI modules (aead/{aes,gcm,chacha}/ffi.rs, digest/sha2/ffi.rs, arithmetic/ffi.rs): length and alignment are validated in Rust, then pointers plus a length/count are passed to the primitive. The 198 unsafe occurrences carry 15 // SAFETY: comments concentrated at these boundaries. Soundness and constant-timeness of the underlying assembly were not evaluated (see Methodology scope), so unsafe-safe is not asserted; this annotation supports uses-unsafe and the description of the FFI boundary in the report.