Bind sandbox handoff authority and restore verifier gates
This commit is contained in:
parent
cf914bdf51
commit
49d5def1a9
1 changed files with 951 additions and 170 deletions
|
|
@ -33,8 +33,8 @@ use lazaret_linux::process::{
|
|||
};
|
||||
use lazaret_runtime_supervisor::network::{DnsProbeTarget, NetworkProbeRequest, TcpProbeTarget};
|
||||
use lazaret_runtime_supervisor::sandbox::{
|
||||
NETWORK_NAMESPACE_PATH, RootProcess, SandboxBuildRequest, SandboxBundle, build_sandbox_bundle,
|
||||
production_sandbox_layout,
|
||||
NETWORK_NAMESPACE_PATH, RootProcess, RunscCommand, SandboxBuildRequest, SandboxBundle,
|
||||
build_sandbox_bundle, production_sandbox_layout,
|
||||
};
|
||||
use lazaret_runtime_supervisor::supervision::{
|
||||
AttachedRunscSignal, RuntimeNetworkLaunch, RuntimeSupervisorLaunch,
|
||||
|
|
@ -47,6 +47,7 @@ const SKIP: u8 = 77;
|
|||
const HOST_COORDINATOR_DRIVER: &str = "--runtime-composition-host-coordinator";
|
||||
const SANDBOX_NETWORK_PROBE: &str = "--sandbox-network-probe";
|
||||
const NETWORK_MATRIX: &str = "--network-matrix";
|
||||
const HANDOFF_MATRIX: &str = "--handoff-matrix";
|
||||
const BENCHMARK_DRIVER: &str = "--runtime-composition-benchmark-command";
|
||||
const BENCHMARK_GUEST: &str = "--runtime-composition-benchmark-guest";
|
||||
const BENCHMARK_COMPLETE: &str = "benchmark-complete";
|
||||
|
|
@ -88,6 +89,22 @@ fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
}
|
||||
if arguments.first().and_then(|argument| argument.to_str()) == Some(HANDOFF_MATRIX) {
|
||||
return match run_handoff_matrix() {
|
||||
Ok(()) => {
|
||||
println!("PASS final handoff replacement refusal");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(MatrixError::Skip(reason)) => {
|
||||
println!("SKIP final handoff replacement refusal: {reason}");
|
||||
ExitCode::from(SKIP)
|
||||
}
|
||||
Err(MatrixError::Failure(error)) => {
|
||||
eprintln!("final handoff replacement refusal failed: {error}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
};
|
||||
}
|
||||
match run_matrix() {
|
||||
Ok(()) => {
|
||||
println!("PASS foreground runtime composition and causal-loss matrix");
|
||||
|
|
@ -211,6 +228,7 @@ impl From<std::io::Error> for MatrixError {
|
|||
fn run_matrix() -> Result<(), MatrixError> {
|
||||
require_matrix_prerequisites()?;
|
||||
lost_acknowledgment_refuses_target_work()?;
|
||||
replaced_handoff_authority_refuses_target_work()?;
|
||||
normal_stop_and_stop_resume()?;
|
||||
attached_runsc_loss_stops_guest()?;
|
||||
runtime_supervisor_loss_stops_guest()?;
|
||||
|
|
@ -223,6 +241,12 @@ fn run_network_matrix() -> Result<(), MatrixError> {
|
|||
run_network_cases()
|
||||
}
|
||||
|
||||
fn run_handoff_matrix() -> Result<(), MatrixError> {
|
||||
require_matrix_prerequisites()?;
|
||||
replaced_handoff_authority_refuses_target_work()?;
|
||||
cross_bundle_handoff_refuses_target_work()
|
||||
}
|
||||
|
||||
fn run_network_cases() -> Result<(), MatrixError> {
|
||||
failed_probe_blocks_sandbox_start()?;
|
||||
networked_sandbox_view_isolated()
|
||||
|
|
@ -301,6 +325,54 @@ fn lost_acknowledgment_refuses_target_work() -> Result<(), MatrixError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn replaced_handoff_authority_refuses_target_work() -> Result<(), MatrixError> {
|
||||
let fixture = Fixture::new()?;
|
||||
let handoff = fixture
|
||||
.bundle
|
||||
.capture_handoff_authority(&fixture.configuration)
|
||||
.map_err(failure)?;
|
||||
let mut pending = fixture.launch_pending()?;
|
||||
let staged = pending
|
||||
.receive_staged(&SandboxCoordinator::new(&fixture.store))
|
||||
.map_err(failure)?;
|
||||
let workspace = host_path(fixture.configuration.workspace().canonical_label());
|
||||
let selected = workspace.with_extension("selected");
|
||||
fs::rename(&workspace, &selected)?;
|
||||
fs::create_dir(&workspace)?;
|
||||
let marker = workspace.join("replacement-marker");
|
||||
fs::write(&marker, b"unchanged")?;
|
||||
if pending.acknowledge(staged, &handoff).is_ok() {
|
||||
return Err(failure("replaced handoff authority released sandbox work"));
|
||||
}
|
||||
if fs::read(&marker)? != b"unchanged"
|
||||
|| workspace.join("activity").exists()
|
||||
|| selected.join("activity").exists()
|
||||
{
|
||||
return Err(failure("refused handoff mutated a workspace tree"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cross_bundle_handoff_refuses_target_work() -> Result<(), MatrixError> {
|
||||
let fixture = Fixture::new()?;
|
||||
let handoff = fixture
|
||||
.bundle
|
||||
.capture_handoff_authority(&fixture.configuration)
|
||||
.map_err(failure)?;
|
||||
let alternate = fixture.alternate_bundle()?;
|
||||
if fixture.bundle.command().arguments() != alternate.command().arguments() {
|
||||
return Err(failure("cross-bundle fixture changed runsc argv"));
|
||||
}
|
||||
let mut pending = fixture.launch_pending_with_command(alternate.command())?;
|
||||
let staged = pending
|
||||
.receive_staged(&SandboxCoordinator::new(&fixture.store))
|
||||
.map_err(failure)?;
|
||||
if pending.acknowledge(staged, &handoff).is_ok() || fixture.activity.exists() {
|
||||
return Err(failure("cross-bundle handoff released sandbox work"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn normal_stop_and_stop_resume() -> Result<(), MatrixError> {
|
||||
let fixture = Fixture::new()?;
|
||||
let runtime = fixture.launch_committed()?;
|
||||
|
|
@ -738,6 +810,13 @@ impl Fixture {
|
|||
}
|
||||
|
||||
fn launch_pending(&self) -> Result<AwaitingAttachedRunsc, MatrixError> {
|
||||
self.launch_pending_with_command(self.bundle.command())
|
||||
}
|
||||
|
||||
fn launch_pending_with_command(
|
||||
&self,
|
||||
command: &RunscCommand,
|
||||
) -> Result<AwaitingAttachedRunsc, MatrixError> {
|
||||
let launch = RuntimeSupervisorLaunch::from_runsc_command(
|
||||
ProcessOwnershipContext {
|
||||
domain_id: self.configuration.domain_id(),
|
||||
|
|
@ -745,19 +824,43 @@ impl Fixture {
|
|||
coordinator_id: CoordinatorId::new([3; 32]),
|
||||
slot: lazaret_core::records::HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
|
||||
},
|
||||
self.bundle.command(),
|
||||
command,
|
||||
)?;
|
||||
AwaitingAttachedRunsc::launch(&SandboxCoordinator::new(&self.store), &launch)
|
||||
.map_err(failure)
|
||||
}
|
||||
|
||||
fn alternate_bundle(&self) -> Result<SandboxBundle, MatrixError> {
|
||||
let namespace =
|
||||
lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))?;
|
||||
build_sandbox_bundle(SandboxBuildRequest {
|
||||
configuration: &self.configuration,
|
||||
container_id: ContainerId::new([6; 32]),
|
||||
expected_network_namespace: namespace,
|
||||
root_process: RootProcess::new(
|
||||
"/bin/sh",
|
||||
[
|
||||
"-c".to_owned(),
|
||||
"echo cross-bundle >> /workspace/activity".to_owned(),
|
||||
],
|
||||
)
|
||||
.map_err(failure)?,
|
||||
terminal: None,
|
||||
})
|
||||
.map_err(failure)
|
||||
}
|
||||
|
||||
fn launch_committed(&self) -> Result<CommittedSandboxRuntime, MatrixError> {
|
||||
let handoff = self
|
||||
.bundle
|
||||
.capture_handoff_authority(&self.configuration)
|
||||
.map_err(failure)?;
|
||||
let mut pending = self.launch_pending()?;
|
||||
let staged = pending
|
||||
.receive_staged(&SandboxCoordinator::new(&self.store))
|
||||
.map_err(failure)?;
|
||||
pending
|
||||
.acknowledge(staged)
|
||||
.acknowledge(staged, &handoff)
|
||||
.map_err(failure)?
|
||||
.commit(&SandboxCoordinator::new(&self.store))
|
||||
.map_err(failure)
|
||||
|
|
@ -779,32 +882,9 @@ impl NetworkFixture {
|
|||
fn new() -> Result<Self, MatrixError> {
|
||||
let tree = tempfile::tempdir()?;
|
||||
let root = tree.path();
|
||||
let configuration_directory = root.join("configuration");
|
||||
let workspace = root.join("workspace");
|
||||
let runtime = root.join("runtime");
|
||||
fs::create_dir(&configuration_directory)?;
|
||||
fs::create_dir(&workspace)?;
|
||||
fs::create_dir(&runtime)?;
|
||||
let configuration_path = configuration_directory.join("lazaret.toml");
|
||||
fs::write(
|
||||
&configuration_path,
|
||||
format!(
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/network-fixture:v1\"\npersistent_dirs = []\n",
|
||||
root.join("data").display()
|
||||
),
|
||||
)?;
|
||||
let configuration = load_trusted_configuration(ConfigurationLoadRequest {
|
||||
explicit_configuration_path: Some(configuration_path),
|
||||
selected_profile: "default".into(),
|
||||
workspace: workspace.clone(),
|
||||
home: root.join("home"),
|
||||
xdg_config_home: None,
|
||||
xdg_data_home: None,
|
||||
xdg_runtime_dir: runtime,
|
||||
environment: BTreeMap::new(),
|
||||
sandbox_layout: production_sandbox_layout().map_err(failure)?,
|
||||
})
|
||||
.map_err(failure)?;
|
||||
let configuration = network_configuration(root, &workspace, &runtime)?;
|
||||
let runtime_domain = host_path(configuration.runtime_domain_root().canonical_label());
|
||||
let container_id = ContainerId::new([16; 32]);
|
||||
stage_rootfs_and_control(&runtime_domain, container_id)?;
|
||||
|
|
@ -845,36 +925,7 @@ impl NetworkFixture {
|
|||
fs::create_dir_all(&bundle_path)?;
|
||||
fs::write(bundle_path.join("config.json"), bundle.specification_json())?;
|
||||
fs::create_dir_all(runtime_domain.join("runsc"))?;
|
||||
let domain = Domain {
|
||||
domain_id: configuration.domain_id(),
|
||||
profile_name: configuration.selected_profile().to_owned(),
|
||||
canonical_workspace: configuration.workspace().canonical_label().clone(),
|
||||
};
|
||||
let persistent_domain = host_path(configuration.persistent_domain_root().canonical_label());
|
||||
let lock = try_exclusive_flock(lock_file(&persistent_domain.join("domain.lock")))?;
|
||||
let store =
|
||||
HostCoordinatorDomainStore::open(lock, &persistent_domain.join("domain.redb"), &domain)
|
||||
.map_err(failure)?;
|
||||
store
|
||||
.apply(&[DomainStoreMutation::PutGeneration(Generation {
|
||||
domain_id: configuration.domain_id(),
|
||||
generation_id: GenerationId::new(7),
|
||||
configuration_fingerprint: configuration.configuration_fingerprint(),
|
||||
coordinator_id: CoordinatorId::new([13; 32]),
|
||||
network_sidecar_id: NetworkSidecarId::new([14; 32]),
|
||||
sandbox_id: SandboxId::new([15; 32]),
|
||||
durable_decision: DurableDecision::None,
|
||||
})])
|
||||
.map_err(failure)?;
|
||||
SandboxCoordinator::new(&store)
|
||||
.begin_intent(
|
||||
GenerationId::new(7),
|
||||
container_id,
|
||||
&runtime_domain.join("runsc"),
|
||||
BundleDigest::new([17; 32]),
|
||||
current_process_identity()?,
|
||||
)
|
||||
.map_err(failure)?;
|
||||
let store = network_store(&configuration, &runtime_domain, container_id)?;
|
||||
let policy =
|
||||
derive_ipv4_policy(&configuration, [IpAddress::V4([10, 0, 2, 3])]).map_err(failure)?;
|
||||
Ok(Self {
|
||||
|
|
@ -898,6 +949,10 @@ impl NetworkFixture {
|
|||
permitted_address: [u8; 4],
|
||||
permitted_port: u16,
|
||||
) -> Result<CommittedSandboxRuntime, MatrixError> {
|
||||
let handoff = self
|
||||
.bundle
|
||||
.capture_handoff_authority(&self.configuration)
|
||||
.map_err(failure)?;
|
||||
let launch = RuntimeSupervisorLaunch::from_networked_runsc_command(
|
||||
ProcessOwnershipContext {
|
||||
domain_id: self.configuration.domain_id(),
|
||||
|
|
@ -950,7 +1005,7 @@ impl NetworkFixture {
|
|||
.map_err(failure)?;
|
||||
let staged = pending.receive_staged(&sandbox).map_err(failure)?;
|
||||
let (runtime, _) = pending
|
||||
.acknowledge(staged)
|
||||
.acknowledge(staged, &handoff)
|
||||
.map_err(failure)?
|
||||
.commit_networked(&sandbox)
|
||||
.map_err(failure)?;
|
||||
|
|
@ -958,25 +1013,90 @@ impl NetworkFixture {
|
|||
}
|
||||
}
|
||||
|
||||
fn network_configuration(
|
||||
root: &Path,
|
||||
workspace: &Path,
|
||||
runtime: &Path,
|
||||
) -> Result<ResolvedDomainConfiguration, MatrixError> {
|
||||
let configuration_directory = root.join("configuration");
|
||||
fs::create_dir(&configuration_directory)?;
|
||||
fs::create_dir(workspace)?;
|
||||
fs::create_dir(runtime)?;
|
||||
let configuration_path = configuration_directory.join("lazaret.toml");
|
||||
fs::write(
|
||||
&configuration_path,
|
||||
format!(
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/network-fixture:v1\"\npersistent_dirs = []\n",
|
||||
root.join("data").display()
|
||||
),
|
||||
)?;
|
||||
load_trusted_configuration(ConfigurationLoadRequest {
|
||||
explicit_configuration_path: Some(configuration_path),
|
||||
selected_profile: "default".into(),
|
||||
workspace: workspace.to_path_buf(),
|
||||
home: root.join("home"),
|
||||
xdg_config_home: None,
|
||||
xdg_data_home: None,
|
||||
xdg_runtime_dir: runtime.to_path_buf(),
|
||||
environment: BTreeMap::new(),
|
||||
sandbox_layout: production_sandbox_layout().map_err(failure)?,
|
||||
})
|
||||
.map_err(failure)
|
||||
}
|
||||
|
||||
fn network_store(
|
||||
configuration: &ResolvedDomainConfiguration,
|
||||
runtime_domain: &Path,
|
||||
container_id: ContainerId,
|
||||
) -> Result<HostCoordinatorDomainStore, MatrixError> {
|
||||
let domain = Domain {
|
||||
domain_id: configuration.domain_id(),
|
||||
profile_name: configuration.selected_profile().to_owned(),
|
||||
canonical_workspace: configuration.workspace().canonical_label().clone(),
|
||||
};
|
||||
let persistent_domain = host_path(configuration.persistent_domain_root().canonical_label());
|
||||
let lock = try_exclusive_flock(lock_file(&persistent_domain.join("domain.lock")))?;
|
||||
let store =
|
||||
HostCoordinatorDomainStore::open(lock, &persistent_domain.join("domain.redb"), &domain)
|
||||
.map_err(failure)?;
|
||||
store
|
||||
.apply(&[DomainStoreMutation::PutGeneration(Generation {
|
||||
domain_id: configuration.domain_id(),
|
||||
generation_id: GenerationId::new(7),
|
||||
configuration_fingerprint: configuration.configuration_fingerprint(),
|
||||
coordinator_id: CoordinatorId::new([13; 32]),
|
||||
network_sidecar_id: NetworkSidecarId::new([14; 32]),
|
||||
sandbox_id: SandboxId::new([15; 32]),
|
||||
durable_decision: DurableDecision::None,
|
||||
})])
|
||||
.map_err(failure)?;
|
||||
SandboxCoordinator::new(&store)
|
||||
.begin_intent(
|
||||
GenerationId::new(7),
|
||||
container_id,
|
||||
&runtime_domain.join("runsc"),
|
||||
BundleDigest::new([17; 32]),
|
||||
current_process_identity()?,
|
||||
)
|
||||
.map_err(failure)?;
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
fn failed_probe_blocks_sandbox_start() -> Result<(), MatrixError> {
|
||||
let fixture = NetworkFixture::new()?;
|
||||
match fixture.launch_with_permitted([203, 0, 113, 1], 9) {
|
||||
Ok(runtime) => {
|
||||
runtime
|
||||
.stop(&SandboxCoordinator::new(&fixture.store))
|
||||
.map_err(failure)?;
|
||||
Err(failure("failed physical probe started the sandbox"))
|
||||
}
|
||||
Err(_) => {
|
||||
let sandbox = sandbox_participant(&fixture.store);
|
||||
if sandbox.state() != ParticipantState::Intent || fixture.probe_result.exists() {
|
||||
Err(failure(
|
||||
"failed physical probe advanced Sandbox state or guest work",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
if let Ok(runtime) = fixture.launch_with_permitted([203, 0, 113, 1], 9) {
|
||||
runtime
|
||||
.stop(&SandboxCoordinator::new(&fixture.store))
|
||||
.map_err(failure)?;
|
||||
return Err(failure("failed physical probe started the sandbox"));
|
||||
}
|
||||
let sandbox = sandbox_participant(&fixture.store);
|
||||
if sandbox.state() != ParticipantState::Intent || fixture.probe_result.exists() {
|
||||
Err(failure(
|
||||
"failed physical probe advanced Sandbox state or guest work",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue