Centralize sandbox destination authority

This commit is contained in:
ms2k 2026-07-19 04:36:44 +09:00
commit f8849cdb5f
15 changed files with 112 additions and 48 deletions

10
Cargo.lock generated
View file

@ -1646,6 +1646,7 @@ dependencies = [
"lazaret-core", "lazaret-core",
"lazaret-domain-state", "lazaret-domain-state",
"lazaret-linux", "lazaret-linux",
"lazaret-sandbox-contract",
"nix 0.31.3", "nix 0.31.3",
"rustix", "rustix",
"rustls", "rustls",
@ -1685,6 +1686,7 @@ version = "0.0.0"
dependencies = [ dependencies = [
"lazaret-core", "lazaret-core",
"lazaret-linux", "lazaret-linux",
"lazaret-sandbox-contract",
"lazaret-state-anchor", "lazaret-state-anchor",
"redb", "redb",
"rustix", "rustix",
@ -1787,6 +1789,7 @@ dependencies = [
"lazaret-core", "lazaret-core",
"lazaret-ignored-workspace", "lazaret-ignored-workspace",
"lazaret-linux", "lazaret-linux",
"lazaret-sandbox-contract",
"lazaret-state-anchor", "lazaret-state-anchor",
"oci-spec 0.10.0", "oci-spec 0.10.0",
"rustix", "rustix",
@ -1796,6 +1799,13 @@ dependencies = [
"tempfile", "tempfile",
] ]
[[package]]
name = "lazaret-sandbox-contract"
version = "0.0.0"
dependencies = [
"lazaret-core",
]
[[package]] [[package]]
name = "lazaret-state-anchor" name = "lazaret-state-anchor"
version = "0.0.0" version = "0.0.0"

View file

@ -10,6 +10,7 @@ members = [
"crates/lazaret-linux", "crates/lazaret-linux",
"crates/lazaret-prepared-lower", "crates/lazaret-prepared-lower",
"crates/lazaret-runtime-supervisor", "crates/lazaret-runtime-supervisor",
"crates/lazaret-sandbox-contract",
"crates/lazaret-state-anchor", "crates/lazaret-state-anchor",
"crates/lazaret-storage-catalog", "crates/lazaret-storage-catalog",
] ]

View file

@ -17,6 +17,7 @@ lazaret-control-client = { version = "=0.0.0", path = "../lazaret-control-client
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" } lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
lazaret-domain-state = { version = "=0.0.0", path = "../lazaret-domain-state" } lazaret-domain-state = { version = "=0.0.0", path = "../lazaret-domain-state" }
lazaret-linux = { version = "=0.0.0", path = "../lazaret-linux" } lazaret-linux = { version = "=0.0.0", path = "../lazaret-linux" }
lazaret-sandbox-contract = { version = "=0.0.0", path = "../lazaret-sandbox-contract" }
rustix.workspace = true rustix.workspace = true
rustls.workspace = true rustls.workspace = true
sha2.workspace = true sha2.workspace = true

View file

@ -463,7 +463,7 @@ pub(crate) fn load_configuration(
xdg_data_home: optional_environment_path("XDG_DATA_HOME"), xdg_data_home: optional_environment_path("XDG_DATA_HOME"),
xdg_runtime_dir: required_environment_path("XDG_RUNTIME_DIR")?, xdg_runtime_dir: required_environment_path("XDG_RUNTIME_DIR")?,
environment: environment(), environment: environment(),
sandbox_layout: lazaret_core::configuration::SandboxLayoutV1::default_layout()?, sandbox_layout: lazaret_sandbox_contract::default_sandbox_layout()?,
})?) })?)
} }
@ -688,7 +688,7 @@ mod tests {
xdg_data_home: None, xdg_data_home: None,
xdg_runtime_dir: runtime, xdg_runtime_dir: runtime,
environment: BTreeMap::new(), environment: BTreeMap::new(),
sandbox_layout: lazaret_core::configuration::SandboxLayoutV1::default_layout().unwrap(), sandbox_layout: lazaret_sandbox_contract::default_sandbox_layout().unwrap(),
}) })
.unwrap() .unwrap()
} }

