Compose foreground sandbox process lifetimes
This commit is contained in:
parent
3171c41399
commit
fd78804fc7
1 changed files with 2313 additions and 7 deletions
|
|
@ -1,7 +1,7 @@
|
|||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::fd::{AsFd, OwnedFd};
|
||||
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
|
||||
use std::process::Stdio;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
|
|
@ -29,6 +29,13 @@ static CHILD_BROKER: OnceLock<ChildBroker> = OnceLock::new();
|
|||
pub struct TargetCommand {
|
||||
program: OsString,
|
||||
arguments: Vec<OsString>,
|
||||
parent_action: ParentAction,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ParentAction {
|
||||
Kill,
|
||||
LifetimePipe,
|
||||
}
|
||||
|
||||
impl TargetCommand {
|
||||
|
|
@ -37,6 +44,7 @@ impl TargetCommand {
|
|||
Self {
|
||||
program: program.into(),
|
||||
arguments: Vec::new(),
|
||||
parent_action: ParentAction::Kill,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,9 +54,46 @@ impl TargetCommand {
|
|||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_lifetime_pipe_parent_edge(mut self) -> Self {
|
||||
self.parent_action = ParentAction::LifetimePipe;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_types)]
|
||||
fn append_to(&self, command: &mut std::process::Command) {
|
||||
command.arg("--").arg(&self.program).args(&self.arguments);
|
||||
command
|
||||
.arg("--parent-action")
|
||||
.arg(match self.parent_action {
|
||||
ParentAction::Kill => "kill",
|
||||
ParentAction::LifetimePipe => "lifetime-pipe",
|
||||
})
|
||||
.arg("--")
|
||||
.arg(&self.program)
|
||||
.args(&self.arguments);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChildStandardIo {
|
||||
stdout: Option<Stdio>,
|
||||
stderr: Option<Stdio>,
|
||||
}
|
||||
|
||||
impl ChildStandardIo {
|
||||
#[must_use]
|
||||
pub const fn inherit() -> Self {
|
||||
Self {
|
||||
stdout: None,
|
||||
stderr: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn redirected(stdout: impl Into<Stdio>, stderr: impl Into<Stdio>) -> Self {
|
||||
Self {
|
||||
stdout: Some(stdout.into()),
|
||||
stderr: Some(stderr.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,6 +135,12 @@ impl RetainedProcess {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsFd for RetainedProcess {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.pidfd.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChildBroker {
|
||||
exclusion: Mutex<()>,
|
||||
|
|
@ -117,6 +168,32 @@ impl ChildBroker {
|
|||
stage: Stage,
|
||||
acknowledge: Acknowledge,
|
||||
) -> io::Result<RetainedProcess>
|
||||
where
|
||||
Stage: FnOnce(StagedProcessOwnership) -> io::Result<()>,
|
||||
Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result<ProcessOwnershipCommitAck>,
|
||||
{
|
||||
self.acquire_with_stdio(
|
||||
expected_parent,
|
||||
ownership,
|
||||
target,
|
||||
ChildStandardIo::inherit(),
|
||||
stage,
|
||||
acknowledge,
|
||||
)
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// Refuses and reaps the blocked wrapper on every failed acquisition or callback.
|
||||
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
||||
pub fn acquire_with_stdio<Stage, Acknowledge>(
|
||||
&self,
|
||||
expected_parent: PidfsProcessIdentityV1,
|
||||
ownership: ProcessOwnershipContext,
|
||||
target: &TargetCommand,
|
||||
mut standard_io: ChildStandardIo,
|
||||
stage: Stage,
|
||||
acknowledge: Acknowledge,
|
||||
) -> io::Result<RetainedProcess>
|
||||
where
|
||||
Stage: FnOnce(StagedProcessOwnership) -> io::Result<()>,
|
||||
Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result<ProcessOwnershipCommitAck>,
|
||||
|
|
@ -132,6 +209,12 @@ impl ChildBroker {
|
|||
append_parent_identity(&mut command, expected_parent);
|
||||
target.append_to(&mut command);
|
||||
command.stdin(wait.into_stdio());
|
||||
if let Some(stdout) = standard_io.stdout.take() {
|
||||
command.stdout(stdout);
|
||||
}
|
||||
if let Some(stderr) = standard_io.stderr.take() {
|
||||
command.stderr(stderr);
|
||||
}
|
||||
let mut child = command.spawn()?;
|
||||
let host_pid = child.id();
|
||||
let acquired = acquire_spawned_child(host_pid);
|
||||
|
|
@ -217,10 +300,42 @@ fn run_wrapper_role(arguments: Vec<OsString>) -> io::Result<()> {
|
|||
let (expected_parent, target) = parse_wrapper_arguments(arguments)?;
|
||||
BarrierWait::from_stdin()?.wait()?;
|
||||
rustix::io::fcntl_setfd(std::io::stdin(), rustix::io::FdFlags::CLOEXEC)?;
|
||||
arm_parent_death_and_recheck(Signal::KILL, expected_parent)?;
|
||||
match target.parent_action {
|
||||
ParentAction::Kill => {
|
||||
arm_parent_death_and_recheck(Signal::KILL, expected_parent)?;
|
||||
}
|
||||
ParentAction::LifetimePipe => {
|
||||
require_lifetime_pipe_and_recheck(expected_parent)?;
|
||||
}
|
||||
}
|
||||
Err(exec_target(target))
|
||||
}
|
||||
|
||||
fn require_lifetime_pipe_and_recheck(
|
||||
expected_parent: PidfsProcessIdentityV1,
|
||||
) -> io::Result<RetainedProcess> {
|
||||
let stderr = std::io::stderr();
|
||||
let mut descriptors = [PollFd::new(&stderr, PollFlags::IN)];
|
||||
poll(&mut descriptors, Some(&Timespec::default()))?;
|
||||
if descriptors[0]
|
||||
.revents()
|
||||
.intersects(PollFlags::IN | PollFlags::HUP | PollFlags::ERR)
|
||||
{
|
||||
return Err(io::Error::other("parent lifetime pipe is not held"));
|
||||
}
|
||||
let parent = recover_process(expected_parent)?;
|
||||
let observed_parent = rustix::process::getppid()
|
||||
.map(Pid::as_raw_nonzero)
|
||||
.map(i32::from)
|
||||
.and_then(|pid| u32::try_from(pid).ok());
|
||||
if observed_parent != Some(expected_parent.host_pid().get()) {
|
||||
return Err(io::Error::other(
|
||||
"parent changed while arming lifetime pipe",
|
||||
));
|
||||
}
|
||||
Ok(parent)
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
fn acquire_spawned_child(host_pid: u32) -> io::Result<RetainedProcess> {
|
||||
let raw_pid = i32::try_from(host_pid)
|
||||
|
|
@ -658,6 +773,11 @@ pub fn parse_wrapper_arguments(
|
|||
let inode = number_argument(&mut arguments, "--parent-inode")?;
|
||||
let pid = number_argument(&mut arguments, "--parent-pid")?;
|
||||
let start = number_argument(&mut arguments, "--parent-start")?;
|
||||
let parent_action = match named_argument(&mut arguments, "--parent-action")?.to_str() {
|
||||
Some("kill") => ParentAction::Kill,
|
||||
Some("lifetime-pipe") => ParentAction::LifetimePipe,
|
||||
_ => return Err(io::Error::other("wrapper parent action is invalid")),
|
||||
};
|
||||
if arguments.next().as_deref() != Some(OsStr::new("--")) {
|
||||
return Err(io::Error::other("wrapper target separator is missing"));
|
||||
}
|
||||
|
|
@ -667,6 +787,7 @@ pub fn parse_wrapper_arguments(
|
|||
let target = TargetCommand {
|
||||
program,
|
||||
arguments: arguments.collect(),
|
||||
parent_action,
|
||||
};
|
||||
let boot = parse_boot_hex(&boot)?;
|
||||
Ok((
|
||||
|
|
|
|||
Loading…
Reference in a new issue