Own the patched runsc artifact and bind recovery identity

This commit is contained in:
ms2k 2026-07-26 18:01:03 +09:00
commit 2c420012bd
13 changed files with 1967 additions and 130 deletions

View file

@ -4,7 +4,9 @@ use std::sync::Arc;
use std::sync::Mutex;
use lazaret_core::configuration::ResolvedDomainConfiguration;
use lazaret_core::identity::{BundleDigest, ContainerId, HostPath, MountIdentity};
use lazaret_core::identity::{
BundleDigest, ContainerId, HostPath, MountIdentity, RunscBuildId, RunscDigest,
};
use lazaret_core::network::CanonicalDerivedNetworkPolicy;
use lazaret_core::records::Session;
use lazaret_core::records::{
@ -33,7 +35,7 @@ use crate::lifecycle::{
};
use crate::network::NetworkCoordinator;
use crate::runtime::{
AwaitingAttachedRunsc, CommittedSandboxRuntime, SandboxCoordinator,
AwaitingAttachedRunsc, CommittedSandboxRuntime, SandboxCoordinator, SandboxIntent,
StateAnchorAbsenceObservation,
};
use crate::state_anchor::{StateAnchorCoordinator, StateAnchorReleaseAuthority};
@ -74,6 +76,9 @@ pub struct BootstrapPlan<'a> {
pub container_id: ContainerId,
pub runtime_root: PathBuf,
pub bundle_digest: BundleDigest,
pub runsc_program: PathBuf,
pub runsc_build_id: RunscBuildId,
pub runsc_digest: RunscDigest,
pub invoking_uid: u32,
pub non_interactive_executor: Arc<dyn NonInteractiveSessionExecutor>,
pub interactive_executor: Arc<dyn InteractiveSessionExecutor>,
@ -263,17 +268,20 @@ impl<'a> CoordinatorLifecycleAdapter<'a> {
fn start_runtime_supervisor(&mut self) -> Result<(), LifecycleServiceError> {
let sandbox = self.sandbox();
sandbox
.begin_intent(
self.plan.generation_identity.generation_id,
self.plan.container_id,
&self.plan.runtime_root,
self.plan
.begin_intent(SandboxIntent {
generation_id: self.plan.generation_identity.generation_id,
container_id: self.plan.container_id,
runtime_root: &self.plan.runtime_root,
runtime_root_identity: self
.plan
.runtime_staging
.runsc_root_identity()
.map_err(failure)?,
self.plan.bundle_digest,
current_process_identity().map_err(failure)?,
)
bundle_digest: self.plan.bundle_digest,
runsc_build_id: self.plan.runsc_build_id,
runsc_digest: self.plan.runsc_digest,
host_coordinator: current_process_identity().map_err(failure)?,
})
.map_err(failure)?;
let launch = &self.plan.runtime_launch;
self.awaiting_runtime = Some(

View file

@ -17,7 +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::{DOMAIN_OPEN_FILE_LIMIT, default_sandbox_layout};
use lazaret_runtime_supervisor::sandbox::{
DOMAIN_OPEN_FILE_LIMIT, RUNSC_EXECUTABLE_NAME, default_sandbox_layout,
};
use rustix::fs::{Mode, OFlags, openat};
use rustix::process::{Resource, Rlimit, getrlimit, setrlimit};
@ -141,6 +143,7 @@ fn run_private_client_bootstrap(arguments: &[OsString]) -> Result<(), PrivateCli
InstalledProductPaths {
escalation_client: &installed.join("lazaret-escalation-client"),
sandbox_init: &installed.join("lazaret-sandbox-init"),
runsc: &installed.join(RUNSC_EXECUTABLE_NAME),
},
))
.map_err(failure)?;

View file

@ -10,7 +10,9 @@ use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use lazaret_core::configuration::{ConfinedPathPlan, ResolvedDomainConfiguration};
use lazaret_core::identity::{BundleDigest, ContainerId, HostPath, MountIdentity};
use lazaret_core::identity::{
BundleDigest, ContainerId, HostPath, MountIdentity, RunscBuildId, RunscDigest,
};
use lazaret_core::network::derive_ipv4_policy;
use lazaret_core::records::{
Generation, GenerationIdentity, HostProcessOwnershipSlot, PreparedArtifact, Session,
@ -84,6 +86,10 @@ pub struct ColdStartInputs<'a> {
pub invoking_uid: u32,
pub escalation_client: &'a File,
pub sandbox_init: &'a File,
pub runsc: &'a File,
pub runsc_program: &'a Path,
pub runsc_build_id: RunscBuildId,
pub runsc_digest: RunscDigest,
pub user_namespace_map: UserNamespaceMap,
}
@ -144,6 +150,9 @@ pub struct ColdStart<'a> {
container_id: ContainerId,
runtime_root: PathBuf,
bundle_digest: BundleDigest,
runsc_program: PathBuf,
runsc_build_id: RunscBuildId,
runsc_digest: RunscDigest,
invoking_uid: u32,
staging: RuntimeStaging,
git_routing: Option<GitRoutingLayout>,
@ -192,6 +201,7 @@ impl<'a> ColdStart<'a> {
.map_err(|error| failure(format!("protected runtime files: {error}")))?;
let request = SandboxBuildRequest {
configuration: inputs.configuration,
runsc_program: inputs.runsc_program,
container_id: inputs.container_id,
expected_network_namespace,
root_process: inputs.root_process,
@ -269,6 +279,9 @@ impl<'a> ColdStart<'a> {
container_id: inputs.container_id,
runtime_root: runtime_domain.join("runsc"),
bundle_digest,
runsc_program: inputs.runsc_program.to_owned(),
runsc_build_id: inputs.runsc_build_id,
runsc_digest: inputs.runsc_digest,
invoking_uid: inputs.invoking_uid,
staging,
git_routing,
@ -390,6 +403,9 @@ impl<'a> ColdStart<'a> {
container_id: self.container_id,
runtime_root: self.runtime_root.clone(),
bundle_digest: self.bundle_digest,
runsc_program: self.runsc_program.clone(),
runsc_build_id: self.runsc_build_id,
runsc_digest: self.runsc_digest,
invoking_uid: self.invoking_uid,
non_interactive_executor: Arc::new(
RunscNonInteractiveExecutor::from_run_command(

View file

@ -271,7 +271,7 @@ impl RunscNonInteractiveExecutor {
container_id: ContainerId,
environment: BTreeMap<String, EnvironmentValue>,
) -> Result<Self, ControlError> {
if command.program() != OsStr::new("runsc") {
if !command.program().as_encoded_bytes().starts_with(b"/") {
return Err(ControlError::InvalidRequest);
}
let run = command

View file

@ -457,7 +457,7 @@ pub use reconciliation::{
#[cfg(test)]
use reconciliation::{
hex_lower, recover_before_cold_start_with_opener, recover_publication, role,
seal_runtime_owners,
seal_runtime_owners, validate_recorded_runsc,
};
#[cfg(test)]

View file

@ -1,13 +1,15 @@
use std::io;
use std::fs::File;
use std::io::{self, Read};
use std::os::fd::AsRawFd;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::os::unix::fs::PermissionsExt;
use std::path::{Component, Path};
use lazaret_core::configuration::{ConfinedPathType, ResolvedDomainConfiguration};
use lazaret_core::identity::{GenerationId, HostPath, MountIdentity, PidfsProcessIdentityV1};
use lazaret_core::records::{
ArtifactLease, DurableDecision, Generation, Participant, ParticipantOwnership, ParticipantRole,
ParticipantState, SessionPhase,
ParticipantState, SandboxOwnership, SessionPhase,
};
use lazaret_linux::filesystem::{current_mount_identity, mount_identity};
use lazaret_linux::process::{
@ -16,8 +18,9 @@ use lazaret_linux::process::{
use lazaret_prepared_lower::PreparedLowerStore;
use lazaret_state_anchor::StateAnchorLayout;
use lazaret_storage_catalog::{Catalog, CatalogError};
use rustix::fs::{AtFlags, Mode, OFlags, fstat, openat, statat, unlinkat};
use rustix::fs::{AtFlags, Mode, OFlags, ResolveFlags, fstat, openat, openat2, statat, unlinkat};
use rustix::io::{FdFlags, fcntl_setfd};
use sha2::{Digest, Sha256};
use crate::control::{
CONTROL_SOCKET_NAME, PUBLICATION_IDENTITY_NAME, STAGED_CONTROL_SOCKET_NAME,
@ -176,7 +179,14 @@ where
prepared,
failpoint,
match_recovery_process,
|root, container| delete_runsc_container(root, container, std::ffi::OsStr::new("runsc")),
|root, container, sandbox| {
let executable = std::env::current_exe().map_err(|error| error.to_string())?;
let installed = executable
.parent()
.ok_or("Host Coordinator executable has no installation directory")?;
let program = validate_recorded_runsc(sandbox, installed)?;
delete_runsc_container(root, container, &program)
},
)
}
@ -192,7 +202,7 @@ pub(super) fn recover_before_cold_start_with_opener<Failpoint, Open, Delete>(
where
Failpoint: FnMut(RecoveryBoundary, GenerationId) -> Result<(), String>,
Open: FnMut(PidfsProcessIdentityV1) -> io::Result<PidfsRecoveryMatch>,
Delete: FnMut(&std::os::fd::OwnedFd, &str) -> Result<(), String>,
Delete: FnMut(&std::os::fd::OwnedFd, &str, &SandboxOwnership) -> Result<(), String>,
{
recover_state_reset(configuration, store).map_err(|error| error.to_string())?;
failpoint(RecoveryBoundary::Snapshot, GenerationId::new(0))?;
@ -250,7 +260,7 @@ fn recover_generation<Failpoint, Open, Delete>(
where
Failpoint: FnMut(RecoveryBoundary, GenerationId) -> Result<(), String>,
Open: FnMut(PidfsProcessIdentityV1) -> io::Result<PidfsRecoveryMatch>,
Delete: FnMut(&std::os::fd::OwnedFd, &str) -> Result<(), String>,
Delete: FnMut(&std::os::fd::OwnedFd, &str, &SandboxOwnership) -> Result<(), String>,
{
let generation_id = generation.generation_id;
let snapshot = store.snapshot().map_err(|error| error.to_string())?;
@ -481,7 +491,7 @@ fn require_sandbox_absent(
configuration: &ResolvedDomainConfiguration,
store: &HostCoordinatorDomainStore,
generation_id: GenerationId,
delete: &mut impl FnMut(&std::os::fd::OwnedFd, &str) -> Result<(), String>,
delete: &mut impl FnMut(&std::os::fd::OwnedFd, &str, &SandboxOwnership) -> Result<(), String>,
) -> Result<(), String> {
let Some(participant) = role(store, generation_id, ParticipantRole::Sandbox)? else {
return Ok(());
@ -524,7 +534,7 @@ fn require_sandbox_absent(
if !rustix::fs::FileType::from_raw_mode(stat.st_mode).is_dir() {
return Err("recorded runsc container changed type".into());
}
delete(&root, &container)?;
delete(&root, &container, sandbox)?;
match statat(&root, &container, AtFlags::SYMLINK_NOFOLLOW) {
Err(rustix::io::Errno::NOENT) => {}
Err(error) => return Err(error.to_string()),
@ -535,6 +545,50 @@ fn require_sandbox_absent(
Ok(())
}
pub(super) fn validate_recorded_runsc(
sandbox: &SandboxOwnership,
installed: &Path,
) -> Result<std::ffi::OsString, String> {
let (build_id, expected) = sandbox
.attached_runsc
.runsc_identity()
.ok_or("Sandbox runsc executable identity is absent")?;
let path = installed.join(format!("lazaret-runsc-{}", hex_lower(build_id.as_bytes())));
if !path.is_absolute()
|| path
.components()
.any(|component| !matches!(component, Component::RootDir | Component::Normal(_)))
{
return Err("Sandbox runsc executable path is not absolute and normalized".into());
}
let descriptor = openat2(
rustix::fs::CWD,
&path,
OFlags::RDONLY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
Mode::empty(),
ResolveFlags::NO_MAGICLINKS | ResolveFlags::NO_SYMLINKS,
)
.map_err(|error| error.to_string())?;
let mut file = File::from(descriptor);
let metadata = file.metadata().map_err(|error| error.to_string())?;
if !metadata.is_file() || metadata.permissions().mode() & 0o111 == 0 {
return Err("Sandbox runsc executable is not an executable regular file".into());
}
let mut digest = Sha256::new();
let mut buffer = vec![0_u8; 64 * 1024];
loop {
let read = file.read(&mut buffer).map_err(|error| error.to_string())?;
if read == 0 {
break;
}
digest.update(&buffer[..read]);
}
if digest.finalize().as_slice() != expected.as_bytes() {
return Err("Sandbox runsc executable digest changed".into());
}
Ok(path.into_os_string())
}
/// Deletes one exact container beneath a retained, already-verified runsc root descriptor.
///
/// # Errors

View file

@ -13,8 +13,8 @@ use lazaret_core::configuration::{
use lazaret_core::identity::{
BootId, BundleDigest, ContainerId, CoordinatorId, DeviceId, HostPidLocator, IdMapDigest,
InodeId, MountId, NetworkSidecarId, PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1,
PolicyId, PrepareOperationId, PreparedArtifactId, ProcessStartTimeTicks, SandboxId,
SelectedManifestDigest, SourceIdentity,
PolicyId, PrepareOperationId, PreparedArtifactId, ProcessStartTimeTicks, RunscBuildId,
RunscDigest, SandboxId, SelectedManifestDigest, SourceIdentity,
};
use lazaret_core::records::{
ArtifactLease, ArtifactState, DurableDecision, GitRoutingOwnership, HelperOwnership,
@ -28,6 +28,7 @@ use lazaret_runtime_supervisor::sandbox::default_sandbox_layout;
use lazaret_state_anchor::StateAnchorLayout;
use lazaret_storage_catalog::Catalog;
use rustix::fs::{AtFlags, unlinkat};
use sha2::{Digest, Sha256};
use super::*;
use crate::control::{
@ -36,6 +37,34 @@ use crate::control::{
};
use crate::domain_store::{DomainStoreMutation, HostCoordinatorDomainStore};
#[test]
fn recorded_runsc_identity_refuses_replacement() {
let tree = tempfile::tempdir().unwrap();
let build_id = RunscBuildId::new([13; 32]);
let program = tree
.path()
.join(format!("lazaret-runsc-{}", hex_lower(build_id.as_bytes())));
fs::copy("/bin/true", &program).unwrap();
let ownership = SandboxOwnership {
container_id: None,
runtime_root: None,
runtime_root_identity: None,
bundle_digest: None,
host_coordinator: PersistedHostProcessOwnership::unassigned(),
runtime_supervisor: PersistedHostProcessOwnership::unassigned(),
attached_runsc: PersistedHostProcessOwnership::runsc_unassigned(
build_id,
RunscDigest::new(Sha256::digest(fs::read(&program).unwrap()).into()),
),
};
assert_eq!(
validate_recorded_runsc(&ownership, tree.path()).unwrap(),
program
);
fs::copy("/bin/false", &program).unwrap();
assert!(validate_recorded_runsc(&ownership, tree.path()).is_err());
}
fn process(value: u64) -> PidfsProcessIdentityV1 {
PidfsProcessIdentityV1::new(
PidfsProcessAuthorityV1 {
@ -373,7 +402,7 @@ fn recovery_preflight_opens_every_exact_owner_once() {
*opened.entry(identity.host_pid().get()).or_insert(0) += 1;
Ok(PidfsRecoveryMatch::BootMismatch)
},
|root, container| {
|root, container, _| {
unlinkat(root, container, AtFlags::REMOVEDIR).map_err(|error| error.to_string())
},
)
@ -460,7 +489,7 @@ fn publication_replacement_and_runsc_root_replacement_refuse_untouched() {
&fixture.prepared,
|_, _| Ok(()),
|_| Ok(PidfsRecoveryMatch::BootMismatch),
|_, _| {
|_, _, _| {
deletes += 1;
Ok(())
},
@ -509,7 +538,7 @@ impl RecoveryFixture {
&self.prepared,
failpoint,
match_recovery_process,
|root, container| {
|root, container, _| {
unlinkat(root, container, AtFlags::REMOVEDIR).map_err(|error| error.to_string())
},
)

View file

@ -7,7 +7,7 @@ use std::time::{Duration, Instant};
use crate::state_anchor::StateAnchorCoordinator;
use lazaret_core::identity::{
BundleDigest, ContainerId, GenerationId, HostPath, MountIdentity, NamespaceIdentity,
PidfsProcessIdentityV1, PreparedArtifactId,
PidfsProcessIdentityV1, PreparedArtifactId, RunscBuildId, RunscDigest,
};
use lazaret_core::network::CanonicalDerivedNetworkPolicy;
use lazaret_core::records::{
@ -40,6 +40,18 @@ pub struct StateAnchorAbsenceObservation {
pub observed: MountIdentity,
}
#[derive(Clone, Copy)]
pub struct SandboxIntent<'path> {
pub generation_id: GenerationId,
pub container_id: ContainerId,
pub runtime_root: &'path Path,
pub runtime_root_identity: MountIdentity,
pub bundle_digest: BundleDigest,
pub runsc_build_id: RunscBuildId,
pub runsc_digest: RunscDigest,
pub host_coordinator: PidfsProcessIdentityV1,
}
#[derive(Debug)]
pub enum SandboxCoordinatorError {
Store(DomainStoreError),
@ -104,15 +116,17 @@ impl<'store> SandboxCoordinator<'store> {
///
/// # Errors
/// Refuses a missing generation, changed physical identity, or existing nonmatching state.
pub fn begin_intent(
&self,
generation_id: GenerationId,
container_id: ContainerId,
runtime_root: &Path,
runtime_root_identity: MountIdentity,
bundle_digest: BundleDigest,
host_coordinator: PidfsProcessIdentityV1,
) -> Result<(), SandboxCoordinatorError> {
pub fn begin_intent(&self, request: SandboxIntent<'_>) -> Result<(), SandboxCoordinatorError> {
let SandboxIntent {
generation_id,
container_id,
runtime_root,
runtime_root_identity,
bundle_digest,
runsc_build_id,
runsc_digest,
host_coordinator,
} = request;
let snapshot = self.domain_store.snapshot()?;
if !snapshot
.generations
@ -130,7 +144,10 @@ impl<'store> SandboxCoordinator<'store> {
bundle_digest: Some(bundle_digest),
host_coordinator: PersistedHostProcessOwnership::committed(host_coordinator),
runtime_supervisor: PersistedHostProcessOwnership::unassigned(),
attached_runsc: PersistedHostProcessOwnership::unassigned(),
attached_runsc: PersistedHostProcessOwnership::runsc_unassigned(
runsc_build_id,
runsc_digest,
),
};
let intent = sandbox_participant(generation_id, ParticipantState::Intent, ownership)?;
match sandbox_role(&snapshot.participants, generation_id) {
@ -216,7 +233,12 @@ impl<'store> SandboxCoordinator<'store> {
));
}
ownership.runtime_supervisor = PersistedHostProcessOwnership::committed(rs);
ownership.attached_runsc = PersistedHostProcessOwnership::staged(staged.identity());
ownership.attached_runsc = ownership
.attached_runsc
.stage_runsc(staged.identity())
.ok_or(SandboxCoordinatorError::InvalidState(
"attached runsc identity is absent",
))?;
self.put_intent(current.generation_id(), ownership)?;
Ok(ProcessOwnershipCommitAck::v1(staged))
}
@ -231,7 +253,8 @@ impl<'store> SandboxCoordinator<'store> {
) -> Result<(), SandboxCoordinatorError> {
self.require_staged(staged, HostProcessOwnershipSlot::SandboxAttachedRunsc)?;
let (_, mut ownership) = self.intent_ownership(staged.generation_id())?;
if ownership.attached_runsc != PersistedHostProcessOwnership::staged(staged.identity())
if ownership.attached_runsc.authoritative_pidfs().ok() != Some(&staged.identity())
|| !ownership.attached_runsc.is_staged()
|| !ownership.runtime_supervisor.is_current_committed()
|| !ownership.host_coordinator.is_current_committed()
{
@ -239,7 +262,12 @@ impl<'store> SandboxCoordinator<'store> {
"Sandbox readiness does not match staged ownership",
));
}
ownership.attached_runsc = PersistedHostProcessOwnership::committed(staged.identity());
ownership.attached_runsc = ownership
.attached_runsc
.commit_runsc(staged.identity())
.ok_or(SandboxCoordinatorError::InvalidState(
"attached runsc identity is absent",
))?;
self.domain_store
.apply(&[DomainStoreMutation::PutParticipant(sandbox_participant(
staged.generation_id(),

View file

@ -3,7 +3,8 @@ use std::path::Path;
use lazaret_core::identity::{
BootId, ConfigurationFingerprint, CoordinatorId, DomainId, HostPidLocator, NetworkSidecarId,
PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1, ProcessStartTimeTicks, SandboxId,
PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1, ProcessStartTimeTicks, RunscBuildId,
RunscDigest, SandboxId,
};
use lazaret_core::records::{Domain, DurableDecision, Generation};
use lazaret_linux::filesystem::{current_mount_identity, try_exclusive_flock};
@ -87,14 +88,16 @@ fn sandbox_staging_readiness_and_release_preserve_exact_ownership() {
let store = store(&directory);
let coordinator = SandboxCoordinator::new(&store);
coordinator
.begin_intent(
generation().generation_id,
ContainerId::new(bytes(6)),
directory.path(),
current_mount_identity(directory.path()).unwrap(),
BundleDigest::new(bytes(7)),
process(100),
)
.begin_intent(SandboxIntent {
generation_id: generation().generation_id,
container_id: ContainerId::new(bytes(6)),
runtime_root: directory.path(),
runtime_root_identity: current_mount_identity(directory.path()).unwrap(),
bundle_digest: BundleDigest::new(bytes(7)),
runsc_build_id: RunscBuildId::new(bytes(8)),
runsc_digest: RunscDigest::new(bytes(8)),
host_coordinator: process(100),
})
.unwrap();
let rs = staged(
HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
@ -123,9 +126,14 @@ fn sandbox_staging_readiness_and_release_preserve_exact_ownership() {
ownership.runtime_supervisor,
PersistedHostProcessOwnership::committed(process(101))
);
assert!(ownership.attached_runsc.is_staged());
assert_eq!(
ownership.attached_runsc,
PersistedHostProcessOwnership::staged(process(102))
ownership.attached_runsc.authoritative_pidfs().unwrap(),
&process(102)
);
assert_eq!(
ownership.attached_runsc.runsc_identity(),
Some((RunscBuildId::new(bytes(8)), RunscDigest::new(bytes(8))))
);
coordinator.commit_ready(runsc).unwrap();
assert_eq!(
@ -152,14 +160,16 @@ fn mismatched_and_skipped_acquisition_boundaries_refuse_without_state_change() {
let store = store(&directory);
let coordinator = SandboxCoordinator::new(&store);
coordinator
.begin_intent(
generation().generation_id,
ContainerId::new(bytes(6)),
directory.path(),
current_mount_identity(directory.path()).unwrap(),
BundleDigest::new(bytes(7)),
process(100),
)
.begin_intent(SandboxIntent {
generation_id: generation().generation_id,
container_id: ContainerId::new(bytes(6)),
runtime_root: directory.path(),
runtime_root_identity: current_mount_identity(directory.path()).unwrap(),
bundle_digest: BundleDigest::new(bytes(7)),
runsc_build_id: RunscBuildId::new(bytes(8)),
runsc_digest: RunscDigest::new(bytes(8)),
host_coordinator: process(100),
})
.unwrap();
let runsc = staged(HostProcessOwnershipSlot::SandboxAttachedRunsc, process(102));
assert!(coordinator.stage_attached_runsc(runsc).is_err());

View file

@ -1,5 +1,6 @@
use std::fmt;
use std::fs::File;
use std::io::Read;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
@ -8,7 +9,7 @@ use std::sync::Arc;
use lazaret_core::configuration::ResolvedDomainConfiguration;
use lazaret_core::identity::{
ConfigurationFingerprint, ContainerId, CoordinatorId, DomainId, GenerationId, NetworkSidecarId,
PrepareOperationId, SandboxId,
PrepareOperationId, RunscBuildId, RunscDigest, SandboxId,
};
use lazaret_core::records::{DurableDecision, Generation, PreparedArtifact, VolumeKind};
use lazaret_linux::filesystem::SharedFileLock;
@ -26,6 +27,7 @@ use lazaret_runtime_supervisor::sandbox::{
use lazaret_sandbox_contract::RUNTIME_FILE_DESTINATIONS;
use lazaret_storage_catalog::{Catalog, CatalogError};
use rustix::fs::{Mode, OFlags, ResolveFlags, openat2};
use sha2::Digest;
use crate::cold_start::ColdStartInputs;
use crate::domain_store::HostCoordinatorDomainStore;
@ -72,6 +74,7 @@ fn failure(error: impl fmt::Display) -> RuntimeSelectionError {
pub struct InstalledProductPaths<'a> {
pub escalation_client: &'a Path,
pub sandbox_init: &'a Path,
pub runsc: &'a Path,
}
pub struct RuntimeSelection {
@ -87,6 +90,10 @@ pub struct RuntimeSelection {
container_id: ContainerId,
escalation_client: File,
sandbox_init: File,
runsc: File,
runsc_program: PathBuf,
runsc_build_id: RunscBuildId,
runsc_digest: RunscDigest,
user_namespace_map: UserNamespaceMap,
}
@ -125,6 +132,10 @@ impl RuntimeSelection {
invoking_uid: rustix::process::getuid().as_raw(),
escalation_client: &self.escalation_client,
sandbox_init: &self.sandbox_init,
runsc: &self.runsc,
runsc_program: &self.runsc_program,
runsc_build_id: self.runsc_build_id,
runsc_digest: self.runsc_digest,
user_namespace_map: self.user_namespace_map.clone(),
})
}
@ -164,6 +175,8 @@ async fn select_runtime_for_platform(
let user_namespace_map = UserNamespaceMap::resolve_current().map_err(failure)?;
let escalation_client = open_installed_executable(product_paths.escalation_client)?;
let sandbox_init = open_installed_executable(product_paths.sandbox_init)?;
let runsc = open_installed_executable(product_paths.runsc)?;
let runsc_digest = RunscDigest::new(executable_digest(&runsc)?);
let catalog = Catalog::under_storage_root(configuration.storage_root()).map_err(failure)?;
match catalog.initialize() {
Ok(()) | Err(CatalogError::AlreadyInitialized) => {}
@ -242,6 +255,12 @@ async fn select_runtime_for_platform(
container_id: ContainerId::new(random_id()?),
escalation_client,
sandbox_init,
runsc,
runsc_program: product_paths.runsc.to_owned(),
runsc_build_id: RunscBuildId::new(
lazaret_runtime_supervisor::sandbox::RUNSC_BUILD_ID_BYTES,
),
runsc_digest,
user_namespace_map,
})
}
@ -316,6 +335,18 @@ fn next_generation(store: &HostCoordinatorDomainStore) -> Result<u64, RuntimeSel
}
fn open_installed_executable(path: &Path) -> Result<File, RuntimeSelectionError> {
if !path.is_absolute()
|| path.components().any(|component| {
!matches!(
component,
std::path::Component::RootDir | std::path::Component::Normal(_)
)
})
{
return Err(failure(
"installed product path is not absolute and normalized",
));
}
let descriptor = openat2(
rustix::fs::CWD,
path,
@ -334,6 +365,20 @@ fn open_installed_executable(path: &Path) -> Result<File, RuntimeSelectionError>
Ok(file)
}
fn executable_digest(file: &File) -> Result<[u8; 32], RuntimeSelectionError> {
let mut source = file.try_clone().map_err(failure)?;
let mut digest = sha2::Sha256::new();
let mut buffer = vec![0_u8; 64 * 1024];
loop {
let read = source.read(&mut buffer).map_err(failure)?;
if read == 0 {
break;
}
digest.update(&buffer[..read]);
}
Ok(digest.finalize().into())
}
fn distinct_generation_ids()
-> Result<(CoordinatorId, NetworkSidecarId, SandboxId), RuntimeSelectionError> {
for _ in 0..8 {
@ -445,6 +490,7 @@ mod tests {
InstalledProductPaths {
escalation_client: Path::new("/missing-escalation-client"),
sandbox_init: Path::new("/missing-sandbox-init"),
runsc: Path::new("/missing-runsc"),
},
&observation,
));

View file

@ -8,7 +8,8 @@ use std::sync::Arc;
use lazaret_core::configuration::{ConfigurationLoadRequest, load_trusted_configuration};
use lazaret_core::identity::{
ContainerId, CoordinatorId, DeviceId, GenerationId, HostPath, IdMapDigest, InodeId, PolicyId,
PreparedArtifactId, SandboxId, SelectedManifestDigest, SourceIdentity,
PreparedArtifactId, RunscBuildId, RunscDigest, SandboxId, SelectedManifestDigest,
SourceIdentity,
};
use lazaret_core::records::{ArtifactState, Domain, DurableDecision, Generation, PreparedArtifact};
use lazaret_host_coordinator::cold_start::{ColdStart, ColdStartInputs};
@ -280,6 +281,10 @@ impl ColdStartFixture {
invoking_uid: getuid().as_raw(),
escalation_client,
sandbox_init: escalation_client,
runsc: escalation_client,
runsc_program: Path::new("/bin/true"),
runsc_build_id: RunscBuildId::new([11; 32]),
runsc_digest: RunscDigest::new([12; 32]),
user_namespace_map: test_user_namespace_map(),
}
}

View file

@ -16,7 +16,7 @@ use lazaret_core::configuration::{
};
use lazaret_core::identity::{
BundleDigest, ClientId, ContainerId, CoordinatorId, GenerationId, HostPath, NetworkSidecarId,
PidfsProcessIdentityV1, SandboxId, SessionAnchorId, SessionId,
PidfsProcessIdentityV1, RunscBuildId, RunscDigest, SandboxId, SessionAnchorId, SessionId,
};
use lazaret_core::network::derive_ipv4_policy;
use lazaret_core::records::{
@ -28,7 +28,7 @@ use lazaret_host_coordinator::domain_store::{DomainStoreMutation, HostCoordinato
use lazaret_host_coordinator::git_routing::GitRoutingCoordinator;
use lazaret_host_coordinator::network::NetworkCoordinator;
use lazaret_host_coordinator::runtime::{
AwaitingAttachedRunsc, CommittedSandboxRuntime, SandboxCoordinator,
AwaitingAttachedRunsc, CommittedSandboxRuntime, SandboxCoordinator, SandboxIntent,
};
use lazaret_host_coordinator::terminal::{
NoActivePrompt, RunscSessionTerminalFactory, SessionTerminalEngine, TerminalAcquisition,

View file

@ -4,11 +4,11 @@ use super::{
DurableDecision, File, Generation, GenerationId, GitRoutingCoordinator,
HostCoordinatorDomainStore, HostPath, HostProcessOwnershipSlot, Ipv4Addr, MatrixError,
NETWORK_NAMESPACE_PATH, NetworkCoordinator, NetworkSidecarId, Path, PathBuf,
ProcessOwnershipContext, ResolvedDomainConfiguration, RootProcess, RunscCommand,
RuntimeGitRoutingAuthority, RuntimeGitRoutingLaunch, RuntimeNetworkLaunch,
ProcessOwnershipContext, ResolvedDomainConfiguration, RootProcess, RunscBuildId, RunscCommand,
RunscDigest, RuntimeGitRoutingAuthority, RuntimeGitRoutingLaunch, RuntimeNetworkLaunch,
RuntimeSupervisorLaunch, SandboxBuildRequest, SandboxBundle, SandboxCoordinator, SandboxId,
SocketAddr, SocketAddrExt, TargetCommand, TcpListener, TempDir, UnixListener, UserNamespaceMap,
build_sandbox_bundle, build_sandbox_bundle_with_workspace, child_broker,
SandboxIntent, SocketAddr, SocketAddrExt, TargetCommand, TcpListener, TempDir, UnixListener,
UserNamespaceMap, build_sandbox_bundle, build_sandbox_bundle_with_workspace, child_broker,
current_mount_identity, current_process_identity, default_sandbox_layout, derive_ipv4_policy,
failure, fs, hex, host_path, load_trusted_configuration, lock_file,
stage_benchmark_rootfs_and_control, stage_rootfs_and_control, try_exclusive_flock,
@ -132,6 +132,7 @@ impl Fixture {
lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))?;
let bundle = build_sandbox_bundle(SandboxBuildRequest {
configuration: &configuration,
runsc_program: Path::new("/usr/local/bin/runsc"),
container_id: ContainerId::new([6; 32]),
expected_network_namespace: namespace,
root_process,
@ -199,6 +200,7 @@ impl Fixture {
lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))?;
let request = SandboxBuildRequest {
configuration: &configuration,
runsc_program: Path::new("/usr/local/bin/runsc"),
container_id,
expected_network_namespace: namespace,
root_process,
@ -261,14 +263,16 @@ impl Fixture {
})])
.map_err(failure)?;
SandboxCoordinator::new(&store)
.begin_intent(
GenerationId::new(7),
.begin_intent(SandboxIntent {
generation_id: GenerationId::new(7),
container_id,
&runtime_domain.join("runsc"),
current_mount_identity(&runtime_domain.join("runsc"))?,
BundleDigest::new([7; 32]),
current_process_identity()?,
)
runtime_root: &runtime_domain.join("runsc"),
runtime_root_identity: current_mount_identity(&runtime_domain.join("runsc"))?,
bundle_digest: BundleDigest::new([7; 32]),
runsc_build_id: RunscBuildId::new([8; 32]),
runsc_digest: RunscDigest::new([8; 32]),
host_coordinator: current_process_identity()?,
})
.map_err(failure)?;
Ok(Self {
_tree: tree,
@ -338,6 +342,7 @@ impl Fixture {
lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))?;
build_sandbox_bundle(SandboxBuildRequest {
configuration: &self.configuration,
runsc_program: Path::new("/usr/local/bin/runsc"),
container_id: ContainerId::new([6; 32]),
expected_network_namespace: namespace,
root_process: RootProcess::new(
@ -478,6 +483,7 @@ impl NetworkFixture {
lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))?;
let bundle = build_sandbox_bundle(SandboxBuildRequest {
configuration: &configuration,
runsc_program: Path::new("/usr/local/bin/runsc"),
container_id,
expected_network_namespace: namespace,
root_process,
@ -609,14 +615,16 @@ fn network_store(
})])
.map_err(failure)?;
SandboxCoordinator::new(&store)
.begin_intent(
GenerationId::new(7),
.begin_intent(SandboxIntent {
generation_id: GenerationId::new(7),
container_id,
&runtime_domain.join("runsc"),
current_mount_identity(&runtime_domain.join("runsc"))?,
BundleDigest::new([17; 32]),
current_process_identity()?,
)
runtime_root: &runtime_domain.join("runsc"),
runtime_root_identity: current_mount_identity(&runtime_domain.join("runsc"))?,
bundle_digest: BundleDigest::new([17; 32]),
runsc_build_id: RunscBuildId::new([18; 32]),
runsc_digest: RunscDigest::new([18; 32]),
host_coordinator: current_process_identity()?,
})
.map_err(failure)?;
Ok(store)
}