diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md
index 60a1c1fa1dd..948f1ee6c63 100644
--- a/src/tools/miri/README.md
+++ b/src/tools/miri/README.md
@@ -451,36 +451,32 @@ Some native rustc `-Z` flags are also very relevant for Miri:
 * `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri
   enables this per default because it is needed for [Stacked Borrows] and [Tree Borrows].
 
-Moreover, Miri recognizes some environment variables:
+Moreover, Miri recognizes some environment variables (unless noted otherwise, these are supported
+by all intended entry points, i.e. `cargo miri` and `./miri {test,run}`):
 
 * `MIRI_AUTO_OPS` indicates whether the automatic execution of rustfmt, clippy and toolchain setup
   should be skipped. If it is set to `no`, they are skipped. This is used to allow automated IDE
   actions to avoid the auto ops.
 * `MIRI_LOG`, `MIRI_BACKTRACE` control logging and backtrace printing during
   Miri executions, also [see "Testing the Miri driver" in `CONTRIBUTING.md`][testing-miri].
-* `MIRIFLAGS` (recognized by `cargo miri` and the test suite) defines extra
-  flags to be passed to Miri.
-* `MIRI_LIB_SRC` defines the directory where Miri expects the sources of the
-  standard library that it will build and use for interpretation. This directory
-  must point to the `library` subdirectory of a `rust-lang/rust` repository
-  checkout.
-* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When
-  using `cargo miri`, this skips the automatic setup -- only set this if you do not want to use the
-  automatically created sysroot. For directly invoking the Miri driver, this variable (or a
-  `--sysroot` flag) is mandatory. When invoking `cargo miri setup`, this indicates where the sysroot
-  will be put.
-* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target
+* `MIRIFLAGS` defines extra flags to be passed to Miri.
+* `MIRI_LIB_SRC` defines the directory where Miri expects the sources of the standard library that
+  it will build and use for interpretation. This directory must point to the `library` subdirectory
+  of a `rust-lang/rust` repository checkout.
+* `MIRI_SYSROOT` indicates the sysroot to use. When using `cargo miri`, this skips the automatic
+  setup -- only set this if you do not want to use the automatically created sysroot. When invoking
+  `cargo miri setup`, this indicates where the sysroot will be put.
+* `MIRI_TEST_TARGET` (recognized by `./miri {test,run}`) indicates which target
   architecture to test against.  `miri` and `cargo miri` accept the `--target` flag for the same
   purpose.
-* `MIRI_TEST_THREADS` (recognized by the test suite): set the number of threads to use for running tests.
-  By default the number of cores is used.
-* `MIRI_NO_STD` (recognized by `cargo miri`) makes sure that the target's sysroot is built without
-  libstd. This allows testing and running no_std programs.
-  (Miri has a heuristic to detect no-std targets based on the target name; this environment variable
-  is only needed when that heuristic fails.)
-* `RUSTC_BLESS` (recognized by the test suite and `cargo-miri-test/run-test.py`): overwrite all
+* `MIRI_TEST_THREADS` (recognized by `./miri test`): set the number of threads to use for running tests.
+  By default, the number of cores is used.
+* `MIRI_NO_STD` makes sure that the target's sysroot is built without libstd. This allows testing
+  and running no_std programs. (Miri has a heuristic to detect no-std targets based on the target
+  name; this environment variable is only needed when that heuristic fails.)
+* `RUSTC_BLESS` (recognized by `./miri test` and `cargo-miri-test/run-test.py`): overwrite all
   `stderr` and `stdout` files instead of checking whether the output matches.
-* `MIRI_SKIP_UI_CHECKS` (recognized by the test suite): don't check whether the
+* `MIRI_SKIP_UI_CHECKS` (recognized by `./miri test`): don't check whether the
   `stderr` or `stdout` files match the actual output.
 
 The following environment variables are *internal* and must not be used by
diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs
index 04f3b2918b5..ca8b35a17be 100644
--- a/src/tools/miri/cargo-miri/src/phases.rs
+++ b/src/tools/miri/cargo-miri/src/phases.rs
@@ -172,8 +172,6 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo.
     cmd.args(args);
 
-    // Let it know where the Miri sysroot lives.
-    cmd.env("MIRI_SYSROOT", miri_sysroot);
     // Set `RUSTC_WRAPPER` to ourselves.  Cargo will prepend that binary to its usual invocation,
     // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
     // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
@@ -200,10 +198,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     // always applied. However, buggy build scripts (https://github.com/eyre-rs/eyre/issues/84) and
     // also cargo (https://github.com/rust-lang/cargo/issues/10885) will invoke `rustc` even when
     // `RUSTC_WRAPPER` is set, bypassing the wrapper. To make sure everything is coherent, we want
-    // that to be the Miri driver, but acting as rustc, on the target level. (Target, rather than
-    // host, is needed for cross-interpretation situations.) This is not a perfect emulation of real
-    // rustc (it might be unable to produce binaries since the sysroot is check-only), but it's as
-    // close as we can get, and it's good enough for autocfg.
+    // that to be the Miri driver, but acting as rustc, in host mode.
     //
     // In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc
     // or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that
@@ -212,7 +207,10 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     // bootstrap `rustc` thing in our way! Instead, we have MIRI_HOST_SYSROOT to use for host
     // builds.
     cmd.env("RUSTC", fs::canonicalize(find_miri()).unwrap());
-    cmd.env("MIRI_BE_RUSTC", "target"); // we better remember to *unset* this in the other phases!
+    // In case we get invoked as RUSTC without the wrapper, let's be a host rustc. This makes no
+    // sense for cross-interpretation situations, but without the wrapper, this will use the host
+    // sysroot, so asking it to behave like a target build makes even less sense.
+    cmd.env("MIRI_BE_RUSTC", "host"); // we better remember to *unset* this in the other phases!
 
     // Set rustdoc to us as well, so we can run doctests.
     if let Some(orig_rustdoc) = env::var_os("RUSTDOC") {
@@ -220,6 +218,8 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     }
     cmd.env("RUSTDOC", &cargo_miri_path);
 
+    // Forward some crucial information to our own re-invocations.
+    cmd.env("MIRI_SYSROOT", miri_sysroot);
     cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
     if verbose > 0 {
         cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose.
@@ -412,6 +412,9 @@ pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
     // Arguments are treated very differently depending on whether this crate is
     // for interpretation by Miri, or for use by a build script / proc macro.
     if target_crate {
+        // Set the sysroot.
+        cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
+
         // Forward arguments, but patched.
         let emit_flag = "--emit";
         // This hack helps bootstrap run standard library tests in Miri. The issue is as follows:
@@ -574,6 +577,12 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
         cmd.env(name, val);
     }
 
+    if phase != RunnerPhase::Rustdoc {
+        // Set the sysroot. Not necessary in rustdoc, where we already set the sysroot when invoking
+        // rustdoc itself, which will forward that flag when invoking rustc (i.e., us), so the flag
+        // is present in `info.args`.
+        cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
+    }
     // Forward rustc arguments.
     // We need to patch "--extern" filenames because we forced a check-only
     // build without cargo knowing about that: replace `.rlib` suffix by
diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs
index 55b3b62819f..66707dee5e7 100644
--- a/src/tools/miri/miri-script/src/commands.rs
+++ b/src/tools/miri/miri-script/src/commands.rs
@@ -2,6 +2,7 @@ use std::env;
 use std::ffi::OsString;
 use std::io::Write;
 use std::ops::Not;
+use std::path::PathBuf;
 use std::process;
 use std::thread;
 use std::time;
@@ -20,10 +21,11 @@ const JOSH_FILTER: &str =
 const JOSH_PORT: &str = "42042";
 
 impl MiriEnv {
-    fn build_miri_sysroot(&mut self, quiet: bool) -> Result<()> {
-        if self.sh.var("MIRI_SYSROOT").is_ok() {
+    /// Returns the location of the sysroot.
+    fn build_miri_sysroot(&mut self, quiet: bool) -> Result<PathBuf> {
+        if let Some(miri_sysroot) = self.sh.var_os("MIRI_SYSROOT") {
             // Sysroot already set, use that.
-            return Ok(());
+            return Ok(miri_sysroot.into());
         }
         let manifest_path = path!(self.miri_dir / "cargo-miri" / "Cargo.toml");
         let Self { toolchain, cargo_extra_flags, .. } = &self;
@@ -57,8 +59,8 @@ impl MiriEnv {
             .with_context(|| "`cargo miri setup` failed")?;
             panic!("`cargo miri setup` didn't fail again the 2nd time?");
         };
-        self.sh.set_var("MIRI_SYSROOT", output);
-        Ok(())
+        self.sh.set_var("MIRI_SYSROOT", &output);
+        Ok(output.into())
     }
 }
 
@@ -505,8 +507,10 @@ impl Command {
             flags.push("--edition=2021".into()); // keep in sync with `tests/ui.rs`.`
         }
 