View file

@ -128,20 +128,6 @@ impl fmt::Debug for SandboxLayoutV1 {
} }
impl SandboxLayoutV1 { impl SandboxLayoutV1 {
/// Returns the fixed protected destination set shared by all executable roles.
///
/// # Errors
/// Returns an error only if the built-in layout violates the layout contract.
pub fn default_layout() -> Result<Self, ConfigurationError> {
Self::from_protected_destinations([
"/workspace".to_owned(),
"/run/lazaret".to_owned(),
"/proc".to_owned(),
"/sys".to_owned(),
"/dev".to_owned(),
])
}
/// Builds the closed destination set supplied by default sandbox-layout construction. /// Builds the closed destination set supplied by default sandbox-layout construction.
/// ///
/// # Errors /// # Errors

View file

@ -13,6 +13,7 @@ redb.workspace = true
serde.workspace = true serde.workspace = true
[dev-dependencies] [dev-dependencies]
lazaret-sandbox-contract = { version = "=0.0.0", path = "../lazaret-sandbox-contract" }
rustix.workspace = true rustix.workspace = true
tempfile = "=3.27.0" tempfile = "=3.27.0"

View file

@ -490,7 +490,7 @@ mod tests {
xdg_data_home: None, xdg_data_home: None,
xdg_runtime_dir: runtime, xdg_runtime_dir: runtime,
environment: BTreeMap::new(), environment: BTreeMap::new(),
sandbox_layout: lazaret_core::configuration::SandboxLayoutV1::default_layout().unwrap(), sandbox_layout: lazaret_sandbox_contract::default_sandbox_layout().unwrap(),
}) })
.unwrap() .unwrap()
} }

View file

