cargo / autocfg / audit
cargo : autocfg @ 1.5.1
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

concurrency-documentedconcurrency-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-benignuses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

autocfg 1.5.1 is a build-script helper that probes the host rustc for compiler feature support by spawning it as a subprocess. No findings. No unsafe code, no external dependencies, no network access.

Report

Subject

autocfg is a build-script helper library that detects Rust compiler features at build time. Downstream crates add it as a [build-dependencies] entry and call it from their build.rs to determine whether the host rustc supports a given type, trait, path, expression, or constant. Detection works by writing a small Rust source snippet to rustc's stdin and observing the exit status; when a probe succeeds, the library emits a cargo:rustc-cfg=has_* line to stdout. The library has no runtime presence: it executes only during build-script invocation.

Methodology

The published crate at sha256:f2032f911046... was compared against the upstream Git repository at the commit recorded in .cargo_vcs_info.json using diff -rq. All five source files in contents/src/ (925 lines total) were read in full. The four integration test files in contents/tests/ and the tests/support/mod.rs helper were also read in full. Grep surveys were run for unsafe, extern "C", network APIs, filesystem APIs, process execution, environment variables, crypto, RNG, and concurrency primitives before the line-by-line read. Tools used: openvet 0.6.0, diff, grep, file, wc.

Results

The diff between contents/ and vcs/ shows no source-file divergence. The only differences are Cargo's manifest normalisation (reordering, expansion of implicit fields) and the presence of Cargo.lock and .cargo_vcs_info.json in the published archive but not in VCS. The crate ships no binary artefacts (has-binaries) and has no build.rs (has-build-exec). [lib] proc-macro = true is absent. The crate is itself a pure library with no install hooks (has-install-exec).

The codebase contains zero unsafe blocks (uses-unsafe). There is no FFI, no network access (uses-network), no JIT (uses-jit), no embedded interpreter (uses-interpreter), and no cryptographic operations (uses-crypto). The crate implements no algorithms beyond a FNV-1a hash used only to generate unique probe crate names, no parsers, no data structures, no concurrency primitives, and no protocols (impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm, impl-concurrency).

uses-exec is true: the core mechanism is spawning rustc via std::process::Command. Commands are built in argv form with no shell involvement; the rustc binary path originates from the RUSTC environment variable (set by Cargo) or the default "rustc" on PATH. Probe source code is piped to rustc's stdin, not passed as command-line arguments. The crate's own documentation notes that probe inputs are not validated and that injection of unintended content via the probe APIs is possible but unsupported. In practice the caller controls probe content and the consequence of injection is compilation of an unexpected snippet inside a subprocess with the caller's own rustc, not remote execution. exec-safe is assessed true.

uses-filesystem is true: fs::metadata validates that OUT_DIR is a directory before use, and fs::remove_file cleans up .ll output files from OUT_DIR after each successful probe (best-effort, errors ignored). All filesystem access is confined to OUT_DIR. filesystem-safe is true.

uses-environment is true: the crate reads OUT_DIR, TARGET, HOST, RUSTC, RUSTFLAGS, CARGO_ENCODED_RUSTFLAGS, RUSTC_WRAPPER, RUSTC_WORKSPACE_WRAPPER, and CARGO_TARGET_DIR. All are standard Cargo build-script variables; the crate does not enumerate the environment or leak values. environment-safe is true.