-        // Prepare a sysroot.
-        e.build_miri_sysroot(/* quiet */ true)?;
+        // Prepare a sysroot, and add it to the flags.
+        let miri_sysroot = e.build_miri_sysroot(/* quiet */ true)?;
+        flags.push("--sysroot".into());
+        flags.push(miri_sysroot.into());
 
         // Then run the actual command. Also add MIRIFLAGS.
         let miri_manifest = path!(e.miri_dir / "Cargo.toml");
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 8fffb91542f..67a5bf3d141 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -271,25 +271,6 @@ fn run_compiler(
     callbacks: &mut (dyn rustc_driver::Callbacks + Send),
     using_internal_features: std::sync::Arc<std::sync::atomic::AtomicBool>,
 ) -> ! {
-    if target_crate {
-        // Miri needs a custom sysroot for target crates.
-        // If no `--sysroot` is given, the `MIRI_SYSROOT` env var is consulted to find where
-        // that sysroot lives, and that is passed to rustc.
-        let sysroot_flag = "--sysroot";
-        if !args.iter().any(|e| e.starts_with(sysroot_flag)) {
-            // Using the built-in default here would be plain wrong, so we *require*
-            // the env var to make sure things make sense.
-            let miri_sysroot = env::var("MIRI_SYSROOT").unwrap_or_else(|_| {
-                show_error!(
-                    "Miri was invoked in 'target' mode without `MIRI_SYSROOT` or `--sysroot` being set"
-                    )
-            });
-
-            args.push(sysroot_flag.to_owned());
-            args.push(miri_sysroot);
-        }
-    }
-
     // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
     // a "host" crate. That may cause procedural macros (and probably build scripts) to
     // depend on Miri-only symbols, such as `miri_resolve_frame`:
diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs
index 68446f40e07..ace0da01253 100644
--- a/src/tools/miri/tests/ui.rs
+++ b/src/tools/miri/tests/ui.rs
@@ -112,6 +112,13 @@ fn run_tests(
     config.program.envs.push(("RUST_BACKTRACE".into(), Some("1".into())));
 
     // Add some flags we always want.
+    config.program.args.push(
+        format!(
+            "--sysroot={}",
+            env::var("MIRI_SYSROOT").expect("MIRI_SYSROOT must be set to run the ui test suite")
+        )
+        .into(),
+    );
     config.program.args.push("-Dwarnings".into());
     config.program.args.push("-Dunused".into());
     config.program.args.push("-Ainternal_features".into());