# P2-init-subsystems.patch # Extract init subsystem hardening: service respawn, unit store Option returns, # scheduler PID tracking, and service spawn error handling. # # Files: init/src/scheduler.rs, init/src/service.rs, init/src/unit.rs diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs index d42a4e57..64e64e1e 100644 --- a/init/src/scheduler.rs +++ b/init/src/scheduler.rs @@ -5,6 +5,7 @@ use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; pub struct Scheduler { pending: VecDeque, + pub respawn_pids: Vec<(UnitId, u32)>, } struct Job { @@ -20,6 +21,7 @@ impl Scheduler { pub fn new() -> Scheduler { Scheduler { pending: VecDeque::new(), + respawn_pids: Vec::new(), } } @@ -43,7 +45,10 @@ impl Scheduler { ) { let loaded_units = unit_store.load_units(unit_id.clone(), errors); for unit_id in loaded_units { - if !unit_store.unit(&unit_id).conditions_met() { + let Some(unit) = unit_store.unit(&unit_id) else { + continue; + }; + if !unit.conditions_met() { continue; } @@ -62,7 +67,10 @@ impl Scheduler { match job.kind { JobKind::Start => { - let unit = unit_store.unit_mut(&job.unit); + let Some(unit) = unit_store.unit_mut(&job.unit) else { + eprintln!("init: unit {} not found in store, skipping", job.unit.0); + continue 'a; + }; for dep in &unit.info.requires_weak { for pending_job in &self.pending { @@ -73,14 +81,17 @@ impl Scheduler { } } - run(unit, init_config); + let pid = run(unit, init_config); + if let Some(pid) = pid { + self.respawn_pids.push((job.unit.clone(), pid)); + } } } } } } -fn run(unit: &mut Unit, config: &mut InitConfig) { +fn run(unit: &mut Unit, config: &mut InitConfig) -> Option { match &unit.kind { UnitKind::LegacyScript { script } => { for cmd in script.clone() { @@ -92,25 +103,30 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { } UnitKind::Service { service } => { if config.skip_cmd.contains(&service.cmd) { - eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); - return; + eprintln!("init: skipping {} {}", service.cmd, service.args.join(" ")); + return None; } - if config.log_debug { + eprintln!( + "init: starting {} ({})", + unit.info.description.as_ref().unwrap_or(&unit.id.0), + service.cmd, + ); + let pid = service.spawn(&config.envs); + if pid.is_some() { eprintln!( - "Starting {} ({})", + "init: started {} (pid {})", unit.info.description.as_ref().unwrap_or(&unit.id.0), - service.cmd, + pid.unwrap_or(0), ); } - service.spawn(&config.envs); + return pid; } UnitKind::Target {} => { - if config.log_debug { - eprintln!( - "Reached target {}", - unit.info.description.as_ref().unwrap_or(&unit.id.0), - ); - } + eprintln!( + "init: reached target {}", + unit.info.description.as_ref().unwrap_or(&unit.id.0), + ); } } + None } diff --git a/init/src/service.rs b/init/src/service.rs index ed0023e9..cc95d02b 100644 --- a/init/src/service.rs +++ b/init/src/service.rs @@ -22,6 +22,8 @@ pub struct Service { pub inherit_envs: BTreeSet, #[serde(rename = "type")] pub type_: ServiceType, + #[serde(default)] + pub respawn: bool, } #[derive(Clone, Debug, Default, Deserialize)] @@ -35,7 +37,7 @@ pub enum ServiceType { } impl Service { - pub fn spawn(&self, base_envs: &BTreeMap) { + pub fn spawn(&self, base_envs: &BTreeMap) -> Option { let mut command = Command::new(&self.cmd); command.args(self.args.iter().map(|arg| subst_env(arg))); command.env_clear(); @@ -46,20 +48,28 @@ impl Service { } command.envs(base_envs).envs(&self.envs); - let (mut read_pipe, write_pipe) = io::pipe().unwrap(); + let (mut read_pipe, write_pipe) = match io::pipe() { + Ok(pair) => pair, + Err(err) => { + eprintln!("init: failed to create readiness pipe for {:?}: {err}", command); + return None; + } + }; unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_pipe.into()) }; let mut child = match command.spawn() { Ok(child) => child, Err(err) => { eprintln!("init: failed to execute {:?}: {}", command, err); - return; + return None; } }; match &self.type_ { ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { - Ok(()) => {} + Ok(()) => { + eprintln!("init: {} ready (notify)", self.cmd); + } Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { eprintln!("init: {command:?} exited without notifying readiness"); } @@ -81,23 +91,34 @@ impl Service { }) => continue, Ok(0) => { eprintln!("init: {command:?} exited without notifying readiness"); - return; + return None; } Ok(1) => break, Ok(n) => { eprintln!("init: incorrect amount of fds {n} returned"); - return; + return None; } Err(err) => { eprintln!("init: failed to wait for {command:?}: {err}"); - return; + return None; } } } - let current_namespace_fd = libredox::call::getns().expect("TODO"); - libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) - .expect("TODO"); + let current_namespace_fd = match libredox::call::getns() { + Ok(fd) => fd, + Err(err) => { + eprintln!("init: failed to get current namespace for {command:?}: {err}"); + return None; + } + }; + if let Err(err) = + libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) + { + eprintln!("init: failed to register scheme {scheme:?} for {command:?}: {err}"); + } else { + eprintln!("init: {} ready (scheme {})", self.cmd, scheme); + } } ServiceType::Oneshot => { drop(read_pipe); @@ -105,6 +126,8 @@ impl Service { Ok(exit_status) => { if !exit_status.success() { eprintln!("init: {command:?} failed with {exit_status}"); + } else { + eprintln!("init: {} done (oneshot)", self.cmd); } } Err(err) => { @@ -112,8 +135,13 @@ impl Service { } } } - ServiceType::OneshotAsync => {} + ServiceType::OneshotAsync => { + if self.respawn { + return Some(child.id()); + } + } } + None } } diff --git a/init/src/unit.rs b/init/src/unit.rs index 98053cb2..a58bfb96 100644 --- a/init/src/unit.rs +++ b/init/src/unit.rs @@ -23,8 +23,14 @@ impl UnitStore { } pub fn set_runtime_target(&mut self, unit_id: UnitId) { - assert!(self.runtime_target.is_none()); - assert!(self.units.contains_key(&unit_id)); + if self.runtime_target.is_some() { + eprintln!("init: runtime target already set, ignoring {}", unit_id.0); + return; + } + if !self.units.contains_key(&unit_id) { + eprintln!("init: runtime target {} not found in unit store", unit_id.0); + return; + } self.runtime_target = Some(unit_id); } @@ -85,8 +91,10 @@ impl UnitStore { let unit = self.load_single_unit(unit_id, errors); if let Some(unit) = unit { loaded_units.push(unit.clone()); - for dep in &self.unit(&unit).info.requires_weak { - pending_units.push(dep.clone()); + if let Some(u) = self.unit(&unit) { + for dep in &u.info.requires_weak { + pending_units.push(dep.clone()); + } } } } @@ -94,12 +102,12 @@ impl UnitStore { loaded_units } - pub fn unit(&self, unit: &UnitId) -> &Unit { - self.units.get(unit).unwrap() + pub fn unit(&self, unit: &UnitId) -> Option<&Unit> { + self.units.get(unit) } - pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit { - self.units.get_mut(unit).unwrap() + pub fn unit_mut(&mut self, unit: &UnitId) -> Option<&mut Unit> { + self.units.get_mut(unit) } } @@ -180,7 +188,7 @@ impl Unit { ) -> io::Result { let config = fs::read_to_string(config_path)?; - let Some(ext) = config_path.extension().map(|ext| ext.to_str().unwrap()) else { + let Some(ext) = config_path.extension().and_then(|ext| ext.to_str()) else { let script = Script::from_str(&config, errors)?; return Ok(Unit { id,