Skip to content

Commit d71ec1c

Browse files
committed
tree-wide: Clean up our re-exec path
Prep for fixing #1434 Basically in the selinux path we copy our binary to a tempfile, which breaks `/proc/self/exe`. Fix this by setting an environment variable when we do that re-exec and ensuring that *everything* references an internal API (now moved to utils/ so it can be shared) that looks for the env var first.
1 parent d35d235 commit d71ec1c

File tree

10 files changed

+30
-53
lines changed

10 files changed

+30
-53
lines changed

crates/blockdev/src/blockdev.rs

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::collections::HashMap;
22
use std::env;
33
use std::path::Path;
4-
use std::path::PathBuf;
54
use std::process::{Command, Stdio};
65
use std::sync::OnceLock;
76

@@ -247,7 +246,7 @@ impl LoopbackDevice {
247246
/// if the parent process dies unexpectedly
248247
fn spawn_cleanup_helper(device_path: &str) -> Result<LoopbackCleanupHandle> {
249248
// Try multiple strategies to find the bootc binary
250-
let bootc_path = Self::find_bootc_binary()
249+
let bootc_path = bootc_utils::reexec::executable_path()
251250
.context("Failed to locate bootc binary for cleanup helper")?;
252251

253252
// Create the helper process
@@ -270,44 +269,6 @@ impl LoopbackDevice {
270269
Ok(LoopbackCleanupHandle { child })
271270
}
272271

273-
/// Find the bootc binary using multiple strategies
274-
fn find_bootc_binary() -> Result<PathBuf> {
275-
// Strategy 1: Try /proc/self/exe (works in most cases)
276-
if let Ok(exe_path) = std::fs::read_link("/proc/self/exe") {
277-
if exe_path.exists() {
278-
return Ok(exe_path);
279-
} else {
280-
tracing::warn!("/proc/self/exe points to non-existent path: {:?}", exe_path);
281-
}
282-
} else {
283-
tracing::warn!("Failed to read /proc/self/exe");
284-
}
285-
286-
// Strategy 2: Try argv[0] from std::env
287-
if let Some(argv0) = std::env::args().next() {
288-
let argv0_path = PathBuf::from(argv0);
289-
if argv0_path.is_absolute() && argv0_path.exists() {
290-
return Ok(argv0_path);
291-
}
292-
// If it's relative, try to resolve it
293-
if let Ok(canonical) = argv0_path.canonicalize() {
294-
return Ok(canonical);
295-
}
296-
}
297-
298-
// Strategy 3: Try common installation paths
299-
let common_paths = ["/usr/bin/bootc", "/usr/local/bin/bootc"];
300-
301-
for path in &common_paths {
302-
let path_buf = PathBuf::from(path);
303-
if path_buf.exists() {
304-
return Ok(path_buf);
305-
}
306-
}
307-
308-
anyhow::bail!("Could not locate bootc binary using any available strategy")
309-
}
310-
311272
// Shared backend for our `close` and `drop` implementations.
312273
fn impl_close(&mut self) -> Result<()> {
313274
// SAFETY: This is the only place we take the option

crates/lib/src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ pub(crate) fn ensure_self_unshared_mount_namespace() -> Result<()> {
685685
anyhow::bail!("Failed to unshare mount namespace");
686686
}
687687
}
688-
crate::reexec::reexec_with_guardenv(recurse_env, &["unshare", "-m", "--"])
688+
bootc_utils::reexec::reexec_with_guardenv(recurse_env, &["unshare", "-m", "--"])
689689
}
690690

691691
/// Acquire a locked sysroot.

crates/lib/src/install.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -914,11 +914,11 @@ async fn install_container(
914914
}
915915

916916
/// Run a command in the host mount namespace
917-
pub(crate) fn run_in_host_mountns(cmd: &str) -> Command {
918-
let mut c = Command::new("/proc/self/exe");
917+
pub(crate) fn run_in_host_mountns(cmd: &str) -> Result<Command> {
918+
let mut c = Command::new(bootc_utils::reexec::executable_path()?);
919919
c.lifecycle_bind()
920920
.args(["exec-in-host-mount-namespace", cmd]);
921-
c
921+
Ok(c)
922922
}
923923

924924
#[context("Re-exec in host mountns")]

crates/lib/src/install/baseline.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ pub(crate) fn udev_settle() -> Result<()> {
152152
// our way out of this.
153153
std::thread::sleep(std::time::Duration::from_millis(200));
154154

155-
let st = super::run_in_host_mountns("udevadm")
155+
let st = super::run_in_host_mountns("udevadm")?
156156
.arg("settle")
157157
.status()?;
158158
if !st.success() {

crates/lib/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ pub(crate) mod metadata;
2121
mod podman;
2222
mod progress_jsonl;
2323
mod reboot;
24-
mod reexec;
2524
pub mod spec;
2625
mod status;
2726
mod store;

crates/lib/src/lsm.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ pub(crate) fn selinux_ensure_install() -> Result<bool> {
9292
// to match that of /usr/bin/ostree, and then re-exec. This is really a gross
9393
// hack; we can't always rely on https://github.com/fedora-selinux/selinux-policy/pull/1500/commits/67eb283c46d35a722636d749e5b339615fe5e7f5
9494
let mut tmpf = tempfile::NamedTempFile::new()?;
95-
let mut src = std::fs::File::open("/proc/self/exe")?;
95+
let srcpath = std::env::current_exe()?;
96+
let mut src = std::fs::File::open(&srcpath)?;
9697
let meta = src.metadata()?;
9798
std::io::copy(&mut src, &mut tmpf).context("Copying self to tempfile for selinux re-exec")?;
9899
tmpf.as_file_mut()
@@ -107,6 +108,7 @@ pub(crate) fn selinux_ensure_install() -> Result<bool> {
107108

108109
let mut cmd = Command::new(&tmpf);
109110
cmd.env(guardenv, tmpf);
111+
cmd.env(bootc_utils::reexec::ORIG, srcpath);
110112
cmd.args(std::env::args_os().skip(1));
111113
cmd.log_debug();
112114
Err(anyhow::Error::msg(cmd.exec()).context("execve"))

crates/lib/src/podman.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub(crate) struct ImageListEntry {
2424
/// Given an image ID, return its manifest digest
2525
pub(crate) fn imageid_to_digest(imgid: &str) -> Result<String> {
2626
use bootc_utils::CommandRunExt;
27-
let o: Vec<Inspect> = crate::install::run_in_host_mountns("podman")
27+
let o: Vec<Inspect> = crate::install::run_in_host_mountns("podman")?
2828
.args(["inspect", imgid])
2929
.run_and_parse_json()?;
3030
let i = o

crates/ostree-ext/src/container/deploy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ pub async fn deploy(
152152

153153
// Note that the sysroot is provided as `.` but we use cwd_dir to
154154
// make the process current working directory the sysroot.
155-
let st = std::process::Command::new("/proc/self/exe")
155+
let st = std::process::Command::new(std::env::current_exe()?)
156156
.args(["internals", "bootc-install-completion", ".", stateroot])
157157
.cwd_dir(sysroot_dir.try_clone()?)
158158
.lifecycle_bind()

crates/utils/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ mod timestamp;
1212
pub use timestamp::*;
1313
mod tracing_util;
1414
pub use tracing_util::*;
15+
/// Re-execute the current process
16+
pub mod reexec;

crates/lib/src/reexec.rs renamed to crates/utils/src/reexec.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
use std::os::unix::process::CommandExt;
2+
use std::path::PathBuf;
23
use std::process::Command;
34

45
use anyhow::Result;
5-
use fn_error_context::context;
6+
7+
/// Environment variable holding a reference to our original binary
8+
pub const ORIG: &str = "_BOOTC_ORIG_EXE";
9+
10+
/// Return the path to our own executable. In some cases (SELinux) we may have
11+
/// performed a re-exec with a temporary copy of the binary and
12+
/// this environment variable will hold the path to the original binary.
13+
pub fn executable_path() -> Result<PathBuf> {
14+
if let Some(p) = std::env::var_os(ORIG) {
15+
Ok(p.into())
16+
} else {
17+
std::env::current_exe().map_err(Into::into)
18+
}
19+
}
620

721
/// Re-execute the current process if the provided environment variable is not set.
8-
#[context("Reexec self")]
9-
pub(crate) fn reexec_with_guardenv(k: &str, prefix_args: &[&str]) -> Result<()> {
22+
pub fn reexec_with_guardenv(k: &str, prefix_args: &[&str]) -> Result<()> {
1023
if std::env::var_os(k).is_some() {
1124
tracing::trace!("Skipping re-exec due to env var {k}");
1225
return Ok(());
1326
}
14-
let self_exe = std::fs::read_link("/proc/self/exe")?;
27+
let self_exe = executable_path()?;
1528
let mut prefix_args = prefix_args.iter();
1629
let mut cmd = if let Some(p) = prefix_args.next() {
1730
let mut c = Command::new(p);

0 commit comments

Comments
 (0)