Skip to content

Commit 1e415f5

Browse files
committed
sandlock-oci: propagate detached exec exit to the shim via reaped proxy
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent 8ffaf42 commit 1e415f5

1 file changed

Lines changed: 73 additions & 5 deletions

File tree

crates/sandlock-oci/src/main.rs

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,10 @@ fn cmd_exec(
891891
args: req.args,
892892
env: req.env,
893893
cwd: req.cwd,
894-
detach,
894+
// Always ask the supervisor to report the exec'd process's exit. The
895+
// caller's `--detach` (set by the containerd shim) is handled locally
896+
// by the exec proxy below, which still needs that exit status.
897+
detach: false,
895898
};
896899
let payload = serde_json::to_vec(&cmd).context("serialize exec command")?;
897900

@@ -924,15 +927,80 @@ fn cmd_exec(
924927
other => bail!("unexpected exec reply: {:?}", other),
925928
};
926929

930+
if detach {
931+
// Detached exec (containerd: `exec --detach --pid-file`). The shim
932+
// expects this CLI to return promptly and reaps the pid-file pid for
933+
// exit. The exec'd process is sandlock-init's child, which the shim
934+
// cannot reap; so fork a proxy whose pid goes in the pid-file. When this
935+
// CLI returns, the proxy reparents to the shim (the subreaper), and the
936+
// proxy exits with the exec'd process's status, so the shim reaps a
937+
// matching wait-status. This is the exec-path analog of the create-path
938+
// supervisor that the shim already reaps.
939+
let mut rdy = [0i32; 2];
940+
if unsafe { libc::pipe2(rdy.as_mut_ptr(), libc::O_CLOEXEC) } != 0 {
941+
bail!("pipe2 for exec proxy failed: {}", std::io::Error::last_os_error());
942+
}
943+
let (rdy_r, rdy_w) = (rdy[0], rdy[1]);
944+
let kid = unsafe { libc::fork() };
945+
if kid < 0 {
946+
bail!("fork exec proxy failed: {}", std::io::Error::last_os_error());
947+
}
948+
if kid == 0 {
949+
// ===== EXEC PROXY =====
950+
unsafe { libc::close(rdy_r) };
951+
// Drop the shim's exec stdio so we do not pin those streams open;
952+
// the exec'd process holds its own dup'd copies (via SCM_RIGHTS), so
953+
// the shim still sees EOF when that process exits.
954+
unsafe {
955+
let n = libc::open(b"/dev/null\0".as_ptr() as *const libc::c_char, libc::O_RDWR);
956+
if n >= 0 {
957+
libc::dup2(n, 0);
958+
libc::dup2(n, 1);
959+
libc::dup2(n, 2);
960+
if n > 2 {
961+
libc::close(n);
962+
}
963+
}
964+
}
965+
// The pid the shim will reap is THIS proxy.
966+
if let Some(pf) = pid_file {
967+
let _ = std::fs::write(pf, std::process::id().to_string());
968+
}
969+
// Tell the parent the pid-file is written; it can now return.
970+
unsafe {
971+
libc::write(rdy_w, b"x".as_ptr() as *const libc::c_void, 1);
972+
libc::close(rdy_w);
973+
}
974+
// Block until the exec'd process exits, then exit with its status.
975+
let mut line2 = String::new();
976+
if reader.read_line(&mut line2).is_ok() {
977+
if let Ok(supervisor::SupervisorReply::Exit { code, signal }) =
978+
serde_json::from_str::<supervisor::SupervisorReply>(line2.trim())
979+
{
980+
supervisor_exit(Some(state::ExitInfo { code, signal }));
981+
}
982+
}
983+
// Lost the channel before an Exit arrived: report a generic failure
984+
// so the shim does not hang.
985+
unsafe { libc::_exit(255) };
986+
}
987+
// ===== PARENT: wait until the proxy has written the pid-file, return =====
988+
unsafe { libc::close(rdy_w) };
989+
let mut b = [0u8; 1];
990+
unsafe {
991+
libc::read(rdy_r, b.as_mut_ptr() as *mut libc::c_void, 1);
992+
libc::close(rdy_r);
993+
}
994+
return Ok(());
995+
}
996+
997+
// Attached: this CLI is the handle the caller waits on. Record the exec'd
998+
// pid and block until it exits.
927999
if let Some(pf) = pid_file {
9281000
std::fs::write(pf, exec_pid.to_string())
9291001
.with_context(|| format!("write pid file {:?}", pf))?;
9301002
}
9311003

932-
if detach {
933-
return Ok(());
934-
}
935-
9361004
// Second reply: Exit. Exit this CLI with the same status.
9371005
let mut line2 = String::new();
9381006
reader.read_line(&mut line2).context("read exec exit reply")?;

0 commit comments

Comments
 (0)