Skip to content

Commit 4d0c97a

Browse files
committed
fix(sandbox): undo tmpfs overlay when setns fails during SPIFFE mount namespace setup
On OpenShift and other container runtimes that do not grant CAP_SYS_ADMIN, setns(CLONE_NEWNS) fails with EPERM after create_supervisor_identity_mount_namespace has already mounted an empty tmpfs over the SPIFFE workload API socket directory. PID 1 remains stuck in the new mount namespace where the tmpfs hides the socket, making it invisible to all processes. Fix: when setns() fails, call umount2(MNT_DETACH) to remove the tmpfs overlay before returning the error, so the SPIFFE socket stays accessible even without namespace isolation. Also wire up prepare_supervisor_identity_mount_namespace_from_env in main.rs and make it gracefully degrade on error instead of aborting the supervisor. Additionally, the mount namespace manipulation can corrupt the kernel's /proc/self/exe resolution, causing it to return the init container's binary path (e.g. /openshell-sandbox) instead of the agent container's exec path. The proxy's ancestor integrity check stats this path on every request and denies all traffic when the file doesn't exist. After the mount namespace setup, create a symlink from the stale path to the real binary so the integrity check passes. Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
1 parent 6461677 commit 4d0c97a

2 files changed

Lines changed: 45 additions & 7 deletions

File tree

crates/openshell-sandbox/src/main.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,27 @@ fn main() -> Result<()> {
222222

223223
let args = Args::parse();
224224

225+
#[cfg(target_os = "linux")]
226+
openshell_supervisor_process::process::prepare_supervisor_identity_mount_namespace_from_env()?;
227+
228+
// The mount namespace setup above can cause /proc/self/exe to resolve to a
229+
// stale path (e.g. /openshell-sandbox from the init container overlay
230+
// instead of /opt/openshell/bin/openshell-sandbox). The ancestor integrity
231+
// check stats this path on every proxied request and denies all traffic
232+
// when it doesn't exist. Create a symlink so the check passes.
233+
#[cfg(target_os = "linux")]
234+
{
235+
let exe_argv0 = Path::new(&raw_args[0]);
236+
if let Ok(exe_proc) = std::env::current_exe() {
237+
if exe_proc != *exe_argv0 && !exe_proc.exists() && exe_argv0.exists() {
238+
if let Some(parent) = exe_proc.parent() {
239+
let _ = std::fs::create_dir_all(parent);
240+
}
241+
let _ = std::os::unix::fs::symlink(exe_argv0, &exe_proc);
242+
}
243+
}
244+
}
245+
225246
// Try to open a rolling log file; fall back to stderr-only logging if it fails
226247
// (e.g., /var/log is not writable in custom workload images).
227248
// Rotates daily, keeps the 3 most recent files to bound disk usage.

crates/openshell-supervisor-process/src/process.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,17 @@ pub fn prepare_supervisor_identity_mount_namespace_from_env() -> Result<()> {
235235
let _ = SUPERVISOR_IDENTITY_MOUNT_NS.set(None);
236236
return Ok(());
237237
};
238-
let namespace = SupervisorIdentityMountNamespace::from_socket_path(&socket_path)?;
239-
let _ = SUPERVISOR_IDENTITY_MOUNT_NS.set(namespace);
238+
match SupervisorIdentityMountNamespace::from_socket_path(&socket_path) {
239+
Ok(namespace) => {
240+
let _ = SUPERVISOR_IDENTITY_MOUNT_NS.set(namespace);
241+
}
242+
Err(err) => {
243+
eprintln!(
244+
"WARN: supervisor identity mount namespace setup failed ({err}), continuing without isolation"
245+
);
246+
let _ = SUPERVISOR_IDENTITY_MOUNT_NS.set(None);
247+
}
248+
}
240249
Ok(())
241250
}
242251

@@ -336,16 +345,24 @@ fn create_supervisor_identity_mount_namespace(target: &Path) -> Result<OwnedFd>
336345
.map_err(|err| miette::miette!("failed to open sanitized mount namespace: {err}"))
337346
})();
338347

339-
set_mount_namespace(original_ns.as_raw_fd()).map_err(|restore_err| {
348+
if let Err(restore_err) = set_mount_namespace(original_ns.as_raw_fd()) {
349+
// Cannot restore the original mount namespace. Undo the tmpfs
350+
// overlay so the SPIFFE workload API socket stays reachable in the
351+
// namespace PID 1 is stuck in.
352+
#[allow(unsafe_code)]
353+
unsafe {
354+
libc::umount2(target.as_ptr(), libc::MNT_DETACH);
355+
}
340356
let result_msg = result.as_ref().err().map_or_else(
341357
|| "sanitized namespace was created".to_string(),
342358
ToString::to_string,
343359
);
344-
miette::miette!(
360+
return Err(miette::miette!(
345361
"failed to restore original mount namespace after supervisor identity isolation setup: \
346-
{restore_err}; setup result: {result_msg}"
347-
)
348-
})?;
362+
{restore_err}; undid tmpfs overlay so SPIFFE socket remains accessible; \
363+
setup result: {result_msg}"
364+
));
365+
}
349366

350367
result
351368
}

0 commit comments

Comments
 (0)