struct ControlPaths { root: PathBuf, root_file: File, root_identity: FileIdentity, socket: PathBuf, identity: PathBuf, staged_socket: PathBuf, staged_identity: PathBuf, } impl ControlPaths { fn open(root: &Path, uid: u32) -> Result { if !root.is_absolute() { return Err(ControlError::InvalidPath( "runtime Domain root is not absolute", )); } let canonical = std::fs::canonicalize(root)?; if canonical != root { return Err(ControlError::InvalidPath( "runtime Domain root contains a symlink", )); } let metadata = std::fs::symlink_metadata(root)?; if !metadata.file_type().is_dir() || metadata.uid() != uid || metadata.mode() & 0o7777 != 0o700 { return Err(ControlError::InvalidPath( "runtime Domain root is not invoking-user-owned mode 0700", )); } let root_file = OpenOptions::new().read(true).open(root)?; let root_identity = FileIdentity::from_metadata(&root_file.metadata()?); Ok(Self { root: root.to_path_buf(), root_file, root_identity, socket: root.join(CONTROL_SOCKET_NAME), identity: root.join(PUBLICATION_IDENTITY_NAME), staged_socket: root.join(STAGED_CONTROL_SOCKET_NAME), staged_identity: root.join(STAGED_PUBLICATION_IDENTITY_NAME), }) } fn validate_root(&self, uid: u32) -> Result<(), ControlError> { let descriptor = self.root_file.metadata()?; let current = std::fs::symlink_metadata(&self.root)?; if FileIdentity::from_metadata(&descriptor) != self.root_identity || FileIdentity::from_metadata(¤t) != self.root_identity || !current.file_type().is_dir() || current.uid() != uid || current.mode() & 0o7777 != 0o700 { return Err(ControlError::InvalidPath("runtime Domain root changed")); } Ok(()) } fn create_named_identity( &self, name: &str, bytes: &[u8], uid: u32, ) -> Result<(File, FileIdentity), ControlError> { let fd = openat( self.root_file.as_fd(), name, OFlags::CREATE | OFlags::EXCL | OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOFOLLOW, Mode::RUSR | Mode::WUSR, ) .map_err(io::Error::from)?; let mut file = File::from(fd); let identity = FileIdentity::from_metadata(&file.metadata()?); let result = (|| { fchmod(file.as_fd(), Mode::RUSR | Mode::WUSR).map_err(io::Error::from)?; file.write_all(bytes) .map_err(|_| ControlError::InvalidPublication("identity write failed"))?; file.sync_all() .map_err(|_| ControlError::InvalidPublication("identity sync failed"))?; self.root_file .sync_all() .map_err(|_| ControlError::InvalidPublication("directory sync failed"))?; self.validate_identity_at(&self.root.join(name), &file, identity, uid, bytes.len()) })(); if let Err(error) = result { remove_if_identity_matches(&self.root.join(name), identity); return Err(error); } Ok((file, identity)) } fn validate_identity( &self, file: &File, expected: FileIdentity, uid: u32, expected_length: usize, ) -> Result<(), ControlError> { self.validate_identity_at(&self.identity, file, expected, uid, expected_length) } fn validate_identity_at( &self, path: &Path, file: &File, expected: FileIdentity, uid: u32, expected_length: usize, ) -> Result<(), ControlError> { self.validate_root(uid)?; let descriptor = file.metadata()?; let current = std::fs::symlink_metadata(path)?; if FileIdentity::from_metadata(&descriptor) != expected || FileIdentity::from_metadata(¤t) != expected || !descriptor.file_type().is_file() || !current.file_type().is_file() || descriptor.uid() != uid || current.uid() != uid || descriptor.mode() & 0o7777 != 0o600 || current.mode() & 0o7777 != 0o600 || usize::try_from(descriptor.len()).ok() != Some(expected_length) || current.len() != descriptor.len() { return Err(ControlError::InvalidPublication( "publication identity changed type, ownership, mode, or content length", )); } Ok(()) } } pub struct PublishedControlFace { listener: std::os::unix::net::UnixListener, paths: ControlPaths, socket_identity: FileIdentity, identity_file: File, identity_file_identity: FileIdentity, publication_identity: ProtocolPublicationIdentity, publication_bytes: Vec, uid: u32, anchor: TrustAnchor, server_config: Arc, registry: Arc, executor: Arc, interactive_executor: Arc, } pub(crate) struct AcceptedControlConnection { socket: std::os::unix::net::UnixStream, } impl AcceptedControlConnection { pub(crate) fn cancellation_handle(&self) -> std::io::Result { self.socket.try_clone() } } pub trait NonInteractiveSessionExecutor: Send + Sync { /// # Errors /// Returns a runtime, stream, or exact-session execution failure. fn execute( &self, request: &NonInteractiveSessionRequest, emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>, ) -> Result; } struct ExecutionUnavailable; impl NonInteractiveSessionExecutor for ExecutionUnavailable { fn execute( &self, _request: &NonInteractiveSessionRequest, _emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>, ) -> Result { Err(ControlError::InvalidRequest) } } pub trait InteractiveControlSession: Send { /// # Errors /// Returns an exact-session terminal, prompt-routing, or transport failure. fn exchange( &mut self, event: InteractiveSessionEvent, ) -> Result; } pub trait InteractiveSessionExecutor: Send + Sync { /// # Errors /// Returns an acquisition or exact-session identity failure. fn start( &self, request: &InteractiveSessionRequest, ) -> Result, ControlError>; } struct InteractiveExecutionUnavailable; impl InteractiveSessionExecutor for InteractiveExecutionUnavailable { fn start( &self, _request: &InteractiveSessionRequest, ) -> Result, ControlError> { Err(ControlError::InvalidRequest) } } pub struct RunscNonInteractiveExecutor { program: OsString, global_arguments: Vec, container: OsString, runner: Arc, } trait NonInteractiveCommandRunner: Send + Sync { fn run( &self, target: &TargetCommand, session_id: SessionId, emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>, ) -> Result; } struct ChildBrokerCommandRunner; impl NonInteractiveCommandRunner for ChildBrokerCommandRunner { fn run( &self, target: &TargetCommand, session_id: SessionId, emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>, ) -> Result { let mut child = child_broker().run_transient_captured(target)?; let mut stdout_done = false; let mut stderr_done = false; while !(stdout_done && stderr_done && child.process().has_exited()?) { let (stdout, stderr) = child.streams(); stdout_done |= emit_available(stdout, session_id, true, emit)?; stderr_done |= emit_available(stderr, session_id, false, emit)?; if !(stdout_done && stderr_done) { std::thread::yield_now(); } } child_broker() .reap_status(child.into_process()) .map_err(Into::into) } } impl RunscNonInteractiveExecutor { /// Derives the exact `runsc exec` prefix from the generation's attached `runsc run` command. /// /// # Errors /// Refuses a non-runsc command or a command without one exact run/container suffix. pub fn from_run_command( command: &RunscCommand, container_id: ContainerId, ) -> Result { if command.program() != OsStr::new("runsc") { return Err(ControlError::InvalidRequest); } let run = command .arguments() .iter() .position(|argument| argument == OsStr::new("run")) .ok_or(ControlError::InvalidRequest)?; if run + 3 != command.arguments().len() || !command.arguments()[run + 1] .as_encoded_bytes() .starts_with(b"-bundle=") { return Err(ControlError::InvalidRequest); } let container = OsString::from(hex_lower(container_id.as_bytes())); if command.arguments()[run + 2] != container { return Err(ControlError::InvalidRequest); } Ok(Self { program: command.program().to_owned(), global_arguments: command.arguments()[..run].to_vec(), container, runner: Arc::new(ChildBrokerCommandRunner), }) } } impl NonInteractiveSessionExecutor for RunscNonInteractiveExecutor { fn execute( &self, request: &NonInteractiveSessionRequest, emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>, ) -> Result { let mut target = TargetCommand::new(self.program.clone()); for argument in &self.global_arguments { target = target.argument(argument.clone()); } target = target .argument("exec") .argument("-env") .argument(format!( "LAZARET_SESSION_ID={}", hex_lower(request.session_id.as_bytes()) )) .argument(self.container.clone()); for argument in &request.argv { target = target.argument(OsString::from_vec(argument.clone())); } let process_status = self.runner.run(&target, request.session_id, emit)?; match (process_status.exit_code, process_status.terminating_signal) { (Some(code), None) => Ok(SessionExit::exited(request.session_id, code)), (None, Some(_)) => Ok(SessionExit::terminated(request.session_id)), _ => Err(ControlError::InvalidRequest), } } } fn emit_available( stream: &mut File, session_id: SessionId, stdout: bool, emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>, ) -> Result { let mut bytes = [0_u8; 16 * 1024]; loop { match stream.read(&mut bytes) { Ok(0) => return Ok(true), Ok(length) => { let output = lazaret_core::records::NonInteractiveOutput { session_id, bytes: bytes[..length].to_vec(), }; emit(if stdout { NonInteractiveSessionMessage::Stdout(output) } else { NonInteractiveSessionMessage::Stderr(output) })?; } Err(error) if error.kind() == io::ErrorKind::WouldBlock => return Ok(false), Err(error) => return Err(error.into()), } } } fn hex_lower(bytes: &[u8]) -> String { use fmt::Write as _; bytes.iter().fold(String::new(), |mut output, byte| { write!(output, "{byte:02x}").expect("writing to a String cannot fail"); output }) }