diff --git a/Cargo.lock b/Cargo.lock index 8d85123..25fd3aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,7 +166,7 @@ dependencies = [ [[package]] name = "bimini" -version = "0.11.0" +version = "0.11.1" dependencies = [ "bimini-core", "clap", @@ -179,7 +179,7 @@ dependencies = [ [[package]] name = "bimini-core" -version = "0.11.0" +version = "0.11.1" dependencies = [ "aws-sigv4", "base64", @@ -198,7 +198,7 @@ dependencies = [ [[package]] name = "bimini-macros" -version = "0.11.0" +version = "0.11.1" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 6204c13..804cefb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bimini" -version = "0.11.0" +version = "0.11.1" edition = "2021" [profile.release] diff --git a/bimini-core/Cargo.lock b/bimini-core/Cargo.lock index f0f2e23..78fb053 100644 --- a/bimini-core/Cargo.lock +++ b/bimini-core/Cargo.lock @@ -118,7 +118,7 @@ dependencies = [ [[package]] name = "bimini-core" -version = "0.11.0" +version = "0.11.1" dependencies = [ "aws-sigv4", "base64", @@ -137,7 +137,7 @@ dependencies = [ [[package]] name = "bimini-macros" -version = "0.11.0" +version = "0.11.1" dependencies = [ "proc-macro2", "quote", diff --git a/bimini-core/Cargo.toml b/bimini-core/Cargo.toml index e8d459e..048d967 100644 --- a/bimini-core/Cargo.toml +++ b/bimini-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bimini-core" -version = "0.11.0" +version = "0.11.1" edition = "2021" [dependencies] diff --git a/bimini-core/src/nix/mod.rs b/bimini-core/src/nix/mod.rs index d6cb38d..28102d3 100644 --- a/bimini-core/src/nix/mod.rs +++ b/bimini-core/src/nix/mod.rs @@ -9,6 +9,3 @@ pub use groups_iter::GroupsIter; mod signal_config; pub use signal_config::SignalConfig; - -mod spawn_directory; -pub use spawn_directory::SpawnDirectory; diff --git a/bimini-core/src/nix/signal_config.rs b/bimini-core/src/nix/signal_config.rs index 2c53a20..7964c27 100644 --- a/bimini-core/src/nix/signal_config.rs +++ b/bimini-core/src/nix/signal_config.rs @@ -1,36 +1,40 @@ -use nix::sys::signal; - use crate::error::BiminiResult; +use nix::{errno, sys::signal}; -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] pub struct SignalConfig { - parent_signals: signal::SigSet, - source_signals: signal::SigSet, + mask_set: signal::SigSet, + source_set: signal::SigSet, sigttin_action: signal::SigAction, sigttou_action: signal::SigAction, } -impl SignalConfig { - pub fn parent_signals(&self) -> &signal::SigSet { - &self.parent_signals - } - - pub fn source_signals(&self) -> &signal::SigSet { - &self.source_signals - } - - pub fn sigttin_action(&self) -> &signal::SigAction { - &self.sigttin_action - } - - pub fn sigttou_action(&self) -> &signal::SigAction { - &self.sigttou_action +impl Default for SignalConfig { + fn default() -> Self { + Self::new(&[ + // Un-blockable signals + signal::SIGKILL, + signal::SIGSTOP, + // Program signals + signal::SIGABRT, + signal::SIGBUS, + #[cfg(not(target_os = "linux"))] + signal::SIGEMT, + signal::SIGFPE, + signal::SIGILL, + signal::SIGIOT, + signal::SIGSEGV, + signal::SIGSYS, + signal::SIGTRAP, + ]) } +} - pub fn new(protected_signals: Vec) -> Self { - let mut parent_signals = signal::SigSet::all(); - for signal in protected_signals { - parent_signals.remove(signal) +impl SignalConfig { + pub fn new(protected_signals: &[signal::Signal]) -> Self { + let mut mask = signal::SigSet::all(); + for sig in protected_signals { + mask.remove(*sig); } let ignore_action = signal::SigAction::new( @@ -39,19 +43,19 @@ impl SignalConfig { signal::SigSet::empty(), ); - SignalConfig { - parent_signals, - source_signals: signal::SigSet::empty(), + Self { + mask_set: mask, + source_set: signal::SigSet::empty(), sigttin_action: ignore_action, sigttou_action: ignore_action, } } - pub fn mask(&mut self) -> BiminiResult<()> { - signal::sigprocmask( + pub fn mask(&mut self) -> BiminiResult<&mut Self> { + signal::pthread_sigmask( signal::SigmaskHow::SIG_SETMASK, - Some(&self.parent_signals), - Some(&mut self.source_signals), + Some(&self.mask_set), + Some(&mut self.source_set), )?; unsafe { @@ -59,39 +63,29 @@ impl SignalConfig { self.sigttou_action = signal::sigaction(signal::SIGTTOU, &self.sigttou_action)?; } - Ok(()) + Ok(self) } - pub fn unmask(&mut self) -> BiminiResult<()> { - signal::sigprocmask( + pub fn unmask(&mut self) -> BiminiResult<&mut Self> { + signal::pthread_sigmask( signal::SigmaskHow::SIG_SETMASK, - Some(&self.source_signals), + Some(&self.source_set), None, )?; - self.source_signals = signal::SigSet::empty(); - unsafe { signal::sigaction(signal::SIGTTIN, &self.sigttin_action)?; signal::sigaction(signal::SIGTTOU, &self.sigttou_action)?; } - Ok(()) + Ok(self) } -} -impl Default for SignalConfig { - fn default() -> Self { - SignalConfig::new(vec![ - signal::SIGFPE, - signal::SIGILL, - signal::SIGSEGV, - signal::SIGBUS, - signal::SIGABRT, - signal::SIGTRAP, - signal::SIGSYS, - signal::SIGTTIN, - signal::SIGTTOU, - ]) + pub fn mask_wait(&self) -> Result { + self.mask_set.wait() + } + + pub fn source_wait(&self) -> Result { + self.source_set.wait() } } diff --git a/bimini-core/src/nix/spawn_directory.rs b/bimini-core/src/nix/spawn_directory.rs deleted file mode 100644 index ba1e6dd..0000000 --- a/bimini-core/src/nix/spawn_directory.rs +++ /dev/null @@ -1,46 +0,0 @@ -use nix::unistd; -use std::{collections::HashMap, path::PathBuf}; - -use crate::{error::BiminiResult, nix::ToEnv}; - -#[derive(Clone, Debug)] -pub struct SpawnDirectory { - user: unistd::User, - path: PathBuf, - initial_dir: PathBuf, -} - -impl SpawnDirectory { - pub fn new(user: unistd::User, path: PathBuf) -> BiminiResult { - Ok(SpawnDirectory { - user, - path, - initial_dir: unistd::getcwd()?, - }) - } - - pub fn chdir(&self) -> BiminiResult<()> { - [&self.path, &self.user.dir, &PathBuf::from("/")] - .iter() - .find_map(|path| path.as_os_str().to_str().map(unistd::chdir)) - .transpose()?; - - Ok(()) - } -} - -impl ToEnv for SpawnDirectory { - fn to_env(&self) -> HashMap { - let mut env = HashMap::new(); - - if let Ok(path) = self.initial_dir.clone().into_os_string().into_string() { - env.insert("OLDPWD".to_string(), path); - } - - if let Ok(path) = self.path.clone().into_os_string().into_string() { - env.insert("PWD".to_string(), path); - } - - env - } -} diff --git a/bimini-core/src/nix/user_spec.rs b/bimini-core/src/nix/user_spec.rs index 9de05cc..331d7ad 100644 --- a/bimini-core/src/nix/user_spec.rs +++ b/bimini-core/src/nix/user_spec.rs @@ -8,8 +8,8 @@ use crate::{error::BiminiResult, nix::ToEnv}; #[derive(Clone, Debug)] pub struct UserSpec { - user: Option, - group: Option, + pub user: Option, + pub group: Option, } impl UserSpec { diff --git a/bimini-core/src/proc/child.rs b/bimini-core/src/proc/child.rs index 0ecdbb7..01ae3bc 100644 --- a/bimini-core/src/proc/child.rs +++ b/bimini-core/src/proc/child.rs @@ -1,12 +1,13 @@ use crate::{ aws::AwsClient, error::{BiminiError, BiminiResult}, - nix::{SignalConfig, SpawnDirectory, ToEnv, UserSpec}, + nix::{SignalConfig, ToEnv, UserSpec}, + proc::SpawnDirectory, vault::VaultClient, }; use derive_builder::Builder; use nix::{errno, unistd}; -use std::{collections::HashMap, env, os::unix::process::CommandExt, process}; +use std::{collections::HashMap, env, os::unix::process::CommandExt, path::PathBuf, process}; #[derive(Builder)] #[builder(build_fn(error = "BiminiError"), pattern = "owned")] @@ -17,7 +18,7 @@ pub struct Child<'a> { user_spec: Option, #[builder(default)] - spawn_directory: Option, + spawn_directory: Option, #[builder(default)] aws_client: Option, @@ -76,15 +77,23 @@ impl<'a> Child<'a> { proc.envs(user_spec.to_env()); } - if let Some(spawn_directory) = &self.spawn_directory { - tracing::debug!("Spawn directory provided, changing proc root"); - spawn_directory.chdir()?; - proc.envs(spawn_directory.to_env()); - } - + tracing::trace!("Changing proc working directory"); + let mut spawn_directory = SpawnDirectory::new( + self.spawn_directory.clone(), + self.user_spec + .as_ref() + .and_then(|user_spec| user_spec.user.clone()), + ); + spawn_directory.chdir(); + proc.envs(spawn_directory.to_env()); + + tracing::trace!("Creating a new proc group for child"); self.isolate()?; + + tracing::trace!("Clearing inherited sigmask"); self.signal_config.unmask()?; + tracing::trace!("Execing child proc"); let error = proc.exec(); if let Some(errno) = error.raw_os_error() { @@ -101,7 +110,7 @@ impl<'a> ToEnv for Child<'a> { (String::from("BIMINI"), String::from("true")), ( String::from("BIMINI_VERSION"), - String::from(env!("CARGO_PKG_VERSION")), + String::from(crate::PKG_VERSION), ), ( String::from("BIMINI_CHILD_PID"), diff --git a/bimini-core/src/proc/controller.rs b/bimini-core/src/proc/controller.rs index fd8c578..f6223fc 100644 --- a/bimini-core/src/proc/controller.rs +++ b/bimini-core/src/proc/controller.rs @@ -8,20 +8,12 @@ use nix::{ sys::{signal, wait}, unistd, }; -use std::{ - process::ExitCode, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - thread, -}; +use std::{process::ExitCode, thread}; #[derive(Debug, Default)] pub struct Controller { signal_config: SignalConfig, child_pid: Option, - sf_runlock: Arc, signal_forwarder: Option>>, } @@ -61,34 +53,38 @@ impl Controller { } #[tracing::instrument(skip_all)] - fn signal_forwarder( - sf_runlock: Arc, - signal_config: SignalConfig, - child_pid: unistd::Pid, - ) -> BiminiResult<()> { + fn signal_forwarder(signal_config: SignalConfig, child_pid: unistd::Pid) -> BiminiResult<()> { tracing::info!("Starting signal forwarding event loop"); - signal::pthread_sigmask( - signal::SigmaskHow::SIG_SETMASK, - Some(signal_config.source_signals()), - None, - )?; + loop { + let signal = signal_config.mask_wait(); - while sf_runlock.load(Ordering::Relaxed) { - match signal_config.parent_signals().wait() { - Ok(signal) if sf_runlock.load(Ordering::Relaxed) => { - tracing::info!("Passing signal to child: {signal}"); + tracing::info!("Received Signal({signal:?}), dispatching"); - signal::kill(child_pid, signal).map_err(|eno| { - if eno == errno::Errno::ESRCH { - tracing::warn!("Child was dead when forwarding signal"); - } - eno - })?; + match signal { + Ok(signal::Signal::SIGCHLD) => { + tracing::info!("Suppressing Signal(SIGCHLD)"); + } + + Ok(signal::Signal::SIGUSR1) => { + tracing::info!("Terminating signal_forwarder loop."); + return Ok(()); } Ok(signal) => { - tracing::trace!("Received signal {signal} after loop termination"); + tracing::info!("Passing Signal({signal}) to Child(pid={child_pid})"); + + if let Err(errno) = signal::kill(child_pid, signal) { + match errno { + errno::Errno::ESRCH => tracing::warn!( + "Child(pid={child_pid}) was dead when forwarding, Signal({signal}) dropped." + ), + errno => { + tracing::error!("Received un-handled error passing signal to child: {errno}"); + return Err(BiminiError::Errno(errno)) + }, + } + } } Err(errno @ errno::Errno::EAGAIN | errno @ errno::Errno::EINTR) => { @@ -101,8 +97,6 @@ impl Controller { } } } - - Ok(()) } #[tracing::instrument(skip_all)] @@ -115,14 +109,11 @@ impl Controller { )); } - self.sf_runlock.store(true, Ordering::Release); - - let runlock_clone = Arc::clone(&self.sf_runlock); let signal_config = self.signal_config.clone(); let child_pid = self.child_pid.unwrap(); self.signal_forwarder = Some(thread::spawn(move || { - Controller::signal_forwarder(runlock_clone, signal_config, child_pid) + Controller::signal_forwarder(signal_config, child_pid) })); Ok(self) @@ -146,7 +137,7 @@ impl Controller { match wait::waitpid(any_proc, None) { Ok(wait::WaitStatus::Exited(pid, status)) if pid == child_pid => { tracing::info!( - "Controller child process {pid} exited normally with status {status}." + "Controller child process Child(pid={pid}) exited normally with status {status}." ); child_exitcode = status; } @@ -208,24 +199,22 @@ impl Controller { pub fn run_reaper(self) -> BiminiResult { loop { tracing::debug!("Running zombie reaper loop."); - let rc = self.reap_zombies()?; - if rc != -1 { - tracing::trace!("Received child status code: {rc}. Cleaning up"); - if let Some(signal_forwarder) = self.signal_forwarder { - tracing::trace!("Releasing signal_forwarding loop runlock"); - self.sf_runlock.store(false, Ordering::Release); - - tracing::trace!("Sending final SIGTERM to signal_forwarding thread"); - signal::kill(unistd::Pid::from_raw(0), signal::SIGTERM)?; + if rc == -1 { + continue; + } - tracing::trace!("Joining signal_forwarding thread"); - signal_forwarder.join()??; - } + tracing::trace!("Received child status code: {rc}. Cleaning up"); + if let Some(signal_forwarder) = self.signal_forwarder { + tracing::trace!("Sending termination signal to signal_forwarding thread"); + signal::kill(unistd::getpid(), signal::SIGUSR1)?; - return Ok(ExitCode::from(TryInto::::try_into(rc)?)); + tracing::trace!("Joining signal_forwarding thread"); + signal_forwarder.join()??; } + + return Ok(ExitCode::from(TryInto::::try_into(rc)?)); } } } diff --git a/bimini-core/src/proc/mod.rs b/bimini-core/src/proc/mod.rs index 028ee37..1e27efe 100644 --- a/bimini-core/src/proc/mod.rs +++ b/bimini-core/src/proc/mod.rs @@ -3,3 +3,6 @@ pub use child::{Child, ChildBuilder}; mod controller; pub use controller::Controller; + +mod spawn_directory; +pub use spawn_directory::SpawnDirectory; diff --git a/bimini-core/src/proc/spawn_directory.rs b/bimini-core/src/proc/spawn_directory.rs new file mode 100644 index 0000000..b2833ab --- /dev/null +++ b/bimini-core/src/proc/spawn_directory.rs @@ -0,0 +1,91 @@ +use crate::nix::ToEnv; +use nix::unistd; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +pub struct SpawnDirectory { + path: Option, + user: Option, + + pwd: Option, + old_pwd: Option, +} + +impl Default for SpawnDirectory { + fn default() -> Self { + Self { + path: None, + user: None, + pwd: unistd::getcwd().ok(), + old_pwd: None, + } + } +} + +impl ToEnv for SpawnDirectory { + fn to_env(&self) -> std::collections::HashMap { + HashMap::from([ + ( + "OLDPWD".to_string(), + self.old_pwd + .as_deref() + .and_then(Path::to_str) + .map(String::from) + .unwrap_or_default(), + ), + ( + "PWD".to_string(), + self.pwd + .as_deref() + .and_then(Path::to_str) + .map(String::from) + .unwrap_or_default(), + ), + ]) + } +} + +impl SpawnDirectory { + pub fn new(path: Option, user: Option) -> Self { + Self { + path, + user, + ..Default::default() + } + } + + pub fn chdir(&mut self) { + let old_pwd = self.pwd.clone(); + + let target_paths = [ + &self.path, + &self.user.as_ref().map(|user| user.dir.clone()), + &self.pwd, + &Some(PathBuf::from("/")), + ]; + + for target_path in target_paths { + match target_path.as_deref().map(unistd::chdir) { + Some(Ok(_)) => break, + + Some(Err(errno)) => { + tracing::warn!( + "chdir failed for {:?} - {errno}", + target_path.as_ref().unwrap() + ); + } + + None => {} + } + } + + let pwd = unistd::getcwd().ok(); + + if pwd != old_pwd { + self.old_pwd = old_pwd; + self.pwd = pwd; + } + } +} diff --git a/bimini-macros/Cargo.toml b/bimini-macros/Cargo.toml index d6e8c63..801efef 100644 --- a/bimini-macros/Cargo.toml +++ b/bimini-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bimini-macros" -version = "0.11.0" +version = "0.11.1" edition = "2021" [lib] diff --git a/src/main.rs b/src/main.rs index 714337f..2906ce7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use bimini_core::{ }, }; use clap::{builder::ArgPredicate, Parser}; -use std::{process, str::FromStr}; +use std::{path::PathBuf, process, str::FromStr}; use tracing_subscriber::{filter::LevelFilter, prelude::*, Registry}; #[derive(clap::ValueEnum, Clone, Debug)] @@ -75,7 +75,7 @@ struct CliArgs { long("spawn-directory"), env("BIMINI_SPAWN_DIRECTORY") )] - spawn_directory: Option, + spawn_directory: Option, /// Enable Amazon Web Services client. #[clap( @@ -216,7 +216,6 @@ struct CliArgs { long, env, groups(["vault-creds-provider"]), - requires("enable_aws_client"), requires("vault_addr") )] vault_role: Option, @@ -477,6 +476,7 @@ fn main() -> BiminiResult { .map(|s| UserSpec::from_str(s.as_str())) .transpose()?, ) + .spawn_directory(cli_args.spawn_directory) .aws_client(if cli_args.enable_aws_creds_env_injection { aws_client } else {