Make external codec boundaries explicit and exhaustive

This commit is contained in:
ms2k 2026-07-19 05:33:39 +09:00
commit d7de87fb8f
4 changed files with 319 additions and 25 deletions

View file

@ -673,6 +673,10 @@ mod tests {
#[test]
fn network_probe_request_vector_is_stable() {
assert!(
crate::supervision::RUNTIME_SUPERVISOR_SERIALIZED_RECORD_TYPE_NAMES
.contains(&<NetworkProbeRequest as lazaret_core::records::SchemaType>::NAME)
);
let request = NetworkProbeRequest {
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(2),

View file

@ -44,6 +44,14 @@ const MAX_RUNTIME_HANDOFF_BYTES: usize = 64 * 1024;
const READINESS_TIMEOUT: Duration = Duration::from_secs(30);
const TERMINATION_GRACE: Duration = Duration::from_secs(2);
pub const RUNTIME_SUPERVISOR_SERIALIZED_RECORD_TYPE_NAMES: &[&str] = &[
"RuntimeSupervisorHandoff",
"NetworkProbeRequest",
"AttachedRunscSignal",
"SupervisorEvent",
"CoordinatorMessage",
];
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum AttachedRunscSignal {
Stop,

View file

@ -469,6 +469,10 @@ mod handoff_tests {
#[test]
fn v1_vector_and_non_utf8_runsc_arguments_are_stable() {
assert!(
crate::supervision::RUNTIME_SUPERVISOR_SERIALIZED_RECORD_TYPE_NAMES
.contains(&<RuntimeSupervisorHandoff as lazaret_core::records::SchemaType>::NAME)
);
let directory = |number, identity| RuntimeDescriptorV1 {
number,
device: identity,

View file

@ -1,15 +1,241 @@
use std::ffi::OsString;
use lazaret_core::identity::{CoordinatorId, DomainId, GenerationId};
use lazaret_core::records::HostProcessOwnershipSlot;
use lazaret_core::identity::{
BootId, ConfigurationFingerprint, CoordinatorId, DerivedPolicyDigest, DomainId, GenerationId,
HostPidLocator, NetworkSidecarId, PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1,
PidfsProcessIdentityV1, ProcessStartTimeTicks,
};
use lazaret_core::records::{
HostProcessOwnershipSlot, NetworkObservation, NetworkReachabilityProbe,
NetworkReadinessAttestation, NetworkReadinessAttestationFields, PolicyDelivery,
ProcessOwnershipCommitAck, SchemaType, StagedProcessOwnership,
};
use lazaret_linux::ipc::private_seqpacket_pair;
use lazaret_linux::process::ChildStandardIoPolicy;
use rustix::pipe::{PipeFlags, pipe_with};
use sha2::{Digest, Sha256};
use crate::network::NetworkProbeRequest;
use super::runtime::{attached_runsc_standard_io, runsc_delete_target, runsc_target};
use super::*;
fn process() -> PidfsProcessIdentityV1 {
PidfsProcessIdentityV1::new(
PidfsProcessAuthorityV1 {
boot_id: BootId::new([6; 16]),
pidfs_st_dev: PidfsDeviceId::new(7),
pidfs_st_ino: PidfsInodeId::new(8),
},
HostPidLocator::new(9),
ProcessStartTimeTicks::new(10),
)
}
fn staged() -> StagedProcessOwnership {
StagedProcessOwnership::v1(
DomainId::new([1; 32]),
GenerationId::new(2),
CoordinatorId::new([3; 32]),
HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
process(),
)
}
fn event_tag(event: &SupervisorEvent) -> &'static str {
match event {
SupervisorEvent::StateAnchorStaged(_) => "StateAnchorStaged",
SupervisorEvent::StateAnchorReady { .. } => "StateAnchorReady",
SupervisorEvent::GitRoutingStaged(_) => "GitRoutingStaged",
SupervisorEvent::GitRoutingReady { .. } => "GitRoutingReady",
SupervisorEvent::SidecarStarted => "SidecarStarted",
SupervisorEvent::HelperStaged(_) => "HelperStaged",
SupervisorEvent::HelperReady(_) => "HelperReady",
SupervisorEvent::NetworkAttestation(_) => "NetworkAttestation",
SupervisorEvent::NetworkProbe(_) => "NetworkProbe",
SupervisorEvent::HelperExited => "HelperExited",
SupervisorEvent::Staged(_) => "Staged",
SupervisorEvent::Ready { .. } => "Ready",
SupervisorEvent::SignalApplied(_) => "SignalApplied",
SupervisorEvent::SandboxStopped => "SandboxStopped",
SupervisorEvent::GitRoutingStopped { .. } => "GitRoutingStopped",
SupervisorEvent::SidecarStopped => "SidecarStopped",
SupervisorEvent::StateAnchorUnmounted { .. } => "StateAnchorUnmounted",
SupervisorEvent::StateAnchorAbsenceObserved { .. } => "StateAnchorAbsenceObserved",
SupervisorEvent::Exited => "Exited",
SupervisorEvent::AttachedRunscExited => "AttachedRunscExited",
SupervisorEvent::Stopped => "Stopped",
SupervisorEvent::Refused(_) => "Refused",
}
}
fn message_tag(message: &CoordinatorMessage) -> &'static str {
match message {
CoordinatorMessage::MountStateAnchor => "MountStateAnchor",
CoordinatorMessage::StateAnchorStagedAck(_) => "StateAnchorStagedAck",
CoordinatorMessage::StartGitRouting => "StartGitRouting",
CoordinatorMessage::GitRoutingStagedAck(_) => "GitRoutingStagedAck",
CoordinatorMessage::StartSidecar => "StartSidecar",
CoordinatorMessage::HelperStagedAck(_) => "HelperStagedAck",
CoordinatorMessage::NetworkAttestationAccepted(_) => "NetworkAttestationAccepted",
CoordinatorMessage::NetworkReadinessCommitted => "NetworkReadinessCommitted",
CoordinatorMessage::StagedAck(_) => "StagedAck",
CoordinatorMessage::SandboxCommitted => "SandboxCommitted",
CoordinatorMessage::SignalAttachedRunsc(_) => "SignalAttachedRunsc",
CoordinatorMessage::StopSandbox => "StopSandbox",
CoordinatorMessage::StopGitRouting => "StopGitRouting",
CoordinatorMessage::StopSidecar => "StopSidecar",
CoordinatorMessage::UnmountStateAnchor => "UnmountStateAnchor",
CoordinatorMessage::ObserveStateAnchorAbsence { .. } => "ObserveStateAnchorAbsence",
CoordinatorMessage::Exit => "Exit",
CoordinatorMessage::Stop => "Stop",
}
}
fn representative_events(
namespace: NamespaceIdentity,
mount: MountIdentity,
staged: StagedProcessOwnership,
attestation: NetworkReadinessAttestation,
probe: NetworkReachabilityProbe,
) -> Vec<SupervisorEvent> {
vec![
SupervisorEvent::StateAnchorStaged(staged),
SupervisorEvent::StateAnchorReady {
staged,
user_namespace: namespace,
mount_namespace: namespace,
mount,
},
SupervisorEvent::GitRoutingStaged(staged),
SupervisorEvent::GitRoutingReady { staged, mount },
SupervisorEvent::SidecarStarted,
SupervisorEvent::HelperStaged(staged),
SupervisorEvent::HelperReady(staged),
SupervisorEvent::NetworkAttestation(attestation),
SupervisorEvent::NetworkProbe(probe),
SupervisorEvent::HelperExited,
SupervisorEvent::Staged(staged),
SupervisorEvent::Ready {
staged,
sentry_pid_inventory: 14,
},
SupervisorEvent::SignalApplied(AttachedRunscSignal::Continue),
SupervisorEvent::SandboxStopped,
SupervisorEvent::GitRoutingStopped { mount },
SupervisorEvent::SidecarStopped,
SupervisorEvent::StateAnchorUnmounted { mount },
SupervisorEvent::StateAnchorAbsenceObserved {
user_namespace: namespace,
mount_namespace: namespace,
mount,
observed: mount,
},
SupervisorEvent::Exited,
SupervisorEvent::AttachedRunscExited,
SupervisorEvent::Stopped,
SupervisorEvent::Refused("refused".into()),
]
}
fn representative_messages(
namespace: NamespaceIdentity,
mount: MountIdentity,
acknowledgment: ProcessOwnershipCommitAck,
request: NetworkProbeRequest,
) -> Vec<CoordinatorMessage> {
vec![
CoordinatorMessage::MountStateAnchor,
CoordinatorMessage::StateAnchorStagedAck(acknowledgment),
CoordinatorMessage::StartGitRouting,
CoordinatorMessage::GitRoutingStagedAck(acknowledgment),
CoordinatorMessage::StartSidecar,
CoordinatorMessage::HelperStagedAck(acknowledgment),
CoordinatorMessage::NetworkAttestationAccepted(request),
CoordinatorMessage::NetworkReadinessCommitted,
CoordinatorMessage::StagedAck(acknowledgment),
CoordinatorMessage::SandboxCommitted,
CoordinatorMessage::SignalAttachedRunsc(AttachedRunscSignal::Kill),
CoordinatorMessage::StopSandbox,
CoordinatorMessage::StopGitRouting,
CoordinatorMessage::StopSidecar,
CoordinatorMessage::UnmountStateAnchor,
CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace: namespace,
mount_namespace: namespace,
mount,
},
CoordinatorMessage::Exit,
CoordinatorMessage::Stop,
]
}
fn assert_event_variant_coverage(events: &[SupervisorEvent]) {
assert_eq!(
events.iter().map(event_tag).collect::<Vec<_>>(),
[
"StateAnchorStaged",
"StateAnchorReady",
"GitRoutingStaged",
"GitRoutingReady",
"SidecarStarted",
"HelperStaged",
"HelperReady",
"NetworkAttestation",
"NetworkProbe",
"HelperExited",
"Staged",
"Ready",
"SignalApplied",
"SandboxStopped",
"GitRoutingStopped",
"SidecarStopped",
"StateAnchorUnmounted",
"StateAnchorAbsenceObserved",
"Exited",
"AttachedRunscExited",
"Stopped",
"Refused",
]
);
}
fn assert_message_variant_coverage(messages: &[CoordinatorMessage]) {
assert_eq!(
messages.iter().map(message_tag).collect::<Vec<_>>(),
[
"MountStateAnchor",
"StateAnchorStagedAck",
"StartGitRouting",
"GitRoutingStagedAck",
"StartSidecar",
"HelperStagedAck",
"NetworkAttestationAccepted",
"NetworkReadinessCommitted",
"StagedAck",
"SandboxCommitted",
"SignalAttachedRunsc",
"StopSandbox",
"StopGitRouting",
"StopSidecar",
"UnmountStateAnchor",
"ObserveStateAnchorAbsence",
"Exit",
"Stop",
]
);
}
fn corpus_digest<T: SchemaType + serde::Serialize>(records: &[T]) -> [u8; 32] {
let mut corpus = Sha256::new();
for record in records {
let record = encode(record).unwrap();
corpus.update(record.len().to_be_bytes());
corpus.update(record);
}
corpus.finalize().into()
}
#[test]
fn private_control_record_vectors_are_stable() {
let namespace = NamespaceIdentity {
@ -21,32 +247,78 @@ fn private_control_record_vectors_are_stable() {
device: lazaret_core::identity::DeviceId::new(4),
inode: lazaret_core::identity::InodeId::new(5),
};
let records = [
encode(&AttachedRunscSignal::Continue).unwrap(),
encode(&SupervisorEvent::StateAnchorAbsenceObserved {
user_namespace: namespace,
mount_namespace: namespace,
mount,
observed: mount,
})
.unwrap(),
encode(&CoordinatorMessage::ObserveStateAnchorAbsence {
user_namespace: namespace,
mount_namespace: namespace,
mount,
})
.unwrap(),
let staged = staged();
let acknowledgment = ProcessOwnershipCommitAck::v1(staged);
let attestation = NetworkReadinessAttestation::v1(NetworkReadinessAttestationFields {
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(2),
configuration_fingerprint: ConfigurationFingerprint::new([11; 32]),
network_sidecar_id: NetworkSidecarId::new([12; 32]),
derived_policy_digest: DerivedPolicyDigest::new([13; 32]),
enforcement_namespace: namespace,
runtime_supervisor: process(),
connectivity_helper: process(),
observation: NetworkObservation {
policy_delivery: PolicyDelivery::SealedMemfd,
helper_ready: true,
observed_routes: Vec::new(),
routes_attested: true,
},
});
let probe = NetworkReachabilityProbe {
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(2),
enforcement_namespace: namespace,
denied_destinations: Vec::new(),
permitted_destinations: Vec::new(),
dns_observations: Vec::new(),
};
let request = NetworkProbeRequest {
domain_id: DomainId::new([1; 32]),
generation_id: GenerationId::new(2),
enforcement_namespace: namespace,
denied: Vec::new(),
permitted: Vec::new(),
dns: Vec::new(),
};
let signals = [
AttachedRunscSignal::Stop,
AttachedRunscSignal::Continue,
AttachedRunscSignal::Kill,
];
let mut corpus = Sha256::new();
for record in records {
corpus.update(record.len().to_be_bytes());
corpus.update(record);
}
let events = representative_events(namespace, mount, staged, attestation, probe);
let messages = representative_messages(namespace, mount, acknowledgment, request);
assert_eq!(
RUNTIME_SUPERVISOR_SERIALIZED_RECORD_TYPE_NAMES,
&[
"RuntimeSupervisorHandoff",
<NetworkProbeRequest as SchemaType>::NAME,
<AttachedRunscSignal as SchemaType>::NAME,
<SupervisorEvent as SchemaType>::NAME,
<CoordinatorMessage as SchemaType>::NAME,
]
);
assert_event_variant_coverage(&events);
assert_message_variant_coverage(&messages);
assert_eq!(
corpus.finalize().as_slice(),
[
59, 166, 131, 49, 39, 47, 193, 4, 70, 106, 107, 188, 204, 94, 145, 7, 250, 164, 101,
22, 121, 174, 24, 13, 138, 108, 62, 97, 17, 162, 129, 89,
corpus_digest(&signals),
corpus_digest(&events),
corpus_digest(&messages),
],
[
[
81, 139, 171, 185, 206, 78, 197, 44, 238, 115, 83, 251, 40, 178, 113, 209, 41, 53,
251, 52, 93, 195, 13, 90, 91, 33, 97, 9, 204, 249, 127, 100,
],
[
46, 250, 76, 205, 43, 212, 214, 67, 162, 9, 72, 170, 22, 44, 123, 161, 14, 206, 29,
244, 137, 251, 60, 194, 96, 96, 92, 216, 50, 97, 244, 47,
],
[
227, 133, 56, 78, 199, 235, 191, 196, 191, 7, 154, 135, 223, 104, 221, 62, 240,
253, 202, 161, 22, 254, 133, 59, 22, 126, 218, 254, 81, 114, 240, 120,
],
]
);
}