@ -21,9 +21,10 @@ use lazaret_prepared_lower::{
}; };
use lazaret_runtime_supervisor::network::{DnsProbeTarget, NetworkProbeRequest, TcpProbeTarget}; use lazaret_runtime_supervisor::network::{DnsProbeTarget, NetworkProbeRequest, TcpProbeTarget};
use lazaret_runtime_supervisor::sandbox::{ use lazaret_runtime_supervisor::sandbox::{
CREDENTIALS_DESTINATION, ESCALATION_CLIENT_DESTINATION, MAILBOX_REQUESTS_DESTINATION, CREDENTIALS_DESTINATION, DEV_DESTINATION, ESCALATION_CLIENT_DESTINATION,
MAILBOX_RESULTS_DESTINATION, RootProcess, SANDBOX_INIT_DESTINATION, TERMINFO_ROOT_DESTINATION, MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION, NETWORK_NAMESPACE_PATH,
WORKSPACE_DESTINATION, PROC_DESTINATION, RootProcess, SANDBOX_INIT_DESTINATION, SYS_DESTINATION,
TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION,
}; };
use lazaret_storage_catalog::{Catalog, CatalogError}; use lazaret_storage_catalog::{Catalog, CatalogError};
use rustix::fs::{Mode, OFlags, ResolveFlags, openat2}; use rustix::fs::{Mode, OFlags, ResolveFlags, openat2};
@ -33,8 +34,6 @@ use crate::domain_store::HostCoordinatorDomainStore;
use crate::recovery::recover_before_cold_start; use crate::recovery::recover_before_cold_start;
use crate::sandbox_init::PRIVATE_SANDBOX_INIT_ROLE; use crate::sandbox_init::PRIVATE_SANDBOX_INIT_ROLE;
const NETWORK_NAMESPACE_PATH: &str = "/proc/self/ns/net";
#[derive(Debug)] #[derive(Debug)]
pub enum RuntimeSelectionError { pub enum RuntimeSelectionError {
PreparedLower(PreparedLowerError), PreparedLower(PreparedLowerError),
@ -240,9 +239,9 @@ async fn select_runtime_for_platform(
fn prepared_destinations(configuration: &ResolvedDomainConfiguration) -> Vec<PreparedDestination> { fn prepared_destinations(configuration: &ResolvedDomainConfiguration) -> Vec<PreparedDestination> {
let mut destinations = [ let mut destinations = [
"/dev", DEV_DESTINATION,
"/proc", PROC_DESTINATION,
"/sys", SYS_DESTINATION,
"/tmp", "/tmp",
"/etc", "/etc",
"/home", "/home",

View file

@ -726,7 +726,7 @@ mod tests {
xdg_data_home: None, xdg_data_home: None,
xdg_runtime_dir: runtime, xdg_runtime_dir: runtime,
environment: BTreeMap::new(), environment: BTreeMap::new(),
sandbox_layout: lazaret_core::configuration::SandboxLayoutV1::default_layout().unwrap(), sandbox_layout: lazaret_runtime_supervisor::sandbox::default_sandbox_layout().unwrap(),
}) })
.unwrap() .unwrap()
} }

View file

@ -14,6 +14,7 @@ hickory-proto.workspace = true
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" } lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
lazaret-ignored-workspace = { version = "=0.0.0", path = "../lazaret-ignored-workspace" } lazaret-ignored-workspace = { version = "=0.0.0", path = "../lazaret-ignored-workspace" }
lazaret-linux = { version = "=0.0.0", path = "../lazaret-linux" } lazaret-linux = { version = "=0.0.0", path = "../lazaret-linux" }
lazaret-sandbox-contract = { version = "=0.0.0", path = "../lazaret-sandbox-contract" }
lazaret-state-anchor = { version = "=0.0.0", path = "../lazaret-state-anchor" } lazaret-state-anchor = { version = "=0.0.0", path = "../lazaret-state-anchor" }
oci-spec.workspace = true oci-spec.workspace = true
serde.workspace = true serde.workspace = true

View file

@ -10,6 +10,12 @@ use std::path::{Component, Path, PathBuf};
use lazaret_core::configuration::{ConfinedPathPlan, ResolvedDomainConfiguration, SandboxLayoutV1}; use lazaret_core::configuration::{ConfinedPathPlan, ResolvedDomainConfiguration, SandboxLayoutV1};
use lazaret_core::identity::{ConfigurationFingerprint, ContainerId, DomainId, NamespaceIdentity}; use lazaret_core::identity::{ConfigurationFingerprint, ContainerId, DomainId, NamespaceIdentity};
use lazaret_core::records::VolumeAccess; use lazaret_core::records::VolumeAccess;
pub use lazaret_sandbox_contract::{
CONTROL_ROOT_DESTINATION, CREDENTIALS_DESTINATION, DEV_DESTINATION,
ESCALATION_CLIENT_DESTINATION, MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION,
PROC_DESTINATION, SANDBOX_INIT_DESTINATION, SYS_DESTINATION, TERMINFO_ROOT_DESTINATION,
WORKSPACE_DESTINATION,
};
use oci_spec::runtime::{ use oci_spec::runtime::{
Capabilities, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxIdMappingBuilder, Capabilities, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxIdMappingBuilder,
LinuxNamespaceBuilder, LinuxNamespaceType, Mount, MountBuilder, ProcessBuilder, RootBuilder, LinuxNamespaceBuilder, LinuxNamespaceType, Mount, MountBuilder, ProcessBuilder, RootBuilder,
@ -18,14 +24,6 @@ use oci_spec::runtime::{
use rustix::fs::{AtFlags, FileType, Mode, OFlags, ResolveFlags, StatxFlags, openat2, statx}; use rustix::fs::{AtFlags, FileType, Mode, OFlags, ResolveFlags, StatxFlags, openat2, statx};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
pub const WORKSPACE_DESTINATION: &str = "/workspace";
pub const CONTROL_ROOT_DESTINATION: &str = "/run/lazaret";
pub const CREDENTIALS_DESTINATION: &str = "/run/lazaret/credentials";
pub const MAILBOX_REQUESTS_DESTINATION: &str = "/run/lazaret/mailbox/requests";
pub const MAILBOX_RESULTS_DESTINATION: &str = "/run/lazaret/mailbox/results";
pub const ESCALATION_CLIENT_DESTINATION: &str = "/run/lazaret/bin/lazaret-escalate";
pub const SANDBOX_INIT_DESTINATION: &str = "/run/lazaret/bin/lazaret-sandbox-init";
pub const TERMINFO_ROOT_DESTINATION: &str = "/run/lazaret/terminfo";
pub const NETWORK_NAMESPACE_PATH: &str = "/proc/self/ns/net"; pub const NETWORK_NAMESPACE_PATH: &str = "/proc/self/ns/net";
const OCI_VERSION: &str = "1.2.1"; const OCI_VERSION: &str = "1.2.1";
@ -604,7 +602,7 @@ impl SandboxBundle {
/// # Errors /// # Errors
/// Returns an error if the fixed layout violates the configuration layout contract. /// Returns an error if the fixed layout violates the configuration layout contract.
pub fn default_sandbox_layout() -> Result<SandboxLayoutV1, SandboxBuildError> { pub fn default_sandbox_layout() -> Result<SandboxLayoutV1, SandboxBuildError> {
SandboxLayoutV1::default_layout().map_err(|error| { lazaret_sandbox_contract::default_sandbox_layout().map_err(|error| {
SandboxBuildError::new( SandboxBuildError::new(
SandboxBuildErrorKind::InvalidPath, SandboxBuildErrorKind::InvalidPath,
format!("default sandbox layout is invalid: {error}"), format!("default sandbox layout is invalid: {error}"),

View file

@ -1,8 +1,13 @@
fn standard_mounts() -> Result<Vec<Mount>, SandboxBuildError> { fn standard_mounts() -> Result<Vec<Mount>, SandboxBuildError> {
[ [
("/proc", "proc", "proc", vec!["nosuid", "noexec", "nodev"]),
( (
"/dev", PROC_DESTINATION,
"proc",
"proc",
vec!["nosuid", "noexec", "nodev"],
),
(
DEV_DESTINATION,
"tmpfs", "tmpfs",
"tmpfs", "tmpfs",
vec!["nosuid", "strictatime", "mode=755", "size=65536k"], vec!["nosuid", "strictatime", "mode=755", "size=65536k"],
@ -32,7 +37,7 @@ fn standard_mounts() -> Result<Vec<Mount>, SandboxBuildError> {
vec!["nosuid", "noexec", "nodev"], vec!["nosuid", "noexec", "nodev"],
), ),
( (
"/sys", SYS_DESTINATION,
"sysfs", "sysfs",
"sysfs", "sysfs",
vec!["nosuid", "noexec", "nodev", "ro"], vec!["nosuid", "noexec", "nodev", "ro"],
@ -107,4 +112,3 @@ fn bind_mount(
.build() .build()
.map_err(oci_error) .map_err(oci_error)
} }

View file

@ -9,10 +9,11 @@ use lazaret_core::configuration::{
}; };
use lazaret_core::identity::{ContainerId, DeviceId, InodeId, NamespaceIdentity}; use lazaret_core::identity::{ContainerId, DeviceId, InodeId, NamespaceIdentity};
use lazaret_runtime_supervisor::sandbox::{ use lazaret_runtime_supervisor::sandbox::{
CREDENTIALS_DESTINATION, ESCALATION_CLIENT_DESTINATION, HostTerminal, CONTROL_ROOT_DESTINATION, CREDENTIALS_DESTINATION, DEV_DESTINATION,
MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION, NETWORK_NAMESPACE_PATH, RootProcess, ESCALATION_CLIENT_DESTINATION, HostTerminal, MAILBOX_REQUESTS_DESTINATION,
SANDBOX_INIT_DESTINATION, SandboxBuildErrorKind, SandboxBuildRequest, SandboxBundle, MAILBOX_RESULTS_DESTINATION, NETWORK_NAMESPACE_PATH, PROC_DESTINATION, RootProcess,
TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION, build_sandbox_bundle, SANDBOX_INIT_DESTINATION, SYS_DESTINATION, SandboxBuildErrorKind, SandboxBuildRequest,
SandboxBundle, TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION, build_sandbox_bundle,
build_sandbox_bundle_with_workspace, default_sandbox_layout, stage_host_terminal, build_sandbox_bundle_with_workspace, default_sandbox_layout, stage_host_terminal,
}; };
use serde_json::Value; use serde_json::Value;
@ -648,11 +649,11 @@ fn default_layout_reserves_every_mount_root() {
assert_eq!( assert_eq!(
layout.protected_destinations(), layout.protected_destinations(),
&BTreeSet::from([ &BTreeSet::from([
"/dev".to_owned(), DEV_DESTINATION.to_owned(),
"/proc".to_owned(), PROC_DESTINATION.to_owned(),
"/run/lazaret".to_owned(), CONTROL_ROOT_DESTINATION.to_owned(),
"/sys".to_owned(), SYS_DESTINATION.to_owned(),
"/workspace".to_owned(), WORKSPACE_DESTINATION.to_owned(),
]) ])
); );
assert_eq!(TERMINFO_ROOT_DESTINATION, "/run/lazaret/terminfo"); assert_eq!(TERMINFO_ROOT_DESTINATION, "/run/lazaret/terminfo");

View file

@ -0,0 +1,12 @@
[package]
name = "lazaret-sandbox-contract"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
[lints]
workspace = true

View file

@ -0,0 +1,50 @@
#![doc = "Shared destination authority for configuration resolution and sandbox bundles."]
use lazaret_core::configuration::{ConfigurationError, SandboxLayoutV1};
pub const WORKSPACE_DESTINATION: &str = "/workspace";
pub const CONTROL_ROOT_DESTINATION: &str = "/run/lazaret";
pub const CREDENTIALS_DESTINATION: &str = "/run/lazaret/credentials";
pub const MAILBOX_REQUESTS_DESTINATION: &str = "/run/lazaret/mailbox/requests";
pub const MAILBOX_RESULTS_DESTINATION: &str = "/run/lazaret/mailbox/results";
pub const ESCALATION_CLIENT_DESTINATION: &str = "/run/lazaret/bin/lazaret-escalate";
pub const SANDBOX_INIT_DESTINATION: &str = "/run/lazaret/bin/lazaret-sandbox-init";
pub const TERMINFO_ROOT_DESTINATION: &str = "/run/lazaret/terminfo";
pub const PROC_DESTINATION: &str = "/proc";
pub const SYS_DESTINATION: &str = "/sys";
pub const DEV_DESTINATION: &str = "/dev";
/// Returns the protected roots derived from the destinations owned by sandbox construction.
///
/// # Errors
/// Returns an error if the fixed destination contract is not a closed normalized layout.
pub fn default_sandbox_layout() -> Result<SandboxLayoutV1, ConfigurationError> {
SandboxLayoutV1::from_protected_destinations([
WORKSPACE_DESTINATION.to_owned(),
CONTROL_ROOT_DESTINATION.to_owned(),
PROC_DESTINATION.to_owned(),
SYS_DESTINATION.to_owned(),
DEV_DESTINATION.to_owned(),
])
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::*;
#[test]
fn protected_roots_are_derived_from_bundle_destinations() {
assert_eq!(
default_sandbox_layout().unwrap().protected_destinations(),
&BTreeSet::from([
DEV_DESTINATION.to_owned(),
PROC_DESTINATION.to_owned(),
CONTROL_ROOT_DESTINATION.to_owned(),
SYS_DESTINATION.to_owned(),
WORKSPACE_DESTINATION.to_owned(),
])
);
}
}