diff --git a/Cargo.lock b/Cargo.lock
index 97a90a44a398e..9aa32eea8ce2e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3740,6 +3740,9 @@ dependencies = [
 [[package]]
 name = "rustc_fs_util"
 version = "0.0.0"
+dependencies = [
+ "tempfile",
+]
 
 [[package]]
 name = "rustc_graphviz"
@@ -4052,7 +4055,6 @@ dependencies = [
  "rustc_session",
  "rustc_span",
  "rustc_target",
- "tempfile",
  "tracing",
 ]
 
diff --git a/compiler/rustc_codegen_ssa/src/back/archive.rs b/compiler/rustc_codegen_ssa/src/back/archive.rs
index 34c84c64070d2..1e1bdfb5977af 100644
--- a/compiler/rustc_codegen_ssa/src/back/archive.rs
+++ b/compiler/rustc_codegen_ssa/src/back/archive.rs
@@ -13,9 +13,9 @@ use object::read::archive::ArchiveFile;
 use object::read::macho::FatArch;
 use rustc_data_structures::fx::FxIndexSet;
 use rustc_data_structures::memmap::Mmap;
+use rustc_fs_util::TempDirBuilder;
 use rustc_session::Session;
 use rustc_span::Symbol;
-use tempfile::Builder as TempFileBuilder;
 use tracing::trace;
 
 use super::metadata::search_for_section;
@@ -501,7 +501,7 @@ impl<'a> ArArchiveBuilder<'a> {
         // it creates. We need it to be the default mode for back compat reasons however. (See
         // #107495) To handle this we are telling tempfile to create a temporary directory instead
         // and then inside this directory create a file using File::create.
-        let archive_tmpdir = TempFileBuilder::new()
+        let archive_tmpdir = TempDirBuilder::new()
             .suffix(".temp-archive")
             .tempdir_in(output.parent().unwrap_or_else(|| Path::new("")))
             .map_err(|err| {
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 8de68925cabbc..72850e35ced5a 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -18,7 +18,7 @@ use rustc_data_structures::fx::FxIndexSet;
 use rustc_data_structures::memmap::Mmap;
 use rustc_data_structures::temp_dir::MaybeTempDir;
 use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
-use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize};
+use rustc_fs_util::{TempDirBuilder, fix_windows_verbatim_for_gcc, try_canonicalize};
 use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
 use rustc_macros::LintDiagnostic;
 use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
@@ -48,7 +48,6 @@ use rustc_target::spec::{
     LinkerFeatures, LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel,
     SanitizerSet, SplitDebuginfo,
 };
-use tempfile::Builder as TempFileBuilder;
 use tracing::{debug, info, warn};
 
 use super::archive::{ArchiveBuilder, ArchiveBuilderBuilder};
@@ -100,7 +99,7 @@ pub fn link_binary(
         });
 
         if outputs.outputs.should_link() {
-            let tmpdir = TempFileBuilder::new()
+            let tmpdir = TempDirBuilder::new()
                 .prefix("rustc")
                 .tempdir()
                 .unwrap_or_else(|error| sess.dcx().emit_fatal(errors::CreateTempDir { error }));
diff --git a/compiler/rustc_fs_util/Cargo.toml b/compiler/rustc_fs_util/Cargo.toml
index baca3bc7d49eb..90a6acade8b03 100644
--- a/compiler/rustc_fs_util/Cargo.toml
+++ b/compiler/rustc_fs_util/Cargo.toml
@@ -5,4 +5,5 @@ edition = "2024"
 
 [dependencies]
 # tidy-alphabetical-start
+tempfile = "3.7.1"
 # tidy-alphabetical-end
diff --git a/compiler/rustc_fs_util/src/lib.rs b/compiler/rustc_fs_util/src/lib.rs
index 0df1b243d697e..7a883a13b72da 100644
--- a/compiler/rustc_fs_util/src/lib.rs
+++ b/compiler/rustc_fs_util/src/lib.rs
@@ -1,6 +1,8 @@
-use std::ffi::CString;
+use std::ffi::{CString, OsStr};
 use std::path::{Path, PathBuf, absolute};
-use std::{fs, io};
+use std::{env, fs, io};
+
+use tempfile::TempDir;
 
 // Unfortunately, on windows, it looks like msvcrt.dll is silently translating
 // verbatim paths under the hood to non-verbatim paths! This manifests itself as
@@ -102,3 +104,43 @@ pub fn path_to_c_string(p: &Path) -> CString {
 pub fn try_canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
     fs::canonicalize(&path).or_else(|_| absolute(&path))
 }
+
+pub struct TempDirBuilder<'a, 'b> {
+    builder: tempfile::Builder<'a, 'b>,
+}
+
+impl<'a, 'b> TempDirBuilder<'a, 'b> {
+    pub fn new() -> Self {
+        Self { builder: tempfile::Builder::new() }
+    }
+
+    pub fn prefix<S: AsRef<OsStr> + ?Sized>(&mut self, prefix: &'a S) -> &mut Self {
+        self.builder.prefix(prefix);
+        self
+    }
+
+    pub fn suffix<S: AsRef<OsStr> + ?Sized>(&mut self, suffix: &'b S) -> &mut Self {
+        self.builder.suffix(suffix);
+        self
+    }
+
+    pub fn tempdir_in<P: AsRef<Path>>(&self, dir: P) -> io::Result<TempDir> {
+        let dir = dir.as_ref();
+        // On Windows in CI, we had been getting fairly frequent "Access is denied"
+        // errors when creating temporary directories.
+        // So this implements a simple retry with backoff loop.
+        #[cfg(windows)]
+        for wait in 1..11 {
+            match self.builder.tempdir_in(dir) {
+                Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {}
+                t => return t,
+            }
+            std::thread::sleep(std::time::Duration::from_millis(1 << wait));
+        }
+        self.builder.tempdir_in(dir)
+    }
+
+    pub fn tempdir(&self) -> io::Result<TempDir> {
+        self.tempdir_in(env::temp_dir())
+    }
+}
diff --git a/compiler/rustc_metadata/Cargo.toml b/compiler/rustc_metadata/Cargo.toml
index 08dcc3d519a2b..b11f9260be7c0 100644
--- a/compiler/rustc_metadata/Cargo.toml
+++ b/compiler/rustc_metadata/Cargo.toml
@@ -26,7 +26,6 @@ rustc_serialize = { path = "../rustc_serialize" }
 rustc_session = { path = "../rustc_session" }
 rustc_span = { path = "../rustc_span" }
 rustc_target = { path = "../rustc_target" }
-tempfile = "3.2"
 tracing = "0.1"
 # tidy-alphabetical-end
 
diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs
index c4e1e0f1d1a99..e57534b847ef0 100644
--- a/compiler/rustc_metadata/src/fs.rs
+++ b/compiler/rustc_metadata/src/fs.rs
@@ -2,11 +2,11 @@ use std::path::{Path, PathBuf};
 use std::{fs, io};
 
 use rustc_data_structures::temp_dir::MaybeTempDir;
+use rustc_fs_util::TempDirBuilder;
 use rustc_middle::ty::TyCtxt;
 use rustc_session::config::{CrateType, OutFileName, OutputType};
 use rustc_session::output::filename_for_metadata;
 use rustc_session::{MetadataKind, Session};
-use tempfile::Builder as TempFileBuilder;
 
 use crate::errors::{
     BinaryOutputToTty, FailedCopyToStdout, FailedCreateEncodedMetadata, FailedCreateFile,
@@ -45,7 +45,7 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) {
     // final destination, with an `fs::rename` call. In order for the rename to
     // always succeed, the temporary file needs to be on the same filesystem,
     // which is why we create it inside the output directory specifically.
-    let metadata_tmpdir = TempFileBuilder::new()
+    let metadata_tmpdir = TempDirBuilder::new()
         .prefix("rmeta")
         .tempdir_in(out_filename.parent().unwrap_or_else(|| Path::new("")))
         .unwrap_or_else(|err| tcx.dcx().emit_fatal(FailedCreateTempdir { err }));