uses-concurrency is true in a narrow sense: a static AtomicUsize counter (initialised with the deprecated ATOMIC_USIZE_INIT constant, wrapped in #[allow(deprecated)] to maintain Rust 1.0 compatibility) generates monotonically increasing suffixes for probe crate names. Ordering::Relaxed is correct here because uniqueness, not synchronisation, is the goal. No threads are spawned by the library. The AutoCfg type does not document its Send/Sync status. concurrency-safe is true; concurrency-documented is false.

The crate has 22 #[test] cases: 4 unit tests in src/tests.rs covering Version comparison and dir_contains_target, and integration tests in tests/ covering probing paths, types, traits, expressions, constants, no-std mode, RUSTFLAGS handling, wrapper behaviour, edition probing, and probe cleanup. has-unit-tests and has-integration-tests are true. There are no fuzz or property tests (has-fuzz-tests, has-property-tests false). The new_uuid FNV function and the mangle helper are not separately unit-tested, but both are trivially correct by inspection.

No findings were recorded. is-benign is true: no obfuscated code, no base64 blobs, no suspicious network endpoints, no time-based or environment-triggered payload.

Conclusion

autocfg 1.5.1 is a small, straightforward build-script helper. The code is safe, readable, and well-documented. The absence of unsafe, FFI, network access, and external dependencies makes the attack surface minimal. The only process it spawns is rustc, whose path is supplied by Cargo.

Findings

No findings.

Annotations(2)

src/lib.rs

src/lib.rs, line 306-348

    fn probe_fmt<'a>(&self, source: Arguments<'a>) -> Result<(), Error> {
        let crate_name = self.new_crate_name();
        let mut command = self.rustc.command();
        command
            .arg("--crate-name")
            .arg(&crate_name)
            .arg("--crate-type=lib")
            .arg("--out-dir")
            .arg(&self.out_dir)
            .arg("--emit=llvm-ir");

        if let Some(edition) = self.edition.as_ref() {
            command.arg("--edition").arg(edition);
        }

        if let Some(target) = self.target.as_ref() {
            command.arg("--target").arg(target);
        }

        command.args(&self.rustflags);

        command.arg("-").stdin(Stdio::piped());
        let mut child = try!(command.spawn().map_err(error::from_io));
        let mut stdin = child.stdin.take().expect("rustc stdin");

        try!(stdin.write_fmt(source).map_err(error::from_io));
        drop(stdin);

        match child.wait() {
            Ok(status) if status.success() => {
                // Try to remove the output file so it doesn't look like a build product for
                // systems like bazel -- but this is best-effort, so we can ignore failure.
                // The probe itself is already considered successful at this point.
                let mut file = self.out_dir.join(crate_name);
                file.set_extension("ll");
                let _ = fs::remove_file(file);

                Ok(())
            }
            Ok(status) => Err(error::from_exit(status)),
            Err(error) => Err(error::from_io(error)),
        }
    }

probe_fmt builds a Command in argv form, appending --crate-name, --crate-type=lib, --out-dir, --emit=llvm-ir, optional --edition and --target, and the caller's rustflags. The probe source code is written to rustc's stdin, not passed as a command-line argument. The rustc binary path comes from the RUSTC environment variable (set by Cargo) or defaults to "rustc" from PATH. No shell is involved. Justifies uses-exec and exec-safe.

src/lib.rs, line 579-618

fn rustflags(target: &Option<OsString>, dir: &Path) -> Vec<String> {
    // Starting with rust-lang/cargo#9601, shipped in Rust 1.55, Cargo always sets
    // CARGO_ENCODED_RUSTFLAGS for any host/target build script invocation. This
    // includes any source of flags, whether from the environment, toml config, or
    // whatever may come in the future. The value is either an empty string, or a
    // list of arguments separated by the ASCII unit separator (US), 0x1f.
    if let Ok(a) = env::var("CARGO_ENCODED_RUSTFLAGS") {
        return if a.is_empty() {
            Vec::new()
        } else {
            a.split('\x1f').map(str::to_string).collect()
        };
    }

    // Otherwise, we have to take a more heuristic approach, and we don't
    // support values from toml config at all.
    //
    // Cargo only applies RUSTFLAGS for building TARGET artifact in
    // cross-compilation environment. Sadly, we don't have a way to detect
    // when we're building HOST artifact in a cross-compilation environment,
    // so for now we only apply RUSTFLAGS when cross-compiling an artifact.
    //
    // See https://github.com/cuviper/autocfg/pull/10#issuecomment-527575030.
    if *target != env::var_os("HOST")
        || dir_contains_target(target, dir, env::var_os("CARGO_TARGET_DIR"))
    {
        if let Ok(rustflags) = env::var("RUSTFLAGS") {
            // This is meant to match how cargo handles the RUSTFLAGS environment variable.
            // See https://github.com/rust-lang/cargo/blob/69aea5b6f69add7c51cca939a79644080c0b0ba0/src/cargo/core/compiler/build_context/target_info.rs#L434-L441
            return rustflags
                .split(' ')
                .map(str::trim)
                .filter(|s| !s.is_empty())
                .map(str::to_string)
                .collect();
        }
    }

    Vec::new()
}

