Establish bounded descriptor budgets for sandbox domains
This commit is contained in:
parent
42e197510e
commit
503f090121
3 changed files with 191 additions and 12 deletions
|
|
@ -64,5 +64,10 @@ harness = false
|
|||
name = "cold-start"
|
||||
path = "tests/cold_start.rs"
|
||||
|
||||
[[test]]
|
||||
name = "nofile"
|
||||
path = "tests/nofile.rs"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ use lazaret_core::identity::{
|
|||
};
|
||||
use lazaret_core::records::{Domain, Session, SessionPhase};
|
||||
use lazaret_linux::filesystem::try_exclusive_flock;
|
||||
use lazaret_runtime_supervisor::sandbox::default_sandbox_layout;
|
||||
use lazaret_runtime_supervisor::sandbox::{DOMAIN_OPEN_FILE_LIMIT, default_sandbox_layout};
|
||||
use rustix::fs::{Mode, OFlags, openat};
|
||||
use rustix::process::{Resource, Rlimit, getrlimit, setrlimit};
|
||||
|
||||
use crate::bootstrap::{CoordinatorLifecycle, serve_control_connection_with_escalation};
|
||||
use crate::cold_start::{BootstrapOwnerGuard, ColdStart, ColdStartError};
|
||||
|
|
@ -73,6 +74,7 @@ pub fn dispatch_private_client_bootstrap(
|
|||
|
||||
fn run_private_client_bootstrap(arguments: &[OsString]) -> Result<(), PrivateClientBootstrapError> {
|
||||
let parsed = parse_arguments(arguments).inspect_err(|_| signal(b'E'))?;
|
||||
ensure_domain_nofile_limit().inspect_err(|_| signal(b'E'))?;
|
||||
let owner = BootstrapOwnerGuard::from_stdin()
|
||||
.map_err(failure)
|
||||
.inspect_err(|_| signal(b'E'))?;
|
||||
|
|
@ -158,6 +160,51 @@ fn run_private_client_bootstrap(arguments: &[OsString]) -> Result<(), PrivateCli
|
|||
.map_err(failure)
|
||||
}
|
||||
|
||||
/// Establishes the descriptor budget inherited by one Domain runtime.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error when the hard limit is insufficient or the soft limit cannot be raised.
|
||||
pub fn ensure_domain_nofile_limit() -> Result<(), PrivateClientBootstrapError> {
|
||||
let inherited = getrlimit(Resource::Nofile);
|
||||
let Some(required) = required_nofile_limit(inherited)? else {
|
||||
return Ok(());
|
||||
};
|
||||
setrlimit(Resource::Nofile, required).map_err(|error| {
|
||||
failure(format!(
|
||||
"raising Host Coordinator RLIMIT_NOFILE from {} to {} failed: {error}",
|
||||
display_limit(inherited.current),
|
||||
DOMAIN_OPEN_FILE_LIMIT
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn required_nofile_limit(inherited: Rlimit) -> Result<Option<Rlimit>, PrivateClientBootstrapError> {
|
||||
if inherited
|
||||
.maximum
|
||||
.is_some_and(|maximum| maximum < DOMAIN_OPEN_FILE_LIMIT)
|
||||
{
|
||||
return Err(failure(format!(
|
||||
"Host Coordinator requires RLIMIT_NOFILE {DOMAIN_OPEN_FILE_LIMIT}, but inherited soft/hard limits are {}/{}",
|
||||
display_limit(inherited.current),
|
||||
display_limit(inherited.maximum)
|
||||
)));
|
||||
}
|
||||
if inherited
|
||||
.current
|
||||
.is_none_or(|current| current >= DOMAIN_OPEN_FILE_LIMIT)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(Rlimit {
|
||||
current: Some(DOMAIN_OPEN_FILE_LIMIT),
|
||||
maximum: inherited.maximum,
|
||||
}))
|
||||
}
|
||||
|
||||
fn display_limit(limit: Option<u64>) -> String {
|
||||
limit.map_or_else(|| "unlimited".to_owned(), |limit| limit.to_string())
|
||||
}
|
||||
|
||||
fn serve_published(
|
||||
lifecycle: &Mutex<CoordinatorLifecycle<'_, '_>>,
|
||||
waiters: &RefCell<BootstrapWaiterFace>,
|
||||
|
|
@ -495,6 +542,57 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn host_coordinator_plans_a_bounded_raise_from_a_low_soft_nofile_limit() {
|
||||
assert_eq!(
|
||||
required_nofile_limit(Rlimit {
|
||||
current: Some(1_024),
|
||||
maximum: Some(DOMAIN_OPEN_FILE_LIMIT),
|
||||
})
|
||||
.unwrap(),
|
||||
Some(Rlimit {
|
||||
current: Some(DOMAIN_OPEN_FILE_LIMIT),
|
||||
maximum: Some(DOMAIN_OPEN_FILE_LIMIT),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_coordinator_refuses_an_insufficient_hard_nofile_limit() {
|
||||
let error = required_nofile_limit(Rlimit {
|
||||
current: Some(1_024),
|
||||
maximum: Some(DOMAIN_OPEN_FILE_LIMIT - 1),
|
||||
})
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!(
|
||||
"Host Coordinator requires RLIMIT_NOFILE {DOMAIN_OPEN_FILE_LIMIT}, but inherited soft/hard limits are 1024/{}",
|
||||
DOMAIN_OPEN_FILE_LIMIT - 1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_coordinator_preserves_sufficient_and_unlimited_nofile_limits() {
|
||||
for limit in [
|
||||
Rlimit {
|
||||
current: Some(DOMAIN_OPEN_FILE_LIMIT),
|
||||
maximum: Some(DOMAIN_OPEN_FILE_LIMIT),
|
||||
},
|
||||
Rlimit {
|
||||
current: Some(DOMAIN_OPEN_FILE_LIMIT + 1),
|
||||
maximum: None,
|
||||
},
|
||||
Rlimit {
|
||||
current: None,
|
||||
maximum: None,
|
||||
},
|
||||
] {
|
||||
assert_eq!(required_nofile_limit(limit).unwrap(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_bootstrap_arguments_are_exact_and_public() {
|
||||
let arguments = [
|
||||
|
|
|
|||
39
crates/lazaret-host-coordinator/tests/nofile.rs
Normal file
39
crates/lazaret-host-coordinator/tests/nofile.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use std::process::ExitCode;
|
||||
|
||||
use lazaret_host_coordinator::client_bootstrap::ensure_domain_nofile_limit;
|
||||
use lazaret_runtime_supervisor::sandbox::DOMAIN_OPEN_FILE_LIMIT;
|
||||
use rustix::process::{Resource, Rlimit, getrlimit, setrlimit};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match verify_low_inherited_soft_limit() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
eprintln!("NOFILE fixture failed: {error}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_low_inherited_soft_limit() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let inherited = getrlimit(Resource::Nofile);
|
||||
if let Some(maximum) = inherited.maximum
|
||||
&& maximum < DOMAIN_OPEN_FILE_LIMIT
|
||||
{
|
||||
return Err(
|
||||
format!("test host hard limit {maximum} is below {DOMAIN_OPEN_FILE_LIMIT}").into(),
|
||||
);
|
||||
}
|
||||
setrlimit(
|
||||
Resource::Nofile,
|
||||
Rlimit {
|
||||
current: Some(1_024),
|
||||
maximum: inherited.maximum,
|
||||
},
|
||||
)?;
|
||||
ensure_domain_nofile_limit()?;
|
||||
let observed = getrlimit(Resource::Nofile);
|
||||
if observed.current != Some(DOMAIN_OPEN_FILE_LIMIT) || observed.maximum != inherited.maximum {
|
||||
return Err(format!("unexpected raised limit: {observed:?}").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Reference in a new issue