Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions crates/mvm-client/src/launch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,8 @@ impl LocalBackend {
/// authority: the `Reuse` arm keeps an earlier definition, so the spec
/// is re-checked for in-process bootability and the sidecar's recorded
/// secret references are re-validated fail-closed even when this
/// request carries none.
/// request carries none. The request's mailbox is separate one-start
/// authority, passed into admission without persisting it in the definition.
async fn boot_persistent(&self, name: &str, request: &LaunchRequest) -> Result<LaunchOutcome> {
if self.machine_running(name) {
return Err(MvmError::Conflict {
Expand All @@ -924,6 +925,7 @@ impl LocalBackend {
&mut spec,
request.backend.as_deref(),
!request.command.is_empty(),
request.mailbox,
)
.await?;
if let Some(ttl) = request.ttl_seconds {
Expand Down Expand Up @@ -964,6 +966,7 @@ impl LocalBackend {
spec: &mut mp::MachineSpec,
backend_override: Option<&str>,
has_ad_hoc_argv: bool,
mailbox: Option<mvm_contract::protocol::mailbox::MailboxGrant>,
) -> Result<(machine_start::MachineStart, String)> {
let backend = backend_override
.map(str::to_string)
Expand All @@ -988,7 +991,7 @@ impl LocalBackend {
machine_start::MachineStartParams {
hypervisor: &backend,
has_ad_hoc_argv,
mailbox: None,
mailbox,
},
)
.map_err(|e| crate::local::backend_err(format!("{e:#}")))?;
Expand Down Expand Up @@ -1238,7 +1241,7 @@ impl LocalBackend {
ensure_spec_bootable_in_process(&spec)?;
self.validate_sidecar_refs(name)?;
let mut spec = spec;
let (_, backend) = self.boot_spec(&mut spec, None, false).await?;
let (_, backend) = self.boot_spec(&mut spec, None, false, None).await?;
let memory_mib = mvm_core::util::parse_human_size(&spec.memory).unwrap_or_default();
Ok(MachineState {
id: MachineId(name.to_string()),
Expand Down
73 changes: 72 additions & 1 deletion crates/mvm-client/src/launch/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1537,7 +1537,7 @@ async fn a_restart_re_admits_under_the_persisted_grants_not_deny_all() {
mp::save_machine_spec(&spec, false).unwrap();

let (started, _) = client
.boot_spec(&mut spec, None, false)
.boot_spec(&mut spec, None, false, None)
.await
.expect("the start boots");
let admitted = started
Expand Down Expand Up @@ -2051,3 +2051,74 @@ async fn cleanup_keeps_secret_references_changed_after_the_session_wrote_them()

assert!(sidecar.exists());
}

/// The mock cannot serve guest mailboxes. Reaching its canonical capability
/// refusal proves the persistent request retained its one-start grant through
/// admission instead of silently booting an ungranted machine (native EE006).
#[tokio::test]
async fn persistent_request_mailbox_reaches_canonical_backend_admission() {
let home = Isolated::new();
let client = mock_client();
let name = "p-mailbox-one-start";
let grant = crate::admission::mailbox_grant::resolve(Some(&"ab".repeat(32)), None)
.expect("host-authorized grant")
.expect("requested grant");
let request = persistent_request(&home.rootfs(), name)
.mailbox(grant)
.build()
.expect("persistent mailbox request");
let error = client.launch(request).await.expect_err(
"an unsupported mailbox must refuse admission, not boot after stripping its grant",
);
assert!(
error.to_string().contains("mailbox") && error.to_string().contains("cannot serve"),
"must reach the canonical guest-mailbox capability gate, got: {error}"
);
assert!(
!client.machine_running(name),
"refusal must precede backend boot"
);
assert!(
!machine_spec_path(name).exists(),
"remove only the definition this failed launch created"
);
}

/// Persisting a definition never persists the request's mailbox authority.
/// A later ordinary start must explicitly re-admit without that one-start grant.
#[tokio::test]
async fn persistent_definition_restart_does_not_inherit_request_mailbox() {
let home = Isolated::new();
let client = mock_client();
let name = "p-mailbox-restart";
let grant = crate::admission::mailbox_grant::resolve(Some(&"cd".repeat(32)), None)
.expect("host-authorized grant")
.expect("requested grant");
let request = persistent_request(&home.rootfs(), name)
.mailbox(grant)
.build()
.expect("persistent definition request");
client
.create_from_request(&request)
.expect("definition only; no boot");
let definition_before = std::fs::read(machine_spec_path(name)).expect("definition");
let value: serde_json::Value = serde_json::from_slice(&definition_before).unwrap();
assert!(
value.get("mailbox").is_none(),
"a one-start grant is not definition state"
);
let started = client
.start_persistent(name)
.await
.expect("plain start on mock without mailbox");
assert_eq!(
started.status,
mvm_core::client::dto::MachineStatus::Running
);
let plan =
mvm_hostd::audit::plan_persist::read_plan(name).expect("canonical persisted admission");
assert!(
plan.mailbox.is_none(),
"plain restart must not recover an earlier request's mailbox grant"
);
}