Reads the following environment variables: OUT_DIR (required, set by Cargo), TARGET and HOST (set by Cargo), RUSTC (set by Cargo), RUSTFLAGS and CARGO_ENCODED_RUSTFLAGS (set by Cargo or user), RUSTC_WRAPPER and RUSTC_WORKSPACE_WRAPPER (set by Cargo), CARGO_TARGET_DIR (set by Cargo). All are standard Cargo build-script variables; none enumerate or exfiltrate the environment. Justifies uses-environment and environment-safe.

src/lib.rs, line 194-198

        let dir = dir.into();
        let meta = try!(fs::metadata(&dir).map_err(error::from_io));
        if !meta.is_dir() || meta.permissions().readonly() {
            return Err(error::from_str("output path is not a writable directory"));
        }

fs::metadata checks that OUT_DIR is a writable directory before use. fs::remove_file is called on the probe output file (an .ll file within OUT_DIR) after a successful probe; failures are silently ignored. All filesystem access is confined to the OUT_DIR path provided by Cargo. Justifies uses-filesystem and filesystem-safe.

src/lib.rs, line 298-303

    fn new_crate_name(&self) -> String {
        #[allow(deprecated)]
        static ID: AtomicUsize = ATOMIC_USIZE_INIT;

        let id = ID.fetch_add(1, Ordering::Relaxed);
        format!("autocfg_{:016x}_{}", self.uuid, id)

Uses AtomicUsize with Ordering::Relaxed to generate monotonically increasing probe IDs. Initialised with the deprecated ATOMIC_USIZE_INIT constant (suppressed with #[allow(deprecated)]) to maintain Rust 1.0 compatibility. The relaxed ordering is correct here: the counter just needs to be unique within a process, not to synchronise other memory accesses. No threads are spawned. Justifies uses-concurrency and concurrency-safe. Thread-safety of AutoCfg itself is not documented in its public API; justifies concurrency-documented = false.

src/lib.rs, line 621-636

///
/// This attempts to be random, within the constraints of Rust 1.0 and no dependencies.
fn new_uuid() -> u64 {
    const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
    const FNV_PRIME: u64 = 0x100_0000_01b3;

    // This set should have an actual random hasher.
    let set: std::collections::HashSet<u64> = (0..256).collect();

    // Feed the `HashSet`-shuffled order into FNV-1a.
    let mut hash: u64 = FNV_OFFSET_BASIS;
    for x in set {
        hash = (hash ^ x).wrapping_mul(FNV_PRIME);
    }
    hash
}

Generates a pseudo-random u64 by inserting 0..256 into a HashSet<u64> and feeding the iteration order (which is randomised by Rust's default HashMap/HashSet randomisation since 1.7) through FNV-1a. The comment acknowledges this is not a cryptographic RNG. The purpose is only to make probe crate names unique across parallel invocations of different build scripts, not for any security-sensitive purpose. No use of cryptographic operations.

tests

22 unit tests across src/tests.rs (4 tests on Version comparison and dir_contains_target) and tests/ (integration tests covering probe_* methods, no_std, RUSTFLAGS, wrappers, editions, cleanup). wrappers.rs is a harness-free test that also acts as a mock rustc wrapper. Tests exercise the public API end-to-end by spawning real rustc. Justifies has-unit-tests and has-integration-tests.