Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion crates/openshell-sandbox/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,13 @@ async fn load_policy(
// The initial load uses pid=0 (no symlink resolution) because the
// container hasn't started yet. After the entrypoint spawns, the
// engine is rebuilt with the real PID for symlink resolution.
// Normalize process identity before conversion so `prepare_filesystem`
// and `drop_privileges` see the correct user/group. The server-side
// `handle_update_config` does this, but if the supervisor fetches the
// policy before the server has normalized it (or if the policy was
// loaded from disk), the fields may still be empty.
openshell_policy::ensure_sandbox_process_identity(&mut proto_policy);

info!("Creating OPA engine from proto policy data");
let opa_engine = Some(Arc::new(OpaEngine::from_proto(&proto_policy)?));

Expand Down Expand Up @@ -1921,8 +1928,16 @@ fn prepare_filesystem(policy: &SandboxPolicy) -> Result<()> {
_ => None,
};

// If no user/group configured, nothing to do
// If no user/group is configured and we are running as root, fall back to
// "sandbox:sandbox" — same logic as `drop_privileges` so the filesystem is
// prepared for the user that will actually own the process.
if user_name.is_none() && group_name.is_none() {
if nix::unistd::geteuid().is_root() {
let mut fallback = policy.clone();
fallback.process.run_as_user = Some("sandbox".into());
fallback.process.run_as_group = Some("sandbox".into());
return prepare_filesystem(&fallback);
}
return Ok(());
}

Expand Down
32 changes: 20 additions & 12 deletions crates/openshell-sandbox/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4433,21 +4433,29 @@ mod tests {
identity.binary_pid
),
Err(err) => {
// In some CI environments /proc/{pid}/exe is unreadable for the
// forked child, causing an early "failed to resolve peer binary"
// error instead of the later "ambiguous shared socket ownership"
// check. Both paths correctly deny the connection.
let is_ambiguous = err.reason.contains("ambiguous shared socket ownership");
let is_resolve_failure = err.reason.contains("failed to resolve peer binary");
assert!(
err.reason.contains("ambiguous shared socket ownership"),
"expected ambiguous socket ownership error, got: {}",
err.reason
);
assert!(
err.reason.contains(&std::process::id().to_string()),
"error should include parent PID; got: {}",
err.reason
);
assert!(
err.reason.contains(&child_pid.to_string()),
"error should include child PID; got: {}",
is_ambiguous || is_resolve_failure,
"expected ambiguous ownership or resolve failure, got: {}",
err.reason
);
if is_ambiguous {
assert!(
err.reason.contains(&std::process::id().to_string()),
"error should include parent PID; got: {}",
err.reason
);
assert!(
err.reason.contains(&child_pid.to_string()),
"error should include child PID; got: {}",
err.reason
);
}
}
}
}
Expand Down
13 changes: 4 additions & 9 deletions crates/openshell-sandbox/src/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,12 @@ async fn ssh_server_init(
let config = Arc::new(config);
let ca_paths = ca_file_paths.as_ref().map(|p| Arc::new(p.clone()));

// Ensure the parent directory exists and is root-owned with 0700
// permissions. The sandbox entrypoint runs as an unprivileged user; it
// must not be able to enter this directory and connect to the socket.
// Ensure the parent directory exists. When the socket lives directly in a
// shared writable directory (e.g. /tmp), locking the parent to 0700 would
// break other processes — the socket file itself is already tightened to
// 0600 below which prevents the sandbox user from connecting.
if let Some(parent) = listen_path.parent() {
std::fs::create_dir_all(parent).into_diagnostic()?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o700);
std::fs::set_permissions(parent, perms).into_diagnostic()?;
}
}

// Remove any stale socket from a previous run before binding.
Expand Down
Loading