diff --git a/crates/cargo-util/src/paths.rs b/crates/cargo-util/src/paths.rs index 51f0390cfc1..92e5111e7d6 100644 --- a/crates/cargo-util/src/paths.rs +++ b/crates/cargo-util/src/paths.rs @@ -33,6 +33,32 @@ pub fn join_paths>(paths: &[T], env: &str) -> Result { }) } +/// The environment variables that control the temporary directory returned by +/// `std::env::temp_dir()`. +/// +/// Cargo overrides this directory when invoking the compiler or when running +/// build scripts. This is useful when debugging, as it allows more easily +/// inspecting the state the compiler was working with when something went +/// wrong (especially if using `-Csave-temps=yes`). +/// +/// Additionally, this is useful for preventing information leakage and +/// Man-in-the-middle attacks: `/tmp` is world readable and semi-writable by +/// design. This is even an issue on macOS, where the temporary directory is +/// scoped to the current user with `getconf DARWIN_USER_TEMP_DIR`, since you +/// can still have information leakage / MITM to less privileged processes by +/// the same user. +pub fn tmpdir_envvars() -> &'static [&'static str] { + // NOTE: On Windows, there's two environment variables that control the + // temporary directory, `TMP` and `TEMP`, see `GetTempPath2`: + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppath2a + // We set both of them for consistency with programs that only read one. + if cfg!(windows) { + &["TMP", "TEMP"] + } else { + &["TMPDIR"] + } +} + /// Returns the name of the environment variable used for searching for /// dynamic libraries. pub fn dylib_path_envvar() -> &'static str { diff --git a/src/cargo/core/compiler/build_runner/compilation_files.rs b/src/cargo/core/compiler/build_runner/compilation_files.rs index a914dc7056d..07a762de434 100644 --- a/src/cargo/core/compiler/build_runner/compilation_files.rs +++ b/src/cargo/core/compiler/build_runner/compilation_files.rs @@ -389,6 +389,20 @@ impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> { self.build_script_run_dir(unit).join("out") } + /// Returns the directory which build scripts should use for temporary + /// files. + /// `/path/to/target/{debug,release}/build/PKG-HASH/tmp` + pub fn build_script_tmp_dir(&self, unit: &Unit) -> PathBuf { + self.build_script_run_dir(unit).join("tmp") + } + + /// Returns the directory which `rustc` invocations should use for + /// temporary files, and which `CARGO_CFG_TMPDIR` should be set to. + /// `/path/to/target/tmp` + pub fn rustc_tmp_dir(&self, unit: &Unit) -> &Path { + self.layout(unit.kind).build_dir().tmp() + } + /// Returns the path to the executable binary for the given bin target. /// /// This should only to be used when a `Unit` is not available. diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index db9bc9fce07..8d33fc44e1e 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -325,6 +325,7 @@ fn emit_build_output( /// /// * Set environment variables for the build script run. /// * Create the output dir (`OUT_DIR`) for the build script output. +/// * Create the temporary dir (`TMPDIR`/`TMP`/`TEMP`) that will be set. /// * Determine if the build script needs a re-run. /// * Run the build script and store its output. fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult { @@ -339,6 +340,7 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul let script_dir = build_runner.files().build_script_dir(build_script_unit); let script_out_dir = build_runner.files().build_script_out_dir(unit); let script_run_dir = build_runner.files().build_script_run_dir(unit); + let script_tmp_dir = build_runner.files().build_script_tmp_dir(unit); if let Some(deps) = unit.pkg.manifest().metabuild() { prepare_metabuild(build_runner, build_script_unit, deps)?; @@ -376,6 +378,12 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul .env("RUSTDOC", &*bcx.gctx.rustdoc()?) .inherit_jobserver(&build_runner.jobserver); + // Make build scripts output temporary files to `script_tmp_dir` instead + // of the path returned by `std::env::temp_dir()`. + for key in paths::tmpdir_envvars() { + cmd.env(key, &script_tmp_dir); + } + // Find all artifact dependencies and make their file and containing directory discoverable using environment variables. for (var, value) in artifact::get_env(build_runner, dependencies)? { cmd.env(&var, value); @@ -495,6 +503,7 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul paths::create_dir_all(&script_dir)?; paths::create_dir_all(&script_out_dir)?; + paths::create_dir_all(&script_tmp_dir)?; let nightly_features_allowed = build_runner.bcx.gctx.nightly_features_allowed; let targets: Vec = unit.pkg.targets().to_vec(); diff --git a/src/cargo/core/compiler/layout.rs b/src/cargo/core/compiler/layout.rs index e30d178eda4..b26a391c798 100644 --- a/src/cargo/core/compiler/layout.rs +++ b/src/cargo/core/compiler/layout.rs @@ -380,9 +380,8 @@ impl BuildDirLayout { pub fn build_unit(&self, pkg_dir: &str) -> PathBuf { self.build().join(pkg_dir) } - /// Create and return the tmp path. - pub fn prepare_tmp(&self) -> CargoResult<&Path> { - paths::create_dir_all(&self.tmp)?; - Ok(&self.tmp) + /// Return the tmp path. + pub fn tmp(&self) -> &Path { + &self.tmp } } diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index cffa9a69cf6..f30baf419fc 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -787,13 +787,17 @@ fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult } } + let tmpdir = build_runner.files().rustc_tmp_dir(unit); + paths::create_dir_all(tmpdir)?; + + // Make `rustc`, proc-macros and the linker output temporary files to + // `tmpdir` instead of the path returned by `std::env::temp_dir()`. + for key in paths::tmpdir_envvars() { + base.env(key, tmpdir); + } + if unit.target.is_test() || unit.target.is_bench() { - let tmp = build_runner - .files() - .layout(unit.kind) - .build_dir() - .prepare_tmp()?; - base.env("CARGO_TARGET_TMPDIR", tmp.display().to_string()); + base.env("CARGO_TARGET_TMPDIR", tmpdir.display().to_string()); } Ok(base) @@ -923,6 +927,15 @@ fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResu append_crate_version_flag(unit, &mut rustdoc); } + let tmpdir = build_runner.files().rustc_tmp_dir(unit); + paths::create_dir_all(tmpdir)?; + + // Make `rustdoc` and proc-macros output temporary files to `tmpdir` + // instead of the path returned by `std::env::temp_dir()`. + for key in paths::tmpdir_envvars() { + rustdoc.env(key, tmpdir); + } + Ok(rustdoc) } diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 5605f4736ec..07791dd7ce9 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -6425,3 +6425,69 @@ fn embed_metadata_dylib_dep() { ) .run(); } + +#[cargo_test] +fn temp_dir_is_inside_build_dir() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + [dependencies] + pm = { path = "pm" } + "#, + ) + .file( + "src/lib.rs", + r#" + pm::foo!(); + "#, + ) + .file( + "build.rs", + r#" + fn main() { + let temp_dir = std::env::temp_dir(); + assert!(temp_dir.components().any(|c| c.as_os_str() == "custom-build-dir"), "{temp_dir:?}"); + } + "#, + ) + .file( + ".cargo/config.toml", + r#" + [build] + target-dir = "custom-target-dir" + build-dir = "custom-build-dir" + "#, + ) + .file( + "pm/Cargo.toml", + r#" + [package] + name = "pm" + version = "0.1.0" + edition = "2021" + [lib] + proc-macro = true + "#, + ) + .file( + "pm/src/lib.rs", + r#" + use proc_macro::TokenStream; + + #[proc_macro] + pub fn foo(input: TokenStream) -> TokenStream { + let temp_dir = std::env::temp_dir(); + assert!(temp_dir.components().any(|c| c.as_os_str() == "custom-build-dir"), "{temp_dir:?}"); + input + } + "#, + ) + .build(); + + p.cargo("build").run(); +}