cargo / autocfg / audit
cargo : autocfg @ 1.5.0
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.0 is a dependency-free build-script utility that probes rustc features by invoking the compiler on synthesized snippets. No findings; no unsafe, no binaries, no build script, no network — safe to use as a build dependency.

Report

Subject

autocfg is a Rust library intended to be used from build.rs scripts to detect compiler features by compiling small Rust snippets with rustc and observing whether they succeed. Successful probes are turned into cargo:rustc-cfg=... directives that the consuming crate can gate code on with #[cfg(...)]. It exposes probe primitives for paths, types, traits, expressions, constants, sysroot crates, rustc versions, and raw source, and writes its output to the build script's OUT_DIR. The crate keeps a Rust 1.0 minimum supported version and has no runtime dependencies.

Methodology

The published crate contents were compared against the upstream Git repository at the commit recorded in .cargo_vcs_info.json using diff -r. All source files in src/ (lib.rs, rustc.rs, version.rs, error.rs, tests.rs — 925 lines total) were read in full, along with the five example programs and the integration test suite under tests/ (no_std, rustflags, wrappers, tests — 328 lines, plus a support/mod.rs helper and a wrap_ignored bash script used by the wrapper test). The CI configuration in .github/workflows/ci.yaml was reviewed for test-matrix coverage. The codebase was grepped for unsafe, extern, and network APIs.

Results

The published crate matches the VCS source byte-for-byte under src/, examples/, and tests/; the only differences are cargo's standard Cargo.toml normalisation, the auto-generated Cargo.toml.orig, an included Cargo.lock, and .cargo_vcs_info.json. The crate ships no binary artefacts (justifying has-binaries), declares build = false, has no build.rs, and does not declare a proc-macro library — there is no build-time or install-time code execution path (justifying has-build-exec and has-install-exec).

The library shells out to rustc via std::process::Command for every probe (justifying uses-exec). The argv form is used throughout, no shell is involved, and only program-controlled values reach the command line: the crate name is derived from an FNV-1a hash plus an atomic counter, the output directory is the cargo-supplied OUT_DIR, the target and rustflags come from documented cargo environment variables, and the source snippet is passed via stdin rather than the command line (justifying exec-safe). The library reads only documented cargo and rustc environment variables — OUT_DIR, TARGET, HOST, RUSTC, RUSTFLAGS, CARGO_ENCODED_RUSTFLAGS, CARGO_TARGET_DIR, RUSTC_WRAPPER, and RUSTC_WORKSPACE_WRAPPER (justifying uses-environment and environment-safe). Filesystem use is limited to verifying that OUT_DIR is a writable directory, asking rustc to emit its .ll output there, and then deleting that output file; no path is composed from external input apart from the cargo-supplied directory itself (justifying uses-filesystem and filesystem-safe). A single AtomicUsize counter is used to generate unique probe crate names; no threads are spawned and no shared mutable state crosses threads beyond that counter (justifying uses-concurrency, concurrency-safe, and concurrency-documented).

The codebase was reviewed for cryptographic operations, network use, raw pointers, unsafe blocks, FFI, JIT, interpreters, parsers, protocols, data structures, and algorithms, and none were found, justifying uses-crypto, uses-network, uses-unsafe, uses-jit, uses-interpreter, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm, and impl-concurrency. The new_uuid function uses a HashSet iteration and FNV-1a only as a probabilistic unique-name source, not as cryptography, and is documented as such.

The documentation contains an explicit caution that probe inputs are not sanitised and that a caller may inject unintended source code into the synthesised probe. Since the inputs originate in the consumer's own build script, this is a misuse hazard rather than an attack surface, and no finding was raised. The crate carries integration tests for paths, traits, types, expressions, constants, no_std, edition handling, rustflags propagation, and rustc wrappers (justifying has-integration-tests), plus four unit tests for version comparison and target-directory detection (justifying has-unit-tests). CI exercises the matrix from Rust 1.0.0 through 1.85.0, stable, beta, and nightly, including a thumbv6m-none-eabi no_std job and a missing-target job. No fuzz or property tests are present, which is reasonable for a build utility that does not parse adversarial input (justifying has-fuzz-tests and has-property-tests).

No malicious behaviour, telemetry, or supply-chain anomalies were observed (justifying is-benign).

Conclusion

autocfg is a small, dependency-free, well-tested build-utility crate whose behaviour is fully accounted for by reading its source. No findings were raised. It is appropriate for use as a build dependency.

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 spawns rustc with Command, piping a synthesized Rust snippet via stdin, justifying uses-exec. The command form is argv (no shell), and arguments are crate-internal constants (crate name from FNV hash, paths from OUT_DIR) or controlled by the consumer's own build script, justifying 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 CARGO_ENCODED_RUSTFLAGS, RUSTFLAGS, HOST, and CARGO_TARGET_DIR to mirror cargo's flag-propagation logic for cross-compilation probes. Only documented cargo and rustc environment variables are consulted, justifying uses-environment and environment-safe.

src/lib.rs, line 195-198

        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"));
        }

OUT_DIR is checked to exist and be writable before use. Subsequent file operations write only into this directory (rustc --out-dir) and immediately remove the .ll artefact, justifying uses-filesystem and filesystem-safe.

src/lib.rs, line 297-304

    /// Returns a new (hopefully unique) crate name for probes.
    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)
    }

An AtomicUsize counter combined with a per-instance UUID generates unique probe crate names, justifying uses-concurrency and concurrency-safe. Threads are never spawned by this crate; the atomic only protects against concurrent callers issuing probes.

src/lib.rs, line 38-44

//! ## Caution
//!
//! Many of the probing methods of `AutoCfg` document the particular template they
//! use, **subject to change**. The inputs are not validated to make sure they are
//! semantically correct for their expected use, so it's _possible_ to escape and
//! inject something unintended. However, such abuse is unsupported and will not
//! be considered when making changes to the templates.

The crate documents that probe-method inputs are not validated and may inject unintended code into the synthesized rustc snippet. Treated as a build-time misuse hazard, not a security vulnerability, because the inputs originate in the consumer's own build script.

src/rustc.rs

src/rustc.rs, line 17-25

    pub fn new() -> Self {
        Rustc {
            rustc: env::var_os("RUSTC")
                .unwrap_or_else(|| "rustc".into())
                .into(),
            rustc_wrapper: get_rustc_wrapper(false),
            rustc_workspace_wrapper: get_rustc_wrapper(true),
        }
    }

Reads RUSTC, RUSTC_WRAPPER, RUSTC_WORKSPACE_WRAPPER from the environment to discover the compiler binary and wrappers, all of which are documented cargo conventions.