From e7b0d5c786d181262973e77bfda8cd13aa145221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 18 May 2026 11:56:37 +0200 Subject: [PATCH] fix: compare getppid() against actual parent pid, not hardcoded 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The check 'getppid() == 1' intends to detect parent death (where the child is reparented to init/PID 1). In containers the entrypoint process commonly runs as PID 1 itself, so a child forked from it legitimately has getppid() == 1 — causing a false positive and silent child exit. Fix: capture getpid() in the parent before fork and pass it through ChildSpawnArgs so the child compares against the actual parent pid. Fixes: sandlock running from within a containerized server process (e.g. uvicorn as PID 1) where direct Sandbox.run() calls always failed with 'read notif fd from child: pipe closed before 4 bytes read'. --- crates/sandlock-core/src/context.rs | 12 ++++++++++-- crates/sandlock-core/src/sandbox.rs | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/sandlock-core/src/context.rs b/crates/sandlock-core/src/context.rs index 8777b68..041c2ca 100644 --- a/crates/sandlock-core/src/context.rs +++ b/crates/sandlock-core/src/context.rs @@ -734,6 +734,10 @@ pub(crate) struct ChildSpawnArgs<'a> { /// Merged into the child's BPF notif list so the kernel actually /// raises USER_NOTIF for them. pub extra_syscalls: &'a [u32], + /// PID of the parent process captured before fork. Used to detect + /// parent death in the child without assuming PID 1 is always init + /// (incorrect in containers where the entrypoint runs as PID 1). + pub parent_pid: libc::pid_t, } /// Apply irreversible confinement (Landlock + seccomp) then exec the command. @@ -750,6 +754,7 @@ pub(crate) fn confine_child(args: ChildSpawnArgs<'_>) -> ! { keep_fds, sandbox_name, extra_syscalls, + parent_pid, } = args; // Helper: abort child on error. Includes the OS error automatically. macro_rules! fail { @@ -784,8 +789,11 @@ pub(crate) fn confine_child(args: ChildSpawnArgs<'_>) -> ! { fail!("prctl(PR_SET_PDEATHSIG)"); } - // 3. Check parent didn't die between fork and prctl - if unsafe { libc::getppid() } == 1 { + // 3. Check parent didn't die between fork and prctl. + // Compare against the actual parent PID captured before fork rather than + // hardcoding 1, since containers often run the entrypoint as PID 1 and a + // child forked from it legitimately has getppid() == 1. + if unsafe { libc::getppid() } != parent_pid { fail!("parent died before confinement"); } diff --git a/crates/sandlock-core/src/sandbox.rs b/crates/sandlock-core/src/sandbox.rs index 54c464e..9fd39e8 100644 --- a/crates/sandlock-core/src/sandbox.rs +++ b/crates/sandlock-core/src/sandbox.rs @@ -1525,6 +1525,10 @@ impl Sandbox { (None, None) }; + // Capture our PID before fork so the child can detect parent death + // without assuming PID 1 is always init (wrong in containers). + let parent_pid = unsafe { libc::getpid() }; + let pid = unsafe { libc::fork() }; if pid < 0 { return Err(SandboxRuntimeError::Fork(std::io::Error::last_os_error()).into()); @@ -1570,6 +1574,7 @@ impl Sandbox { keep_fds: &gather_keep_fds, sandbox_name: Some(sandbox_name.as_str()), extra_syscalls: &extra_syscalls, + parent_pid, }); }