1use std::collections::BTreeMap;
17use std::fmt;
18use std::os::fd::OwnedFd;
19use std::sync::Arc;
20
21use detcore_model::liteinst_native_exec_state as state_frame;
22use serde::Deserialize;
23use serde::Serialize;
24use sha2::Digest as _;
25use sha2::Sha256;
26pub use state_frame::CutBinding as NativeExecNeutralBinding;
28
29pub use super::GlobalRequest;
31pub use super::GlobalResponse;
33use crate::types::DetTid;
34use crate::types::FilesId;
35use crate::types::MmId;
36
37pub const MAX_STATE_BYTES: usize = state_frame::MAX_BYTES;
39pub const MAX_TRANSITIONS: usize = 1024;
41const STATE_HEADER: usize = state_frame::HEADER_BYTES;
42
43#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
45pub struct ExecTicket(pub [u8; 32]);
46
47impl fmt::Debug for ExecTicket {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 f.write_str("ExecTicket(<private>)")
50 }
51}
52
53#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
55pub enum ExecControlError {
56 Unavailable,
58 Identity,
60 Preparation,
62 Forwarding,
64 Bounds,
66 Digest,
68 Transition,
70 Dead,
72 Capacity,
74 Qualification,
76}
77
78#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
81pub struct NativeExecSocketBinding {
82 pub fd: i32,
84 pub device: u64,
86 pub inode: u64,
88}
89impl NativeExecSocketBinding {
90 pub fn object(self) -> (u64, u64) {
92 (self.device, self.inode)
93 }
94}
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
98pub struct NativeExecSocketBindings {
99 pub detlog: NativeExecSocketBinding,
101 pub observer: NativeExecSocketBinding,
103}
104impl NativeExecSocketBindings {
105 pub fn validate(self) -> Result<(), ExecControlError> {
108 if [self.detlog.fd, self.observer.fd]
109 .into_iter()
110 .any(|fd| fd <= 2 || (198..=202).contains(&fd))
111 || self.detlog.fd == self.observer.fd
112 || self.detlog.object() == self.observer.object()
113 {
114 return Err(ExecControlError::Identity);
115 }
116 Ok(())
117 }
118}
119
120#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
122pub struct PrepareNativeExec {
123 pub sockets: NativeExecSocketBindings,
125 pub cut: NativeExecNeutralBinding,
127 pub prepared_mm: MmId,
129 pub config_digest: [u8; 32],
131 pub target_digest: [u8; 32],
133 pub bootstrap_digest: [u8; 32],
135 pub resume_mask: u64,
137 pub snapshot_digest: [u8; 32],
139 pub snapshot: Vec<u8>,
141}
142
143#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145pub struct NativeExecContext {
146 pub owner: DetTid,
148 pub old_mm: MmId,
150 pub prepared_mm: MmId,
152 pub config_digest: [u8; 32],
154}
155
156pub unsafe trait NativeExecProof: fmt::Debug + Send + Sync {
180 fn owner(&self) -> DetTid;
182 fn physical_ids(&self) -> Option<(i32, i32)> {
186 None
187 }
188 fn is_live(&self) -> bool;
190 fn matches_peer(&self, peer: &OwnedFd) -> bool;
192 fn qualify_target(&self, request: &PrepareNativeExec) -> bool;
195 fn qualify_runtime(&self, bytes: &[u8]) -> bool;
197 fn qualify_completion(&self, _ticket: ExecTicket, _binding: NativeExecBinding) -> bool {
203 false
204 }
205}
206
207#[derive(Clone, Copy, Debug, Eq, PartialEq)]
209pub enum ExecStage {
210 Prepared,
212 Issued,
214 Redeemed,
216 Completed,
218 Cancelled,
220 Dead,
222 Failed,
224}
225
226impl ExecStage {
227 fn live(self) -> bool {
228 matches!(self, Self::Prepared | Self::Issued | Self::Redeemed)
229 }
230}
231
232#[derive(Clone, Copy, Debug, Eq, PartialEq)]
234pub struct NativeExecBinding {
235 pub sockets: NativeExecSocketBindings,
237 pub cut: NativeExecNeutralBinding,
239 pub context: NativeExecContext,
241 pub target_digest: [u8; 32],
243 pub bootstrap_digest: [u8; 32],
245 pub resume_mask: u64,
247 pub snapshot_digest: [u8; 32],
249}
250
251#[derive(Clone, Copy, Debug, Eq, PartialEq)]
252enum ProvisionState {
253 Fresh,
254 Consumed,
255 Delivered,
256}
257
258#[derive(Debug)]
259struct Transition {
260 binding: NativeExecBinding,
261 stage: ExecStage,
262 marked_past_first_exec: bool,
263 provision: ProvisionState,
264 tool: Vec<u8>,
265 state: Vec<u8>,
266 state_digest: Option<[u8; 32]>,
267}
268
269pub struct NativeExecRegistry {
273 proof: Arc<dyn NativeExecProof>,
274 nonce: [u8; 32],
275 next_ticket: u64,
276 current_mm: Option<MmId>,
277 current_files: FilesId,
278 initial_mm: Option<MmId>,
279 forwarding_receipt: Option<crate::detlog::CoordinatorForwardingReceipt>,
280 transitions: BTreeMap<ExecTicket, Transition>,
281 dead: bool,
282 terminal_failure: bool,
283}
284
285impl fmt::Debug for NativeExecRegistry {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 f.debug_struct("NativeExecRegistry")
288 .field("owner", &self.proof.owner())
289 .field("retained_transitions", &self.transitions.len())
290 .field("dead", &self.dead)
291 .field("terminal_failure", &self.terminal_failure)
292 .finish_non_exhaustive()
293 }
294}
295
296impl NativeExecRegistry {
297 pub fn new(proof: Arc<dyn NativeExecProof>, nonce: [u8; 32]) -> Result<Self, ExecControlError> {
300 if proof.owner().as_raw() <= 0 || !proof.is_live() || nonce == [0; 32] {
301 return Err(ExecControlError::Identity);
302 }
303 let current_files = FilesId::initial(proof.owner());
307 Ok(Self {
308 proof,
309 nonce,
310 next_ticket: 1,
311 current_mm: None,
312 current_files,
313 initial_mm: None,
314 forwarding_receipt: None,
315 transitions: BTreeMap::new(),
316 dead: false,
317 terminal_failure: false,
318 })
319 }
320
321 fn check_owner(&mut self, owner: DetTid) -> Result<(), ExecControlError> {
322 if self.terminal_failure {
323 return Err(ExecControlError::Dead);
324 }
325 if self.dead || !self.proof.is_live() {
326 self.died();
327 return Err(ExecControlError::Dead);
328 }
329 if owner != self.proof.owner() {
330 return Err(ExecControlError::Identity);
331 }
332 Ok(())
333 }
334
335 fn check_forwarding_cut(
339 &mut self,
340 owner: DetTid,
341 produced: u64,
342 require_empty: bool,
343 ) -> Result<(), ExecControlError> {
344 self.check_owner(owner)?;
345 let (pid, tid) = self
346 .proof
347 .physical_ids()
348 .ok_or(ExecControlError::Identity)?;
349 if pid <= 0 || pid != tid {
350 return Err(ExecControlError::Identity);
351 }
352 if require_empty && produced != 0 {
353 return Err(ExecControlError::Forwarding);
354 }
355 let receipt = crate::detlog::check_native_exec_forwarding_cut(
356 tid,
357 produced,
358 self.forwarding_receipt.as_ref(),
359 )
360 .map_err(|_| ExecControlError::Forwarding)?;
361 self.forwarding_receipt = Some(receipt);
362 Ok(())
363 }
364
365 pub fn prepare(
367 &mut self,
368 context: NativeExecContext,
369 request: PrepareNativeExec,
370 ) -> Result<ExecTicket, ExecControlError> {
371 self.check_owner(context.owner)?;
372 request.sockets.validate()?;
373 let (physical_pid, physical_tid) = self
374 .proof
375 .physical_ids()
376 .ok_or(ExecControlError::Identity)?;
377 request
378 .cut
379 .validate()
380 .map_err(|_| ExecControlError::Identity)?;
381 if physical_pid <= 0
382 || physical_pid != physical_tid
383 || request.cut.physical_tid != physical_tid
384 || request.cut.dettid != context.owner
385 || request.cut.detpid != context.owner
386 || request.cut.old_mm != context.old_mm
387 || request.cut.old_files != self.current_files
388 || request.cut.prepared_mm != context.prepared_mm
389 || request.cut.config_digest != context.config_digest
390 || self.current_mm.is_some_and(|mm| mm != context.old_mm)
391 || context.old_mm.generation().checked_add(1) != Some(context.prepared_mm.generation())
392 || context.prepared_mm.creator() != context.owner
393 || request.prepared_mm != context.prepared_mm
394 || request.config_digest != context.config_digest
395 {
396 return Err(ExecControlError::Identity);
397 }
398 if self.transitions.values().any(|value| value.stage.live()) {
399 return Err(ExecControlError::Transition);
400 }
401 if self.transitions.len() >= MAX_TRANSITIONS {
402 return Err(ExecControlError::Capacity);
403 }
404 if request.snapshot.is_empty()
405 || request.snapshot.len() > MAX_STATE_BYTES - STATE_HEADER - 1
406 {
407 return Err(ExecControlError::Bounds);
408 }
409 if <[u8; 32]>::from(Sha256::digest(&request.snapshot)) != request.snapshot_digest {
410 return Err(ExecControlError::Digest);
411 }
412 if !self.proof.qualify_target(&request) {
413 return Err(ExecControlError::Qualification);
414 }
415 let mut hash = Sha256::new();
418 hash.update(b"Hermit native exec ticket v1\0");
419 hash.update(self.nonce);
420 hash.update(self.next_ticket.to_le_bytes());
421 let ticket = ExecTicket(hash.finalize().into());
422 self.next_ticket = self
423 .next_ticket
424 .checked_add(1)
425 .ok_or(ExecControlError::Capacity)?;
426 if self.transitions.contains_key(&ticket) {
427 return Err(ExecControlError::Transition);
428 }
429 self.current_mm = Some(context.old_mm);
430 self.initial_mm.get_or_insert(context.old_mm);
431 self.transitions.insert(
432 ticket,
433 Transition {
434 binding: NativeExecBinding {
435 sockets: request.sockets,
436 cut: request.cut,
437 context,
438 target_digest: request.target_digest,
439 bootstrap_digest: request.bootstrap_digest,
440 resume_mask: request.resume_mask,
441 snapshot_digest: request.snapshot_digest,
442 },
443 stage: ExecStage::Prepared,
444 marked_past_first_exec: false,
445 provision: ProvisionState::Fresh,
446 tool: request.snapshot,
447 state: Vec::new(),
448 state_digest: None,
449 },
450 );
451 Ok(ticket)
452 }
453
454 fn transaction(
455 &mut self,
456 owner: DetTid,
457 ticket: ExecTicket,
458 mm: MmId,
459 ) -> Result<&mut Transition, ExecControlError> {
460 self.check_owner(owner)?;
461 let entry = self
462 .transitions
463 .get_mut(&ticket)
464 .ok_or(ExecControlError::Identity)?;
465 if entry.binding.context.owner != owner || entry.binding.context.prepared_mm != mm {
466 return Err(ExecControlError::Identity);
467 }
468 Ok(entry)
469 }
470
471 pub(super) fn mark_past_first_exec(
475 &mut self,
476 owner: DetTid,
477 mm: MmId,
478 ) -> Result<(), ExecControlError> {
479 if !self
480 .transitions
481 .values()
482 .any(|entry| entry.stage.live() && entry.binding.context.owner == owner)
483 {
484 return Ok(());
485 }
486 self.check_owner(owner)?;
487 let entry = self
488 .transitions
489 .values_mut()
490 .find(|entry| entry.stage.live() && entry.binding.context.owner == owner)
491 .expect("one live transaction was checked under this mutex");
492 if entry.stage != ExecStage::Redeemed || entry.binding.context.prepared_mm != mm {
493 return Err(ExecControlError::Transition);
494 }
495 entry.marked_past_first_exec = true;
496 Ok(())
497 }
498
499 pub fn permits_connection_close(
506 &mut self,
507 peer: &OwnedFd,
508 predecessor: Option<ExecTicket>,
509 ) -> Result<bool, ExecControlError> {
510 self.check_owner(self.proof.owner())?;
511 if !self.proof.matches_peer(peer) {
512 return Err(ExecControlError::Identity);
513 }
514 let mm = match predecessor {
515 None => match self.initial_mm {
516 Some(mm) => mm,
517 None => return Ok(false),
518 },
519 Some(ticket) => {
520 let entry = self
521 .transitions
522 .get(&ticket)
523 .ok_or(ExecControlError::Identity)?;
524 if !matches!(entry.stage, ExecStage::Redeemed | ExecStage::Completed) {
525 return Err(ExecControlError::Transition);
526 }
527 entry.binding.context.prepared_mm
528 }
529 };
530 Ok(self.transitions.values().any(|entry| {
531 entry.binding.context.old_mm == mm
532 && matches!(
533 entry.stage,
534 ExecStage::Issued | ExecStage::Redeemed | ExecStage::Completed
535 )
536 }))
537 }
538
539 pub fn binding(
542 &mut self,
543 owner: DetTid,
544 ticket: ExecTicket,
545 mm: MmId,
546 ) -> Result<NativeExecBinding, ExecControlError> {
547 let entry = self.transaction(owner, ticket, mm)?;
548 if !entry.stage.live() {
549 return Err(ExecControlError::Transition);
550 }
551 Ok(entry.binding)
552 }
553
554 pub fn issued_binding(
559 &mut self,
560 peer: &OwnedFd,
561 ticket: ExecTicket,
562 ) -> Result<NativeExecBinding, ExecControlError> {
563 self.check_owner(self.proof.owner())?;
564 if !self.proof.matches_peer(peer) {
565 return Err(ExecControlError::Identity);
566 }
567 let entry = self
568 .transitions
569 .get(&ticket)
570 .ok_or(ExecControlError::Identity)?;
571 if entry.stage != ExecStage::Issued {
572 return Err(ExecControlError::Transition);
573 }
574 Ok(entry.binding)
575 }
576
577 pub fn begin_provision(
582 &mut self,
583 peer: &OwnedFd,
584 ticket: ExecTicket,
585 ) -> Result<NativeExecProvision<'_>, ExecControlError> {
586 self.check_owner(self.proof.owner())?;
587 if !self.proof.matches_peer(peer) {
588 return Err(ExecControlError::Identity);
589 }
590 let entry = self
591 .transitions
592 .get_mut(&ticket)
593 .ok_or(ExecControlError::Identity)?;
594 if entry.stage != ExecStage::Prepared || entry.provision != ProvisionState::Fresh {
595 return Err(ExecControlError::Transition);
596 }
597 let digest = entry.state_digest.ok_or(ExecControlError::Transition)?;
598 let binding = entry.binding;
599 entry.provision = ProvisionState::Consumed;
600 Ok(NativeExecProvision {
601 registry: self,
602 ticket,
603 binding,
604 digest,
605 completed: false,
606 })
607 }
608
609 pub fn seal(
612 &mut self,
613 owner: DetTid,
614 ticket: ExecTicket,
615 mm: MmId,
616 runtime_digest: [u8; 32],
617 runtime: &[u8],
618 ) -> Result<[u8; 32], ExecControlError> {
619 self.check_owner(owner)?;
620 if runtime.is_empty() || runtime.len() > MAX_STATE_BYTES - STATE_HEADER - 1 {
621 return Err(ExecControlError::Bounds);
622 }
623 if <[u8; 32]>::from(Sha256::digest(runtime)) != runtime_digest {
624 return Err(ExecControlError::Digest);
625 }
626 if !self.proof.qualify_runtime(runtime) {
627 return Err(ExecControlError::Qualification);
628 }
629 let entry = self.transaction(owner, ticket, mm)?;
630 if entry.stage != ExecStage::Prepared || entry.state_digest.is_some() {
631 return Err(ExecControlError::Transition);
632 }
633 let length = STATE_HEADER
634 .checked_add(entry.tool.len())
635 .and_then(|n| n.checked_add(runtime.len()))
636 .ok_or(ExecControlError::Bounds)?;
637 if length > MAX_STATE_BYTES {
638 return Err(ExecControlError::Bounds);
639 }
640 let mut state = Vec::with_capacity(length);
641 let mut header = [0; STATE_HEADER];
642 state_frame::encode_header(
643 entry.binding.cut,
644 entry.tool.len(),
645 runtime.len(),
646 &mut header,
647 )
648 .map_err(|_| ExecControlError::Bounds)?;
649 state.extend_from_slice(&header);
650 state.extend_from_slice(&entry.tool);
651 state.extend_from_slice(runtime);
652 let digest = Sha256::digest(&state).into();
653 entry.tool = Vec::new();
654 entry.state = state;
655 entry.state_digest = Some(digest);
656 Ok(digest)
657 }
658
659 pub fn issue(
661 &mut self,
662 owner: DetTid,
663 ticket: ExecTicket,
664 mm: MmId,
665 digest: [u8; 32],
666 ) -> Result<(), ExecControlError> {
667 let entry = self.transaction(owner, ticket, mm)?;
668 if entry.stage != ExecStage::Prepared
669 || entry.state_digest != Some(digest)
670 || entry.provision != ProvisionState::Delivered
671 {
672 return Err(ExecControlError::Transition);
673 }
674 entry.stage = ExecStage::Issued;
675 Ok(())
676 }
677
678 pub fn redeem(
682 &mut self,
683 peer: &OwnedFd,
684 ticket: ExecTicket,
685 config: [u8; 32],
686 digest: [u8; 32],
687 ) -> Result<Vec<u8>, ExecControlError> {
688 let owner = self.proof.owner();
689 self.check_owner(owner)?;
690 if !self.proof.matches_peer(peer) {
691 return Err(ExecControlError::Identity);
692 }
693 let entry = self
694 .transitions
695 .get_mut(&ticket)
696 .ok_or(ExecControlError::Identity)?;
697 if entry.stage != ExecStage::Issued {
698 return Err(ExecControlError::Transition);
699 }
700 if entry.binding.context.config_digest != config || entry.state_digest != Some(digest) {
701 return Err(ExecControlError::Digest);
702 }
703 entry.stage = ExecStage::Redeemed;
704 Ok(std::mem::take(&mut entry.state))
705 }
706
707 pub fn cancel(
710 &mut self,
711 owner: DetTid,
712 ticket: ExecTicket,
713 mm: MmId,
714 ) -> Result<(), ExecControlError> {
715 let entry = self.transaction(owner, ticket, mm)?;
716 if !matches!(entry.stage, ExecStage::Prepared | ExecStage::Issued) {
717 return Err(ExecControlError::Transition);
718 }
719 entry.stage = ExecStage::Cancelled;
720 entry.tool = Vec::new();
721 entry.state = Vec::new();
722 Ok(())
723 }
724
725 pub fn complete(
728 &mut self,
729 owner: DetTid,
730 ticket: ExecTicket,
731 mm: MmId,
732 ) -> Result<(), ExecControlError> {
733 let entry = self.transaction(owner, ticket, mm)?;
734 if entry.stage != ExecStage::Redeemed {
735 return Err(ExecControlError::Transition);
736 }
737 let binding = entry.binding;
738 if !self.proof.qualify_completion(ticket, binding) {
739 self.failed(ticket)?;
740 return Err(ExecControlError::Qualification);
741 }
742 self.transitions
744 .get_mut(&ticket)
745 .expect("retained Redeemed record")
746 .stage = ExecStage::Completed;
747 self.current_mm = Some(mm);
748 self.current_files = binding.cut.old_files.for_exec(owner);
749 Ok(())
750 }
751
752 pub fn failed(&mut self, ticket: ExecTicket) -> Result<(), ExecControlError> {
755 let entry = self
756 .transitions
757 .get_mut(&ticket)
758 .ok_or(ExecControlError::Identity)?;
759 if !entry.stage.live() {
760 return Err(ExecControlError::Transition);
761 }
762 entry.stage = ExecStage::Failed;
763 entry.tool = Vec::new();
764 entry.state = Vec::new();
765 self.terminal_failure = true;
766 Ok(())
767 }
768
769 pub fn provision_transport_failed(&mut self) {
774 self.terminal_failure = true;
775 for entry in self
776 .transitions
777 .values_mut()
778 .filter(|entry| entry.stage.live())
779 {
780 entry.stage = ExecStage::Failed;
781 entry.tool = Vec::new();
782 entry.state = Vec::new();
783 }
784 }
785
786 pub fn died(&mut self) {
788 self.dead = true;
789 for entry in self
790 .transitions
791 .values_mut()
792 .filter(|entry| entry.stage.live())
793 {
794 entry.stage = ExecStage::Dead;
795 entry.tool = Vec::new();
796 entry.state = Vec::new();
797 }
798 }
799
800 pub fn stage(&self, ticket: ExecTicket) -> Option<ExecStage> {
802 self.transitions.get(&ticket).map(|entry| entry.stage)
803 }
804}
805
806#[must_use = "an incomplete provision poisons the native birth"]
809pub struct NativeExecProvision<'a> {
810 registry: &'a mut NativeExecRegistry,
811 ticket: ExecTicket,
812 binding: NativeExecBinding,
813 digest: [u8; 32],
814 completed: bool,
815}
816
817impl NativeExecProvision<'_> {
818 pub fn binding(&self) -> NativeExecBinding {
820 self.binding
821 }
822 pub fn state_digest(&self) -> [u8; 32] {
824 self.digest
825 }
826
827 pub unsafe fn complete(mut self) -> Result<(), ExecControlError> {
836 self.registry.check_owner(self.binding.context.owner)?;
837 let entry = self.registry.transaction(
838 self.binding.context.owner,
839 self.ticket,
840 self.binding.context.prepared_mm,
841 )?;
842 if entry.stage != ExecStage::Prepared
843 || entry.provision != ProvisionState::Consumed
844 || entry.state_digest != Some(self.digest)
845 {
846 return Err(ExecControlError::Transition);
847 }
848 entry.provision = ProvisionState::Delivered;
849 self.completed = true;
850 Ok(())
851 }
852}
853
854impl Drop for NativeExecProvision<'_> {
855 fn drop(&mut self) {
856 if !self.completed {
857 let _ = self.registry.failed(self.ticket);
860 self.registry.terminal_failure = true;
861 }
862 }
863}
864
865pub fn split_state(bytes: &[u8]) -> Result<(&[u8], &[u8]), ExecControlError> {
869 let (_, tool, runtime) = split_state_binding(bytes)?;
870 Ok((tool, runtime))
871}
872
873pub fn split_state_binding(
876 bytes: &[u8],
877) -> Result<(NativeExecNeutralBinding, &[u8], &[u8]), ExecControlError> {
878 state_frame::decode(bytes).map_err(|_| ExecControlError::Bounds)
879}
880
881pub(super) fn is_native_exec_control(mut request: &super::GlobalRequest) -> bool {
884 use super::GlobalRequest as Q;
885 loop {
886 match request {
887 Q::WithForwardedCount(_, inner) => request = inner,
888 Q::PrepareNativeExec(_)
889 | Q::SealNativeExecStats { .. }
890 | Q::IssueNativeExec { .. }
891 | Q::CancelNativeExec { .. }
892 | Q::CompleteNativeExec { .. } => return true,
893 _ => return false,
894 }
895 }
896}
897
898#[derive(Clone, Copy)]
899enum ControlCut {
900 Prepared,
901 EmptyPrepared,
902 Completed,
903}
904
905impl super::GlobalState {
906 pub fn native_exec_host_config(&self) -> &crate::Config {
910 &self.cfg
911 }
912
913 pub fn install_native_exec_registry(
916 &self,
917 registry: NativeExecRegistry,
918 ) -> Result<(), ExecControlError> {
919 self.native_exec_registry
920 .set(std::sync::Mutex::new(registry))
921 .map_err(|_| ExecControlError::Transition)
922 }
923
924 pub fn native_exec_registry(&self) -> Option<&std::sync::Mutex<NativeExecRegistry>> {
927 self.native_exec_registry.get()
928 }
929
930 fn native_exec_transition<R>(
931 &self,
932 owner: DetTid,
933 request_mm: MmId,
934 produced: Option<u64>,
935 cut: ControlCut,
936 ticket: Option<ExecTicket>,
937 transition: impl FnOnce(
938 &mut NativeExecRegistry,
939 Option<NativeExecContext>,
940 ) -> Result<R, ExecControlError>,
941 ) -> Result<R, ExecControlError> {
942 let produced = produced.ok_or(ExecControlError::Forwarding)?;
943 let registry = self
944 .native_exec_registry
945 .get()
946 .ok_or(ExecControlError::Unavailable)?;
947 let sched = self.sched.lock().unwrap();
951 if sched.backend_failed() {
952 return Err(ExecControlError::Dead);
953 }
954 if !self.cfg.sequentialize_threads
955 || sched.registered_process(owner) != Some(owner)
956 || !sched.thread_is_live(owner)
957 || sched.thread_is_logically_killed(owner)
958 {
959 return Err(ExecControlError::Identity);
960 }
961 let live_group: Vec<_> = sched.live_process_threads(owner).collect();
962 if live_group != [owner]
963 || !sched.thread_tree.unreaped_children_of(owner).is_empty()
964 || !sched.holds_serial_grant(owner)
965 {
966 return Err(ExecControlError::Preparation);
967 }
968 let pending = self.pending_exec_states.lock().unwrap();
969 let context = match cut {
970 ControlCut::Completed => {
971 if !self
972 .past_first_execve
973 .load(std::sync::atomic::Ordering::SeqCst)
974 || pending.contains_key(&owner)
975 || sched.thread_mm(owner) != Some(request_mm)
976 || !sched.rpc_incarnation_matches(owner, request_mm)
977 {
978 return Err(ExecControlError::Preparation);
979 }
980 None
981 }
982 ControlCut::Prepared | ControlCut::EmptyPrepared => {
983 let prepared = pending.get(&owner).ok_or(ExecControlError::Preparation)?;
984 if !prepared.fd_blocking.robust_wakes.is_empty() {
987 return Err(ExecControlError::Preparation);
988 }
989 if prepared.caller != owner || prepared.process != owner {
990 return Err(ExecControlError::Identity);
991 }
992 if sched.thread_mm(owner) != Some(prepared.mm)
993 || !sched.rpc_incarnation_matches(owner, prepared.mm)
994 || request_mm != prepared.mm.for_exec(owner)
995 {
996 return Err(ExecControlError::Identity);
997 }
998 Some(NativeExecContext {
999 owner,
1000 old_mm: prepared.mm,
1001 prepared_mm: request_mm,
1002 config_digest: crate::native_exec::native_exec_config_digest(&self.cfg)
1003 .map_err(|_| ExecControlError::Digest)?,
1004 })
1005 }
1006 };
1007 let mut registry = registry.lock().unwrap();
1008 registry.check_owner(owner)?;
1009 if let Some(ticket) = ticket {
1010 if registry.forwarding_receipt.is_none() {
1011 return Err(ExecControlError::Forwarding);
1012 }
1013 let binding = registry.binding(owner, ticket, request_mm)?;
1014 if context.is_some_and(|context| binding.context != context) {
1015 return Err(ExecControlError::Identity);
1016 }
1017 if matches!(cut, ControlCut::Completed)
1018 && !registry
1019 .transaction(owner, ticket, request_mm)?
1020 .marked_past_first_exec
1021 {
1022 return Err(ExecControlError::Preparation);
1023 }
1024 }
1025 registry.check_forwarding_cut(owner, produced, matches!(cut, ControlCut::EmptyPrepared))?;
1026 transition(&mut registry, context)
1027 }
1028
1029 pub(super) fn dispatch_native_exec_control(
1030 &self,
1031 from: reverie::Tid,
1032 request: <Self as reverie::GlobalTool>::Request,
1033 ) -> <Self as reverie::GlobalTool>::Response {
1034 use super::GlobalRequest as Q;
1035 use super::GlobalResponse as R;
1036 let (_, mm, mut request) = request;
1037 let mut produced = None;
1038 let mut envelopes = 0usize;
1039 loop {
1040 match request {
1041 Q::WithForwardedCount(count, inner) => {
1042 envelopes = envelopes.saturating_add(1);
1043 produced = Some(count);
1044 request = *inner;
1045 }
1046 leaf => {
1047 request = leaf;
1048 break;
1049 }
1050 }
1051 }
1052 if envelopes != 1 {
1053 produced = None;
1054 }
1055 let owner = DetTid::from_raw(from.as_raw());
1056 let response = match request {
1057 Q::PrepareNativeExec(request) => R::PrepareNativeExec(self.native_exec_transition(
1058 owner,
1059 mm,
1060 produced,
1061 ControlCut::Prepared,
1062 None,
1063 |registry, context| {
1064 registry.prepare(
1065 context.expect("prepared native cut supplies context"),
1066 request,
1067 )
1068 },
1069 )),
1070 Q::SealNativeExecStats {
1071 ticket,
1072 runtime_digest,
1073 runtime,
1074 } => R::SealNativeExecStats(self.native_exec_transition(
1075 owner,
1076 mm,
1077 produced,
1078 ControlCut::EmptyPrepared,
1079 Some(ticket),
1080 |registry, _| registry.seal(owner, ticket, mm, runtime_digest, &runtime),
1081 )),
1082 Q::IssueNativeExec {
1083 ticket,
1084 state_digest,
1085 } => R::IssueNativeExec(self.native_exec_transition(
1086 owner,
1087 mm,
1088 produced,
1089 ControlCut::EmptyPrepared,
1090 Some(ticket),
1091 |registry, _| registry.issue(owner, ticket, mm, state_digest),
1092 )),
1093 Q::CancelNativeExec { ticket } => R::CancelNativeExec(self.native_exec_transition(
1094 owner,
1095 mm,
1096 produced,
1097 ControlCut::Prepared,
1098 Some(ticket),
1099 |registry, _| registry.cancel(owner, ticket, mm),
1100 )),
1101 Q::CompleteNativeExec { ticket } => R::CompleteNativeExec(self.native_exec_transition(
1102 owner,
1103 mm,
1104 produced,
1105 ControlCut::Completed,
1106 Some(ticket),
1107 |registry, _| registry.complete(owner, ticket, mm),
1108 )),
1109 _ => unreachable!("only native leaves are dispatched on the neutral path"),
1110 };
1111 (None, response)
1112 }
1113}
1114
1115#[cfg(test)]
1116mod control_tests {
1117 use std::os::fd::AsRawFd;
1118 use std::os::fd::FromRawFd;
1119 use std::os::fd::OwnedFd;
1120 use std::sync::Arc;
1121 use std::sync::Mutex;
1122 use std::time::Duration;
1123
1124 use reverie::GlobalTool;
1125 use reverie::Tid;
1126 use sha2::Sha256;
1127
1128 use super::*;
1129 use crate::config::Config;
1130 use crate::ivar::Ivar;
1131 use crate::scheduler::DEFAULT_PRIORITY;
1132 use crate::scheduler::ThreadNextTurn;
1133 use crate::tool_global::GlobalRequest as Q;
1134 use crate::tool_global::GlobalResponse as R;
1135 use crate::tool_global::GlobalState;
1136 use crate::tool_global::PendingExecState;
1137 use crate::types::DetTime;
1138 use crate::types::LogicalTime;
1139
1140 #[derive(Debug)]
1144 struct ProtocolProof {
1145 owner: DetTid,
1146 physical: Option<(i32, i32)>,
1147 }
1148
1149 unsafe impl NativeExecProof for ProtocolProof {
1152 fn owner(&self) -> DetTid {
1153 self.owner
1154 }
1155 fn physical_ids(&self) -> Option<(i32, i32)> {
1156 self.physical
1157 }
1158 fn is_live(&self) -> bool {
1159 true
1160 }
1161 fn matches_peer(&self, _: &OwnedFd) -> bool {
1162 true
1163 }
1164 fn qualify_target(&self, request: &PrepareNativeExec) -> bool {
1165 request.target_digest == [2; 32]
1166 && request.bootstrap_digest == [3; 32]
1167 && request.sockets.detlog.object() == (1, 2)
1168 && request.sockets.observer.object() == (1, 3)
1169 }
1170 fn qualify_runtime(&self, bytes: &[u8]) -> bool {
1171 bytes == b"protocol-runtime"
1172 }
1173 fn qualify_completion(&self, _: ExecTicket, _: NativeExecBinding) -> bool {
1174 true
1176 }
1177 }
1178
1179 fn registry(owner: DetTid, physical: Option<(i32, i32)>) -> NativeExecRegistry {
1180 NativeExecRegistry::new(Arc::new(ProtocolProof { owner, physical }), [7; 32]).unwrap()
1181 }
1182
1183 fn fixture(install: bool, physical: Option<(i32, i32)>) -> (GlobalState, DetTid, MmId, MmId) {
1184 let cfg = Config {
1185 sequentialize_threads: true,
1186 ..Config::default()
1187 };
1188 let state = GlobalState::initialize(&cfg, false);
1189 let owner = DetTid::from_raw(41);
1190 let old_mm = MmId::initial(owner).for_exec(owner);
1191 {
1192 let mut sched = state.sched.lock().unwrap();
1193 sched.thread_tree.add_child(owner, owner, true);
1194 sched.next_turns.insert(
1195 owner,
1196 ThreadNextTurn {
1197 dettid: owner,
1198 child_tid_addr: 0,
1199 req: Ivar::new(),
1200 resp: Ivar::new(),
1201 protocol: Default::default(),
1202 },
1203 );
1204 sched.priorities.insert(owner, DEFAULT_PRIORITY);
1205 sched.record_thread_mm(owner, old_mm);
1206 sched.install_test_exec_incarnation(owner, old_mm);
1207 sched.set_running_for_test(owner);
1208 assert!(sched.prepare_exec_teardown(owner, owner, old_mm));
1209 }
1210 state.pending_exec_states.lock().unwrap().insert(
1211 owner,
1212 PendingExecState {
1213 caller: owner,
1214 process: owner,
1215 mm: old_mm,
1216 fd_blocking: Default::default(),
1217 },
1218 );
1219 let mut clock = DetTime::new(&cfg);
1220 clock.add_syscall_with_cost(1337);
1221 state.global_time.lock().unwrap().update_global_time(
1222 owner,
1223 clock.as_nanos(),
1224 clock.inherited_nanos(),
1225 );
1226 if install {
1227 state
1228 .install_native_exec_registry(registry(owner, physical))
1229 .unwrap();
1230 }
1231 (state, owner, old_mm, old_mm.for_exec(owner))
1232 }
1233
1234 fn prepare(state: &GlobalState, mm: MmId) -> Q {
1235 let bytes = b"protocol-tool".to_vec();
1236 let owner = DetTid::from_raw(41);
1237 let old_mm = state.pending_exec_states.lock().unwrap()[&owner].mm;
1238 let config_digest = crate::native_exec::native_exec_config_digest(&state.cfg).unwrap();
1239 let physical_tid = state
1240 .native_exec_registry()
1241 .and_then(|mutex| mutex.lock().unwrap().proof.physical_ids())
1242 .map_or(41, |ids| ids.1);
1243 Q::PrepareNativeExec(PrepareNativeExec {
1244 sockets: NativeExecSocketBindings {
1245 detlog: NativeExecSocketBinding {
1246 fd: 1025,
1247 device: 1,
1248 inode: 2,
1249 },
1250 observer: NativeExecSocketBinding {
1251 fd: 1026,
1252 device: 1,
1253 inode: 3,
1254 },
1255 },
1256 cut: NativeExecNeutralBinding {
1257 dettid: owner,
1258 detpid: owner,
1259 physical_tid,
1260 old_mm,
1261 old_files: FilesId::initial(owner),
1262 prepared_mm: mm,
1263 config_digest,
1264 },
1265 prepared_mm: mm,
1266 config_digest: crate::native_exec::native_exec_config_digest(&state.cfg).unwrap(),
1267 target_digest: [2; 32],
1268 bootstrap_digest: [3; 32],
1269 resume_mask: 0x42,
1270 snapshot_digest: Sha256::digest(&bytes).into(),
1271 snapshot: bytes,
1272 })
1273 }
1274
1275 fn counted(count: u64, request: Q) -> Q {
1276 Q::WithForwardedCount(count, Box::new(request))
1277 }
1278
1279 fn encoded<T: serde::Serialize>(value: &T) -> Vec<u8> {
1280 bincode::serde::encode_to_vec(value, bincode::config::legacy()).unwrap()
1281 }
1282
1283 async fn neutral(state: &GlobalState, owner: DetTid, mm: MmId, request: Q) -> R {
1284 let now = state.global_time.lock().unwrap().as_nanos();
1287 {
1288 let mut sched = state.sched.lock().unwrap();
1289 sched.register_alarm(
1290 owner,
1291 owner,
1292 now,
1293 LogicalTime::from_nanos(5000),
1294 LogicalTime::ZERO,
1295 nix::sys::signal::Signal::SIGALRM,
1296 );
1297 sched.note_arming_hint(owner, now);
1298 }
1299 let global_before = encoded(&*state.global_time.lock().unwrap());
1300 let sched_before = format!("{:?}", *state.sched.lock().unwrap());
1301 let pending_before = state.pending_exec_states.lock().unwrap().clone();
1302 let mut guest_time = DetTime::new(&state.cfg);
1303 guest_time.add_syscall_with_cost(987654);
1304 let (time, response) = state
1305 .receive_rpc(
1306 Tid::from_raw(owner.as_raw()),
1307 (guest_time.clone(), mm, request),
1308 )
1309 .await;
1310 assert_eq!(time, None);
1311 assert!(!matches!(response, R::WithDeadline(..)));
1312 assert_eq!(
1313 encoded(&*state.global_time.lock().unwrap()),
1314 global_before,
1315 "complete GlobalTime remains unchanged"
1316 );
1317 assert_eq!(
1318 format!("{:?}", *state.sched.lock().unwrap()),
1319 sched_before,
1320 "complete scheduler debug state, including PRNGs, grants and hint, is unchanged"
1321 );
1322 assert_eq!(*state.pending_exec_states.lock().unwrap(), pending_before);
1323 assert_eq!(
1324 state.sched.lock().unwrap().take_deadline_hint(owner),
1325 Some(Duration::from_nanos(5000))
1326 );
1327 response
1328 }
1329
1330 struct Source {
1331 send: OwnedFd,
1332 written: Arc<Mutex<Vec<Vec<u8>>>>,
1333 }
1334
1335 impl Source {
1336 fn new() -> Self {
1337 let mut fds = [-1; 2];
1338 assert_eq!(
1340 unsafe {
1341 libc::socketpair(
1342 libc::AF_UNIX,
1343 libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC,
1344 0,
1345 fds.as_mut_ptr(),
1346 )
1347 },
1348 0
1349 );
1350 let receive = unsafe { OwnedFd::from_raw_fd(fds[0]) };
1351 let send = unsafe { OwnedFd::from_raw_fd(fds[1]) };
1352 let written = Arc::new(Mutex::new(Vec::new()));
1353 let sink = Arc::clone(&written);
1354 crate::detlog::set_forwarded_source(
1355 receive,
1356 Box::new(move |bytes| sink.lock().unwrap().push(bytes.to_vec())),
1357 );
1358 Self { send, written }
1359 }
1360 fn send(&self, physical: i32, index: u64, bytes: &[u8]) {
1361 let record = crate::detlog::tagged_forwarded_message(physical, index, bytes);
1362 assert_eq!(
1364 unsafe {
1365 libc::send(
1366 self.send.as_raw_fd(),
1367 record.as_ptr().cast(),
1368 record.len(),
1369 libc::MSG_NOSIGNAL,
1370 )
1371 },
1372 record.len() as isize
1373 );
1374 }
1375 }
1376
1377 fn isolated(name: &str, body: impl FnOnce()) {
1381 const KEY: &str = "HERMIT_NATIVE_CONTROL_PROTOCOL_CHILD";
1382 if std::env::var(KEY).as_deref() == Ok(name) {
1383 body();
1384 return;
1385 }
1386 let full_name = format!("tool_global::native_exec::control_tests::{name}");
1387 let output = std::process::Command::new(std::env::current_exe().unwrap())
1388 .args(["--exact", &full_name, "--nocapture", "--test-threads=1"])
1389 .env(KEY, name)
1390 .output()
1391 .unwrap();
1392 assert!(
1393 output.status.success(),
1394 "{full_name}: {:?}\n{}\n{}",
1395 output.status,
1396 String::from_utf8_lossy(&output.stdout),
1397 String::from_utf8_lossy(&output.stderr)
1398 );
1399 let stdout = String::from_utf8_lossy(&output.stdout);
1400 assert!(
1401 stdout.contains("running 1 test") && stdout.contains("1 passed; 0 failed"),
1402 "the exact child test must actually execute: {stdout}"
1403 );
1404 }
1405
1406 #[test]
1407 fn native_exec_coordinator_robust_wakes_refuse_without_consuming_preparation() {
1408 isolated(
1409 "native_exec_coordinator_robust_wakes_refuse_without_consuming_preparation",
1410 || {
1411 tokio::runtime::Builder::new_current_thread()
1412 .enable_all()
1413 .build()
1414 .unwrap()
1415 .block_on(async {
1416 let _source = Source::new();
1419 let physical = i32::try_from(std::process::id()).unwrap();
1420 let (state, owner, old_mm, mm) = fixture(true, Some((physical, physical)));
1421 let clean_request = prepare(&state, mm);
1422 let clean_ticket =
1423 match neutral(&state, owner, mm, counted(0, clean_request)).await {
1424 R::PrepareNativeExec(Ok(ticket)) => ticket,
1425 response => panic!("clean preparation must qualify: {response:?}"),
1426 };
1427 assert_eq!(
1428 neutral(
1429 &state,
1430 owner,
1431 mm,
1432 counted(
1433 0,
1434 Q::CancelNativeExec {
1435 ticket: clean_ticket
1436 }
1437 ),
1438 )
1439 .await,
1440 R::CancelNativeExec(Ok(()))
1441 );
1442 state
1443 .pending_exec_states
1444 .lock()
1445 .unwrap()
1446 .get_mut(&owner)
1447 .unwrap()
1448 .fd_blocking
1449 .robust_wakes
1450 .push((owner, crate::types::FutexID::private(old_mm, 0x456000)));
1451 let request = prepare(&state, mm);
1452 assert_eq!(
1453 neutral(&state, owner, mm, counted(0, request)).await,
1454 R::PrepareNativeExec(Err(ExecControlError::Preparation))
1455 );
1456 assert_eq!(
1457 state.pending_exec_states.lock().unwrap()[&owner]
1458 .fd_blocking
1459 .robust_wakes,
1460 vec![(owner, crate::types::FutexID::private(old_mm, 0x456000))]
1461 );
1462 });
1463 },
1464 );
1465 }
1466
1467 #[test]
1475 fn native_exec_preparation_counts_only_the_owners_own_live_threads() {
1476 isolated(
1477 "native_exec_preparation_counts_only_the_owners_own_live_threads",
1478 || {
1479 tokio::runtime::Builder::new_current_thread()
1480 .enable_all()
1481 .build()
1482 .unwrap()
1483 .block_on(async {
1484 let _source = Source::new();
1485 let physical = i32::try_from(std::process::id()).unwrap();
1486 let (state, owner, _, mm) = fixture(true, Some((physical, physical)));
1487 let register = |thread: DetTid| {
1488 let mut sched = state.sched.lock().unwrap();
1489 sched.next_turns.insert(
1490 thread,
1491 ThreadNextTurn {
1492 dettid: thread,
1493 child_tid_addr: 0,
1494 req: Ivar::new(),
1495 resp: Ivar::new(),
1496 protocol: Default::default(),
1497 },
1498 );
1499 sched.priorities.insert(thread, DEFAULT_PRIORITY);
1500 };
1501 let tid = DetTid::from_raw;
1502 {
1505 let mut sched = state.sched.lock().unwrap();
1506 let tree = &mut sched.thread_tree;
1507 tree.add_child(owner, tid(43), false);
1508 tree.add_child_with_wait_metadata(
1509 owner,
1510 tid(44),
1511 true,
1512 true,
1513 libc::SIGCHLD,
1514 );
1515 tree.add_child(tid(44), tid(43), false);
1516 }
1517 register(tid(43));
1518 let ticket =
1519 match neutral(&state, owner, mm, counted(0, prepare(&state, mm))).await
1520 {
1521 R::PrepareNativeExec(Ok(ticket)) => ticket,
1522 response => panic!(
1523 "another process's reused thread must not refuse: {response:?}"
1524 ),
1525 };
1526 assert_eq!(
1527 neutral(
1528 &state,
1529 owner,
1530 mm,
1531 counted(0, Q::CancelNativeExec { ticket }),
1532 )
1533 .await,
1534 R::CancelNativeExec(Ok(()))
1535 );
1536 {
1537 let mut sched = state.sched.lock().unwrap();
1538 let tree = &mut sched.thread_tree;
1539 tree.add_child(owner, tid(45), false);
1540 tree.add_child(tid(45), tid(46), false);
1541 tree.add_child_with_wait_metadata(
1542 owner,
1543 tid(45),
1544 true,
1545 true,
1546 libc::SIGCHLD,
1547 );
1548 }
1549 register(tid(46));
1550 assert_eq!(
1551 neutral(&state, owner, mm, counted(0, prepare(&state, mm))).await,
1552 R::PrepareNativeExec(Err(ExecControlError::Preparation))
1553 );
1554 });
1555 },
1556 );
1557 }
1558
1559 #[test]
1560 fn native_exec_control_absent_and_envelope_refusals_are_neutral() {
1561 isolated(
1562 "native_exec_control_absent_and_envelope_refusals_are_neutral",
1563 || {
1564 tokio::runtime::Builder::new_current_thread()
1565 .enable_all()
1566 .build()
1567 .unwrap()
1568 .block_on(async {
1569 let (state, owner, _, mm) = fixture(false, None);
1570 let ticket = ExecTicket([8; 32]);
1571 let requests = [
1572 (
1573 prepare(&state, mm),
1574 R::PrepareNativeExec(Err(ExecControlError::Unavailable)),
1575 ),
1576 (
1577 Q::SealNativeExecStats {
1578 ticket,
1579 runtime_digest: [0; 32],
1580 runtime: vec![1],
1581 },
1582 R::SealNativeExecStats(Err(ExecControlError::Unavailable)),
1583 ),
1584 (
1585 Q::IssueNativeExec {
1586 ticket,
1587 state_digest: [0; 32],
1588 },
1589 R::IssueNativeExec(Err(ExecControlError::Unavailable)),
1590 ),
1591 (
1592 Q::CancelNativeExec { ticket },
1593 R::CancelNativeExec(Err(ExecControlError::Unavailable)),
1594 ),
1595 (
1596 Q::CompleteNativeExec { ticket },
1597 R::CompleteNativeExec(Err(ExecControlError::Unavailable)),
1598 ),
1599 ];
1600 for (request, expected) in requests {
1601 assert_eq!(
1602 neutral(&state, owner, mm, counted(0, request)).await,
1603 expected
1604 );
1605 }
1606 assert_eq!(
1607 neutral(&state, owner, mm, prepare(&state, mm)).await,
1608 R::PrepareNativeExec(Err(ExecControlError::Forwarding))
1609 );
1610 assert_eq!(
1611 neutral(
1612 &state,
1613 owner,
1614 mm,
1615 counted(0, counted(0, prepare(&state, mm)))
1616 )
1617 .await,
1618 R::PrepareNativeExec(Err(ExecControlError::Forwarding))
1619 );
1620 let before = format!("{:?}", *state.sched.lock().unwrap());
1622 let response = state
1623 .dispatch_rpc(
1624 Tid::from_raw(owner.as_raw()),
1625 (DetTime::new(&state.cfg), mm, prepare(&state, mm)),
1626 )
1627 .await;
1628 assert_eq!(
1629 response,
1630 (
1631 None,
1632 R::PrepareNativeExec(Err(ExecControlError::Forwarding))
1633 )
1634 );
1635 assert_eq!(format!("{:?}", *state.sched.lock().unwrap()), before);
1636 });
1637 },
1638 );
1639 }
1640
1641 #[test]
1642 fn native_exec_control_protocol_counts_real_source_and_completes_after_lifecycle() {
1643 isolated(
1644 "native_exec_control_protocol_counts_real_source_and_completes_after_lifecycle",
1645 || {
1646 tokio::runtime::Builder::new_current_thread()
1647 .enable_all()
1648 .build()
1649 .unwrap()
1650 .block_on(async {
1651 let physical = unsafe { libc::getpid() };
1654 let source = Source::new();
1655 let (state, owner, old, mm) = fixture(true, Some((physical, physical)));
1656 crate::detlog::write_forwarded_for(physical, true);
1660 assert_eq!(
1661 state.install_native_exec_registry(registry(
1662 owner,
1663 Some((physical, physical))
1664 )),
1665 Err(ExecControlError::Transition)
1666 );
1667 source.send(physical, 1, b"before-cut\n");
1668 let ticket =
1669 match neutral(&state, owner, mm, counted(1, prepare(&state, mm))).await
1670 {
1671 R::PrepareNativeExec(Ok(ticket)) => ticket,
1672 response => panic!("prepare: {response:?}"),
1673 };
1674 assert_eq!(
1675 *source.written.lock().unwrap(),
1676 vec![b"before-cut\n".to_vec()]
1677 );
1678 let runtime = b"protocol-runtime".to_vec();
1679 let digest = match neutral(
1680 &state,
1681 owner,
1682 mm,
1683 counted(
1684 0,
1685 Q::SealNativeExecStats {
1686 ticket,
1687 runtime_digest: Sha256::digest(&runtime).into(),
1688 runtime,
1689 },
1690 ),
1691 )
1692 .await
1693 {
1694 R::SealNativeExecStats(Ok(digest)) => digest,
1695 response => panic!("seal: {response:?}"),
1696 };
1697 {
1700 let provision_peer: OwnedFd =
1701 std::fs::File::open("/dev/null").unwrap().into();
1702 let mut provision_registry =
1703 state.native_exec_registry().unwrap().lock().unwrap();
1704 unsafe {
1706 provision_registry
1707 .begin_provision(&provision_peer, ticket)
1708 .unwrap()
1709 .complete()
1710 }
1711 .unwrap();
1712 }
1713 assert_eq!(
1714 neutral(
1715 &state,
1716 owner,
1717 mm,
1718 counted(
1719 0,
1720 Q::IssueNativeExec {
1721 ticket,
1722 state_digest: digest
1723 }
1724 )
1725 )
1726 .await,
1727 R::IssueNativeExec(Ok(()))
1728 );
1729 assert_eq!(
1730 neutral(
1731 &state,
1732 owner,
1733 mm,
1734 counted(0, Q::CompleteNativeExec { ticket })
1735 )
1736 .await,
1737 R::CompleteNativeExec(Err(ExecControlError::Preparation))
1738 );
1739 let peer = std::fs::File::open("/dev/null").unwrap().into();
1743 let bytes = state
1744 .native_exec_registry()
1745 .unwrap()
1746 .lock()
1747 .unwrap()
1748 .redeem(
1749 &peer,
1750 ticket,
1751 crate::native_exec::native_exec_config_digest(&state.cfg).unwrap(),
1752 digest,
1753 )
1754 .unwrap();
1755 assert_eq!(<[u8; 32]>::from(Sha256::digest(&bytes)), digest);
1756 {
1757 let mut registry =
1758 state.native_exec_registry().unwrap().lock().unwrap();
1759 registry
1760 .mark_past_first_exec(DetTid::from_raw(99), mm)
1761 .unwrap();
1762 assert!(
1763 !registry
1764 .transaction(owner, ticket, mm)
1765 .unwrap()
1766 .marked_past_first_exec
1767 );
1768 assert_eq!(
1769 registry.mark_past_first_exec(owner, old),
1770 Err(ExecControlError::Transition)
1771 );
1772 assert!(
1773 !registry
1774 .transaction(owner, ticket, mm)
1775 .unwrap()
1776 .marked_past_first_exec
1777 );
1778 }
1779 let pending = state
1783 .pending_exec_states
1784 .lock()
1785 .unwrap()
1786 .remove(&owner)
1787 .unwrap();
1788 {
1789 let mut sched = state.sched.lock().unwrap();
1790 sched.reconnect_after_exec(crate::scheduler::ExecReconnect {
1791 caller: owner,
1792 new_leader: owner,
1793 detpid: owner,
1794 pre_exec_mm: old,
1795 post_exec_mm: mm,
1796 child_tid_addr: 0,
1797 reconnect_priority: None,
1798 });
1799 }
1800 state
1801 .past_first_execve
1802 .store(true, std::sync::atomic::Ordering::SeqCst);
1803 assert_eq!(
1804 neutral(
1805 &state,
1806 owner,
1807 mm,
1808 counted(0, Q::CompleteNativeExec { ticket })
1809 )
1810 .await,
1811 R::CompleteNativeExec(Err(ExecControlError::Preparation))
1812 );
1813 assert!(
1814 !state
1815 .native_exec_registry()
1816 .unwrap()
1817 .lock()
1818 .unwrap()
1819 .transaction(owner, ticket, mm)
1820 .unwrap()
1821 .marked_past_first_exec
1822 );
1823 state
1826 .pending_exec_states
1827 .lock()
1828 .unwrap()
1829 .insert(owner, pending);
1830 {
1831 let mut sched = state.sched.lock().unwrap();
1832 sched.record_thread_mm(owner, old);
1833 sched.install_test_exec_incarnation(owner, old);
1834 }
1835 state
1836 .past_first_execve
1837 .store(false, std::sync::atomic::Ordering::SeqCst);
1838 let mut mark_time = DetTime::new(&state.cfg);
1839 mark_time.add_syscall_with_cost(987654);
1840 let mark = state
1841 .receive_rpc(
1842 Tid::from_raw(owner.as_raw()),
1843 (
1844 mark_time,
1845 mm,
1846 Q::MarkPastFirstExecve(owner, None, None, None),
1847 ),
1848 )
1849 .await;
1850 let mark_response = match mark.1 {
1851 R::WithDeadline(_, response) => *response,
1852 response => response,
1853 };
1854 assert_eq!(mark_response, R::MarkPastFirstExecve(Default::default()));
1855 assert_eq!(state.sched.lock().unwrap().thread_mm(owner), Some(mm));
1856 assert!(state.pending_exec_states.lock().unwrap().is_empty());
1857 assert!(
1858 state
1859 .past_first_execve
1860 .load(std::sync::atomic::Ordering::SeqCst)
1861 );
1862 assert_ne!(mm, old);
1863 assert_eq!(
1864 neutral(
1865 &state,
1866 owner,
1867 old,
1868 counted(0, Q::CompleteNativeExec { ticket })
1869 )
1870 .await,
1871 R::CompleteNativeExec(Err(ExecControlError::Preparation))
1872 );
1873 assert_eq!(
1874 neutral(
1875 &state,
1876 owner,
1877 mm,
1878 counted(0, Q::CompleteNativeExec { ticket })
1879 )
1880 .await,
1881 R::CompleteNativeExec(Ok(()))
1882 );
1883 assert_eq!(
1884 state
1885 .native_exec_registry()
1886 .unwrap()
1887 .lock()
1888 .unwrap()
1889 .stage(ticket),
1890 Some(ExecStage::Completed)
1891 );
1892 assert_eq!(
1893 neutral(
1894 &state,
1895 owner,
1896 mm,
1897 counted(0, Q::CompleteNativeExec { ticket })
1898 )
1899 .await,
1900 R::CompleteNativeExec(Err(ExecControlError::Transition))
1901 );
1902 crate::detlog::clear_forwarded_source();
1903 });
1904 },
1905 );
1906 }
1907
1908 #[test]
1909 fn native_exec_control_completed_cut_counts_real_post_exec_record() {
1910 isolated(
1911 "native_exec_control_completed_cut_counts_real_post_exec_record",
1912 || {
1913 tokio::runtime::Builder::new_current_thread()
1914 .enable_all()
1915 .build()
1916 .unwrap()
1917 .block_on(async {
1918 let physical = unsafe { libc::getpid() };
1922 let source = Source::new();
1923 let (state, owner, _, mm) = fixture(true, Some((physical, physical)));
1924 crate::detlog::write_forwarded_for(physical, true);
1925 let ticket =
1926 match neutral(&state, owner, mm, counted(0, prepare(&state, mm))).await
1927 {
1928 R::PrepareNativeExec(Ok(ticket)) => ticket,
1929 response => panic!("prepare: {response:?}"),
1930 };
1931 let runtime = b"protocol-runtime".to_vec();
1932 let digest = match neutral(
1933 &state,
1934 owner,
1935 mm,
1936 counted(
1937 0,
1938 Q::SealNativeExecStats {
1939 ticket,
1940 runtime_digest: Sha256::digest(&runtime).into(),
1941 runtime,
1942 },
1943 ),
1944 )
1945 .await
1946 {
1947 R::SealNativeExecStats(Ok(digest)) => digest,
1948 response => panic!("seal: {response:?}"),
1949 };
1950 {
1951 let peer = std::fs::File::open("/dev/null").unwrap().into();
1952 let mut registry =
1953 state.native_exec_registry().unwrap().lock().unwrap();
1954 unsafe { registry.begin_provision(&peer, ticket).unwrap().complete() }
1956 .unwrap();
1957 }
1958 assert_eq!(
1959 neutral(
1960 &state,
1961 owner,
1962 mm,
1963 counted(
1964 0,
1965 Q::IssueNativeExec {
1966 ticket,
1967 state_digest: digest
1968 }
1969 )
1970 )
1971 .await,
1972 R::IssueNativeExec(Ok(()))
1973 );
1974 let peer = std::fs::File::open("/dev/null").unwrap().into();
1975 state
1976 .native_exec_registry()
1977 .unwrap()
1978 .lock()
1979 .unwrap()
1980 .redeem(
1981 &peer,
1982 ticket,
1983 crate::native_exec::native_exec_config_digest(&state.cfg).unwrap(),
1984 digest,
1985 )
1986 .unwrap();
1987 let mut mark_time = DetTime::new(&state.cfg);
1990 mark_time.add_syscall_with_cost(987654);
1991 let mark = state
1992 .receive_rpc(
1993 Tid::from_raw(owner.as_raw()),
1994 (
1995 mark_time,
1996 mm,
1997 Q::MarkPastFirstExecve(owner, None, None, None),
1998 ),
1999 )
2000 .await;
2001 let response = match mark.1 {
2002 R::WithDeadline(_, response) => *response,
2003 response => response,
2004 };
2005 assert_eq!(response, R::MarkPastFirstExecve(Default::default()));
2006 assert!(state.pending_exec_states.lock().unwrap().is_empty());
2007 assert_eq!(
2008 state
2009 .native_exec_registry()
2010 .unwrap()
2011 .lock()
2012 .unwrap()
2013 .stage(ticket),
2014 Some(ExecStage::Redeemed)
2015 );
2016 source.send(physical, 1, b"successor-post-exec\n");
2017 assert!(source.written.lock().unwrap().is_empty());
2018 assert_eq!(
2022 neutral(
2023 &state,
2024 owner,
2025 mm,
2026 counted(1, Q::CompleteNativeExec { ticket })
2027 )
2028 .await,
2029 R::CompleteNativeExec(Ok(()))
2030 );
2031 assert_eq!(
2032 *source.written.lock().unwrap(),
2033 vec![b"successor-post-exec\n".to_vec()]
2034 );
2035 assert_eq!(
2036 state
2037 .native_exec_registry()
2038 .unwrap()
2039 .lock()
2040 .unwrap()
2041 .stage(ticket),
2042 Some(ExecStage::Completed)
2043 );
2044 assert_eq!(
2045 neutral(
2046 &state,
2047 owner,
2048 mm,
2049 counted(0, Q::CompleteNativeExec { ticket })
2050 )
2051 .await,
2052 R::CompleteNativeExec(Err(ExecControlError::Transition))
2053 );
2054 assert_eq!(
2055 *source.written.lock().unwrap(),
2056 vec![b"successor-post-exec\n".to_vec()]
2057 );
2058 crate::detlog::clear_forwarded_source();
2059 });
2060 },
2061 );
2062 }
2063
2064 #[test]
2065 fn native_exec_control_protocol_refuses_wrong_context_late_records_and_source_change() {
2066 isolated(
2067 "native_exec_control_protocol_refuses_wrong_context_late_records_and_source_change",
2068 || {
2069 tokio::runtime::Builder::new_current_thread()
2070 .enable_all()
2071 .build()
2072 .unwrap()
2073 .block_on(async {
2074 let physical = unsafe { libc::getpid() };
2075 let source = Source::new();
2076 let (missing, owner, _, mm) = fixture(true, None);
2077 assert_eq!(
2078 neutral(&missing, owner, mm, counted(0, prepare(&missing, mm))).await,
2079 R::PrepareNativeExec(Err(ExecControlError::Identity))
2080 );
2081 let (wrong_proof, owner, _, mm) = fixture(false, None);
2082 wrong_proof
2083 .install_native_exec_registry(registry(
2084 DetTid::from_raw(99),
2085 Some((physical, physical)),
2086 ))
2087 .unwrap();
2088 assert_eq!(
2089 neutral(
2090 &wrong_proof,
2091 owner,
2092 mm,
2093 counted(0, prepare(&wrong_proof, mm))
2094 )
2095 .await,
2096 R::PrepareNativeExec(Err(ExecControlError::Identity))
2097 );
2098 let (state, owner, old, mm) = fixture(true, Some((physical, physical)));
2099 assert_eq!(
2100 neutral(&state, owner, old, counted(0, prepare(&state, mm))).await,
2101 R::PrepareNativeExec(Err(ExecControlError::Identity))
2102 );
2103 let missing_preparation_request = prepare(&state, mm);
2104 let prepared = state
2105 .pending_exec_states
2106 .lock()
2107 .unwrap()
2108 .remove(&owner)
2109 .unwrap();
2110 assert_eq!(
2111 neutral(&state, owner, mm, counted(0, missing_preparation_request))
2112 .await,
2113 R::PrepareNativeExec(Err(ExecControlError::Preparation))
2114 );
2115 state
2116 .pending_exec_states
2117 .lock()
2118 .unwrap()
2119 .insert(owner, prepared.clone());
2120 state
2121 .pending_exec_states
2122 .lock()
2123 .unwrap()
2124 .get_mut(&owner)
2125 .unwrap()
2126 .caller = DetTid::from_raw(99);
2127 assert_eq!(
2128 neutral(&state, owner, mm, counted(0, prepare(&state, mm))).await,
2129 R::PrepareNativeExec(Err(ExecControlError::Identity))
2130 );
2131 state
2132 .pending_exec_states
2133 .lock()
2134 .unwrap()
2135 .insert(owner, prepared);
2136 let child = DetTid::from_raw(42);
2137 state
2138 .sched
2139 .lock()
2140 .unwrap()
2141 .thread_tree
2142 .add_child(owner, child, true);
2143 assert_eq!(
2144 neutral(&state, owner, mm, counted(0, prepare(&state, mm))).await,
2145 R::PrepareNativeExec(Err(ExecControlError::Preparation))
2146 );
2147 assert_eq!(
2148 state
2149 .sched
2150 .lock()
2151 .unwrap()
2152 .thread_tree
2153 .unreaped_children_of(owner),
2154 vec![child],
2155 "an exited/unregistered but unreaped child still refuses"
2156 );
2157
2158 let (siblings, owner, _, mm) = fixture(true, Some((physical, physical)));
2159 {
2160 let mut sched = siblings.sched.lock().unwrap();
2161 sched.thread_tree.add_child(owner, child, false);
2162 sched.next_turns.insert(
2163 child,
2164 ThreadNextTurn {
2165 dettid: child,
2166 child_tid_addr: 0,
2167 req: Ivar::new(),
2168 resp: Ivar::new(),
2169 protocol: Default::default(),
2170 },
2171 );
2172 assert!(sched.holds_serial_grant(owner));
2173 assert!(sched.thread_tree.unreaped_children_of(owner).is_empty());
2174 }
2175 assert_eq!(
2176 neutral(&siblings, owner, mm, counted(0, prepare(&siblings, mm))).await,
2177 R::PrepareNativeExec(Err(ExecControlError::Preparation))
2178 );
2179
2180 let (failed, owner, _, mm) = fixture(true, Some((physical, physical)));
2181 failed.report_backend_failure(reverie::BackendFailure {
2182 pid: reverie::Pid::from_raw(owner.as_raw()),
2183 tid: Tid::from_raw(owner.as_raw()),
2184 phase: "native control protocol test",
2185 });
2186 assert_eq!(
2187 neutral(&failed, owner, mm, counted(0, prepare(&failed, mm))).await,
2188 R::PrepareNativeExec(Err(ExecControlError::Dead))
2189 );
2190
2191 let (state, owner, _, mm) = fixture(true, Some((physical, physical)));
2192 source.send(physical, 1, b"counted\n");
2193 let ticket =
2194 match neutral(&state, owner, mm, counted(1, prepare(&state, mm))).await
2195 {
2196 R::PrepareNativeExec(Ok(ticket)) => ticket,
2197 response => panic!("prepare: {response:?}"),
2198 };
2199 let runtime = b"protocol-runtime".to_vec();
2200 let digest = match neutral(
2201 &state,
2202 owner,
2203 mm,
2204 counted(
2205 0,
2206 Q::SealNativeExecStats {
2207 ticket,
2208 runtime_digest: Sha256::digest(&runtime).into(),
2209 runtime,
2210 },
2211 ),
2212 )
2213 .await
2214 {
2215 R::SealNativeExecStats(Ok(digest)) => digest,
2216 response => panic!("seal: {response:?}"),
2217 };
2218 source.send(physical, 1, b"after-seal\n");
2219 assert_eq!(
2221 neutral(
2222 &state,
2223 owner,
2224 mm,
2225 counted(
2226 1,
2227 Q::IssueNativeExec {
2228 ticket,
2229 state_digest: digest
2230 }
2231 )
2232 )
2233 .await,
2234 R::IssueNativeExec(Err(ExecControlError::Forwarding))
2235 );
2236 assert_eq!(
2237 state
2238 .native_exec_registry()
2239 .unwrap()
2240 .lock()
2241 .unwrap()
2242 .stage(ticket),
2243 Some(ExecStage::Prepared)
2244 );
2245 assert_eq!(
2247 neutral(
2248 &state,
2249 owner,
2250 mm,
2251 counted(1, Q::CancelNativeExec { ticket })
2252 )
2253 .await,
2254 R::CancelNativeExec(Ok(()))
2255 );
2256 assert_eq!(
2257 *source.written.lock().unwrap(),
2258 vec![b"counted\n".to_vec(), b"after-seal\n".to_vec()]
2259 );
2260 assert!(
2261 state
2262 .pending_exec_states
2263 .lock()
2264 .unwrap()
2265 .contains_key(&owner),
2266 "native cancellation never replaces ordinary CancelExec"
2267 );
2268 let _replacement = Source::new();
2269 assert_eq!(
2270 neutral(&state, owner, mm, counted(0, prepare(&state, mm))).await,
2271 R::PrepareNativeExec(Err(ExecControlError::Forwarding))
2272 );
2273 assert_eq!(
2274 state
2275 .native_exec_registry()
2276 .unwrap()
2277 .lock()
2278 .unwrap()
2279 .stage(ticket),
2280 Some(ExecStage::Cancelled)
2281 );
2282 crate::detlog::clear_forwarded_source();
2283 });
2284 },
2285 );
2286 }
2287 #[test]
2288 fn native_exec_control_protocol_refuses_zero_claim_with_actual_late_record() {
2289 isolated(
2290 "native_exec_control_protocol_refuses_zero_claim_with_actual_late_record",
2291 || {
2292 tokio::runtime::Builder::new_current_thread()
2293 .enable_all()
2294 .build()
2295 .unwrap()
2296 .block_on(async {
2297 let physical = unsafe { libc::getpid() };
2298 let source = Source::new();
2299 let (state, owner, _, mm) = fixture(true, Some((physical, physical)));
2300 let ticket =
2301 match neutral(&state, owner, mm, counted(0, prepare(&state, mm))).await
2302 {
2303 R::PrepareNativeExec(Ok(ticket)) => ticket,
2304 response => panic!("prepare: {response:?}"),
2305 };
2306 let runtime = b"protocol-runtime".to_vec();
2307 let digest = match neutral(
2308 &state,
2309 owner,
2310 mm,
2311 counted(
2312 0,
2313 Q::SealNativeExecStats {
2314 ticket,
2315 runtime_digest: Sha256::digest(&runtime).into(),
2316 runtime,
2317 },
2318 ),
2319 )
2320 .await
2321 {
2322 R::SealNativeExecStats(Ok(digest)) => digest,
2323 response => panic!("seal: {response:?}"),
2324 };
2325 source.send(physical, 1, b"actual-late-record\n");
2326 assert_eq!(
2327 neutral(
2328 &state,
2329 owner,
2330 mm,
2331 counted(
2332 0,
2333 Q::IssueNativeExec {
2334 ticket,
2335 state_digest: digest
2336 }
2337 )
2338 )
2339 .await,
2340 R::IssueNativeExec(Err(ExecControlError::Forwarding))
2341 );
2342 assert!(crate::detlog::determinism_loss().is_some());
2343 assert_eq!(
2344 state
2345 .native_exec_registry()
2346 .unwrap()
2347 .lock()
2348 .unwrap()
2349 .stage(ticket),
2350 Some(ExecStage::Prepared)
2351 );
2352 assert_eq!(
2353 neutral(
2354 &state,
2355 owner,
2356 mm,
2357 counted(1, Q::CancelNativeExec { ticket })
2358 )
2359 .await,
2360 R::CancelNativeExec(Err(ExecControlError::Forwarding)),
2361 "a persistent forwarding loss cannot be waived by cancellation"
2362 );
2363 assert_eq!(
2364 state
2365 .native_exec_registry()
2366 .unwrap()
2367 .lock()
2368 .unwrap()
2369 .stage(ticket),
2370 Some(ExecStage::Prepared)
2371 );
2372 assert!(source.written.lock().unwrap().iter().any(|record| {
2373 record.starts_with(crate::detlog::FORWARDING_LOSS_NOTICE.as_bytes())
2374 }));
2375 crate::detlog::clear_forwarded_source();
2376 assert!(
2377 crate::detlog::determinism_loss().is_some(),
2378 "source cleanup never clears the retained failure"
2379 );
2380 });
2381 },
2382 );
2383 }
2384
2385 #[test]
2386 fn native_exec_control_protocol_refuses_missing_actual_record() {
2387 isolated(
2388 "native_exec_control_protocol_refuses_missing_actual_record",
2389 || {
2390 tokio::runtime::Builder::new_current_thread()
2391 .enable_all()
2392 .build()
2393 .unwrap()
2394 .block_on(async {
2395 let physical = unsafe { libc::getpid() };
2396 let source = Source::new();
2397 let (state, owner, _, mm) = fixture(true, Some((physical, physical)));
2398 assert_eq!(
2401 neutral(&state, owner, mm, counted(1, prepare(&state, mm))).await,
2402 R::PrepareNativeExec(Err(ExecControlError::Forwarding))
2403 );
2404 assert!(
2405 state
2406 .native_exec_registry()
2407 .unwrap()
2408 .lock()
2409 .unwrap()
2410 .transitions
2411 .is_empty()
2412 );
2413 assert!(crate::detlog::determinism_loss().is_some());
2414 assert!(source.written.lock().unwrap().iter().any(|record| {
2415 record.starts_with(crate::detlog::FORWARDING_LOSS_NOTICE.as_bytes())
2416 }));
2417 assert_eq!(
2418 neutral(&state, owner, mm, counted(0, prepare(&state, mm))).await,
2419 R::PrepareNativeExec(Err(ExecControlError::Forwarding))
2420 );
2421 crate::detlog::clear_forwarded_source();
2422 assert!(crate::detlog::determinism_loss().is_some());
2423 });
2424 },
2425 );
2426 }
2427}
2428
2429#[cfg(test)]
2430mod tests {
2431 use std::os::fd::AsRawFd;
2432 use std::os::fd::FromRawFd;
2433 use std::sync::atomic::AtomicBool;
2434 use std::sync::atomic::Ordering;
2435
2436 use super::*;
2437
2438 #[derive(Debug)]
2441 struct Proof {
2442 live: AtomicBool,
2443 pidfd: OwnedFd,
2444 }
2445
2446 fn same_file(left: &OwnedFd, right: &OwnedFd) -> bool {
2447 let mut a = std::mem::MaybeUninit::<libc::stat>::uninit();
2448 let mut b = std::mem::MaybeUninit::<libc::stat>::uninit();
2449 unsafe {
2451 if libc::fstat(left.as_raw_fd(), a.as_mut_ptr()) != 0
2452 || libc::fstat(right.as_raw_fd(), b.as_mut_ptr()) != 0
2453 {
2454 return false;
2455 }
2456 let (a, b) = (a.assume_init(), b.assume_init());
2457 (a.st_dev, a.st_ino) == (b.st_dev, b.st_ino)
2458 }
2459 }
2460
2461 unsafe impl NativeExecProof for Proof {
2464 fn owner(&self) -> DetTid {
2465 DetTid::from_raw(41)
2466 }
2467 fn physical_ids(&self) -> Option<(i32, i32)> {
2468 Some((41, 41))
2470 }
2471 fn is_live(&self) -> bool {
2472 self.live.load(Ordering::SeqCst)
2473 }
2474 fn matches_peer(&self, peer: &OwnedFd) -> bool {
2475 same_file(&self.pidfd, peer)
2476 }
2477 fn qualify_target(&self, request: &PrepareNativeExec) -> bool {
2478 request.target_digest == [2; 32]
2479 && request.bootstrap_digest == [3; 32]
2480 && request.resume_mask == 0x42
2481 && request.sockets.detlog.object() == (1, 2)
2482 && request.sockets.observer.object() == (1, 3)
2483 }
2484 fn qualify_runtime(&self, bytes: &[u8]) -> bool {
2485 bytes == b"actual-runtime-model"
2486 }
2487 fn qualify_completion(&self, _: ExecTicket, _: NativeExecBinding) -> bool {
2488 true
2490 }
2491 }
2492
2493 fn fixture() -> (
2494 NativeExecRegistry,
2495 Arc<Proof>,
2496 NativeExecContext,
2497 PrepareNativeExec,
2498 ) {
2499 let raw = unsafe { libc::syscall(libc::SYS_pidfd_open, libc::getpid(), 0) };
2501 assert!(raw >= 0, "pidfd_open: {}", std::io::Error::last_os_error());
2502 let proof = Arc::new(Proof {
2503 live: AtomicBool::new(true),
2504 pidfd: unsafe { OwnedFd::from_raw_fd(raw as i32) },
2505 });
2506 let owner = proof.owner();
2507 let old_mm = MmId::initial(owner).for_exec(owner);
2508 let context = NativeExecContext {
2509 owner,
2510 old_mm,
2511 prepared_mm: old_mm.for_exec(owner),
2512 config_digest: [1; 32],
2513 };
2514 let request = PrepareNativeExec {
2515 sockets: NativeExecSocketBindings {
2516 detlog: NativeExecSocketBinding {
2517 fd: 1025,
2518 device: 1,
2519 inode: 2,
2520 },
2521 observer: NativeExecSocketBinding {
2522 fd: 1026,
2523 device: 1,
2524 inode: 3,
2525 },
2526 },
2527 cut: NativeExecNeutralBinding {
2528 dettid: owner,
2529 detpid: owner,
2530 physical_tid: proof.physical_ids().unwrap().1,
2531 old_mm,
2532 old_files: FilesId::initial(owner),
2533 prepared_mm: context.prepared_mm,
2534 config_digest: context.config_digest,
2535 },
2536 prepared_mm: context.prepared_mm,
2537 config_digest: context.config_digest,
2538 target_digest: [2; 32],
2539 bootstrap_digest: [3; 32],
2540 resume_mask: 0x42,
2541 snapshot_digest: Sha256::digest(b"actual-tool-model").into(),
2542 snapshot: b"actual-tool-model".to_vec(),
2543 };
2544 (
2545 NativeExecRegistry::new(proof.clone(), [7; 32]).unwrap(),
2546 proof,
2547 context,
2548 request,
2549 )
2550 }
2551
2552 fn seal(
2553 registry: &mut NativeExecRegistry,
2554 context: NativeExecContext,
2555 ticket: ExecTicket,
2556 ) -> [u8; 32] {
2557 let runtime = b"actual-runtime-model";
2558 registry
2559 .seal(
2560 context.owner,
2561 ticket,
2562 context.prepared_mm,
2563 Sha256::digest(runtime).into(),
2564 runtime,
2565 )
2566 .unwrap()
2567 }
2568
2569 #[test]
2570 fn native_registry_retains_exact_binding_and_completes_once() {
2571 let (mut registry, proof, context, request) = fixture();
2572 let ticket = registry.prepare(context, request.clone()).unwrap();
2573 let binding = registry
2574 .binding(context.owner, ticket, context.prepared_mm)
2575 .unwrap();
2576 assert_eq!(binding.context, context);
2577 assert_eq!(binding.sockets, request.sockets);
2578 assert_eq!(binding.cut, request.cut);
2579 assert_eq!(binding.target_digest, request.target_digest);
2580 assert_eq!(binding.bootstrap_digest, request.bootstrap_digest);
2581 assert_eq!(binding.resume_mask, request.resume_mask);
2582 assert_eq!(binding.snapshot_digest, request.snapshot_digest);
2583 assert_eq!(
2584 registry.prepare(context, request.clone()),
2585 Err(ExecControlError::Transition)
2586 );
2587 assert_eq!(
2588 registry.issue(context.owner, ticket, context.prepared_mm, [0; 32]),
2589 Err(ExecControlError::Transition)
2590 );
2591 assert_eq!(
2592 registry.redeem(&proof.pidfd, ticket, context.config_digest, [0; 32]),
2593 Err(ExecControlError::Transition)
2594 );
2595 let digest = seal(&mut registry, context, ticket);
2596 assert_eq!(
2597 registry.seal(
2598 context.owner,
2599 ticket,
2600 context.prepared_mm,
2601 Sha256::digest(b"actual-runtime-model").into(),
2602 b"actual-runtime-model"
2603 ),
2604 Err(ExecControlError::Transition)
2605 );
2606 unsafe {
2609 registry
2610 .begin_provision(&proof.pidfd, ticket)
2611 .unwrap()
2612 .complete()
2613 }
2614 .unwrap();
2615 registry
2616 .issue(context.owner, ticket, context.prepared_mm, digest)
2617 .unwrap();
2618 assert_eq!(
2619 registry.issue(context.owner, ticket, context.prepared_mm, digest),
2620 Err(ExecControlError::Transition)
2621 );
2622 let state = registry
2623 .redeem(&proof.pidfd, ticket, context.config_digest, digest)
2624 .unwrap();
2625 assert_eq!(<[u8; 32]>::from(Sha256::digest(&state)), digest);
2626 assert_eq!(
2627 split_state(&state),
2628 Ok((
2629 request.snapshot.as_slice(),
2630 b"actual-runtime-model".as_slice()
2631 ))
2632 );
2633 assert_eq!(registry.stage(ticket), Some(ExecStage::Redeemed));
2634 assert_eq!(
2635 registry.redeem(&proof.pidfd, ticket, context.config_digest, digest),
2636 Err(ExecControlError::Transition)
2637 );
2638 assert_eq!(
2639 registry.cancel(context.owner, ticket, context.prepared_mm),
2640 Err(ExecControlError::Transition)
2641 );
2642 registry
2643 .complete(context.owner, ticket, context.prepared_mm)
2644 .unwrap();
2645 assert_eq!(
2646 registry.complete(context.owner, ticket, context.prepared_mm),
2647 Err(ExecControlError::Transition)
2648 );
2649 assert_eq!(registry.stage(ticket), Some(ExecStage::Completed));
2650 assert_eq!(
2651 registry.prepare(context, request.clone()),
2652 Err(ExecControlError::Identity)
2653 );
2654 let next_context = NativeExecContext {
2655 old_mm: context.prepared_mm,
2656 prepared_mm: context.prepared_mm.for_exec(context.owner),
2657 ..context
2658 };
2659 let next_request = PrepareNativeExec {
2660 cut: NativeExecNeutralBinding {
2661 old_mm: next_context.old_mm,
2662 old_files: request.cut.old_files.for_exec(context.owner),
2663 prepared_mm: next_context.prepared_mm,
2664 ..request.cut
2665 },
2666 prepared_mm: next_context.prepared_mm,
2667 ..request
2668 };
2669 let next = registry.prepare(next_context, next_request).unwrap();
2670 assert_ne!(next, ticket);
2671 assert_eq!(registry.stage(ticket), Some(ExecStage::Completed));
2672 }
2673
2674 #[test]
2675 fn model_socket_bindings_refuse_invalid_slots_aliases_and_unheld_objects() {
2676 let (mut registry, _, context, request) = fixture();
2677 let original = request.sockets;
2678 let mut structural = Vec::new();
2679 for fd in [-1, 0, 1, 2, 198, 199, 200, 201, 202] {
2680 structural.push(NativeExecSocketBindings {
2681 detlog: NativeExecSocketBinding {
2682 fd,
2683 ..original.detlog
2684 },
2685 ..original
2686 });
2687 structural.push(NativeExecSocketBindings {
2688 observer: NativeExecSocketBinding {
2689 fd,
2690 ..original.observer
2691 },
2692 ..original
2693 });
2694 }
2695 structural.push(NativeExecSocketBindings {
2696 observer: NativeExecSocketBinding {
2697 fd: original.detlog.fd,
2698 ..original.observer
2699 },
2700 ..original
2701 });
2702 structural.push(NativeExecSocketBindings {
2703 observer: NativeExecSocketBinding {
2704 device: original.detlog.device,
2705 inode: original.detlog.inode,
2706 ..original.observer
2707 },
2708 ..original
2709 });
2710 for sockets in structural {
2711 assert_eq!(
2712 registry.prepare(
2713 context,
2714 PrepareNativeExec {
2715 sockets,
2716 ..request.clone()
2717 }
2718 ),
2719 Err(ExecControlError::Identity),
2720 "invalid socket shape {sockets:?}"
2721 );
2722 assert!(registry.transitions.is_empty());
2723 }
2724 for sockets in [
2725 NativeExecSocketBindings {
2726 detlog: NativeExecSocketBinding {
2727 device: 9,
2728 ..original.detlog
2729 },
2730 ..original
2731 },
2732 NativeExecSocketBindings {
2733 detlog: NativeExecSocketBinding {
2734 inode: 9,
2735 ..original.detlog
2736 },
2737 ..original
2738 },
2739 NativeExecSocketBindings {
2740 observer: NativeExecSocketBinding {
2741 device: 9,
2742 ..original.observer
2743 },
2744 ..original
2745 },
2746 NativeExecSocketBindings {
2747 observer: NativeExecSocketBinding {
2748 inode: 9,
2749 ..original.observer
2750 },
2751 ..original
2752 },
2753 NativeExecSocketBindings {
2754 detlog: original.observer,
2755 observer: original.detlog,
2756 },
2757 ] {
2758 assert_eq!(
2759 registry.prepare(
2760 context,
2761 PrepareNativeExec {
2762 sockets,
2763 ..request.clone()
2764 }
2765 ),
2766 Err(ExecControlError::Qualification),
2767 "unheld socket objects {sockets:?}"
2768 );
2769 assert!(registry.transitions.is_empty());
2770 }
2771 let ticket = registry.prepare(context, request).unwrap();
2772 assert_eq!(
2773 registry
2774 .binding(context.owner, ticket, context.prepared_mm)
2775 .unwrap()
2776 .sockets,
2777 original
2778 );
2779 }
2780
2781 #[test]
2782 fn model_socket_bindings_are_per_transaction_and_cancelled_mapping_cannot_provision() {
2783 let (mut registry, proof, context, request) = fixture();
2784 let first = registry.prepare(context, request.clone()).unwrap();
2785 let digest = seal(&mut registry, context, first);
2786 unsafe {
2788 registry
2789 .begin_provision(&proof.pidfd, first)
2790 .unwrap()
2791 .complete()
2792 }
2793 .unwrap();
2794 registry
2795 .issue(context.owner, first, context.prepared_mm, digest)
2796 .unwrap();
2797 registry
2798 .redeem(&proof.pidfd, first, context.config_digest, digest)
2799 .unwrap();
2800 registry
2801 .complete(context.owner, first, context.prepared_mm)
2802 .unwrap();
2803
2804 let next = NativeExecContext {
2805 old_mm: context.prepared_mm,
2806 prepared_mm: context.prepared_mm.for_exec(context.owner),
2807 ..context
2808 };
2809 let current = NativeExecSocketBindings {
2810 detlog: NativeExecSocketBinding {
2811 fd: 3025,
2812 ..request.sockets.detlog
2813 },
2814 observer: NativeExecSocketBinding {
2815 fd: 3026,
2816 ..request.sockets.observer
2817 },
2818 };
2819 let next_request = PrepareNativeExec {
2820 sockets: current,
2821 cut: NativeExecNeutralBinding {
2822 old_mm: next.old_mm,
2823 old_files: request.cut.old_files.for_exec(context.owner),
2824 prepared_mm: next.prepared_mm,
2825 ..request.cut
2826 },
2827 prepared_mm: next.prepared_mm,
2828 ..request.clone()
2829 };
2830 let cancelled = registry.prepare(next, next_request.clone()).unwrap();
2831 assert_eq!(
2832 registry
2833 .binding(next.owner, cancelled, next.prepared_mm)
2834 .unwrap()
2835 .sockets,
2836 current
2837 );
2838 registry
2839 .cancel(next.owner, cancelled, next.prepared_mm)
2840 .unwrap();
2841 assert_eq!(
2842 registry.begin_provision(&proof.pidfd, cancelled).err(),
2843 Some(ExecControlError::Transition)
2844 );
2845 assert_eq!(registry.stage(cancelled), Some(ExecStage::Cancelled));
2846
2847 let fresh = NativeExecSocketBindings {
2848 detlog: NativeExecSocketBinding {
2849 fd: 4025,
2850 ..current.detlog
2851 },
2852 observer: NativeExecSocketBinding {
2853 fd: 4026,
2854 ..current.observer
2855 },
2856 };
2857 let third = registry
2858 .prepare(
2859 next,
2860 PrepareNativeExec {
2861 sockets: fresh,
2862 ..next_request
2863 },
2864 )
2865 .unwrap();
2866 assert_ne!(first, third);
2867 assert_ne!(cancelled, third);
2868 assert_eq!(
2869 registry
2870 .binding(next.owner, third, next.prepared_mm)
2871 .unwrap()
2872 .sockets,
2873 fresh
2874 );
2875 assert_eq!(
2876 registry.transitions[&first].binding.sockets,
2877 request.sockets
2878 );
2879 assert_eq!(registry.transitions[&cancelled].binding.sockets, current);
2880 assert_eq!(registry.stage(first), Some(ExecStage::Completed));
2881 assert_eq!(
2882 registry.begin_provision(&proof.pidfd, cancelled).err(),
2883 Some(ExecControlError::Transition)
2884 );
2885 assert_eq!(registry.stage(third), Some(ExecStage::Prepared));
2886 assert_eq!(
2887 registry.begin_provision(&proof.pidfd, first).err(),
2888 Some(ExecControlError::Transition)
2889 );
2890 let _digest = seal(&mut registry, next, third);
2891 let provision = registry.begin_provision(&proof.pidfd, third).unwrap();
2892 assert_eq!(provision.binding().sockets, fresh);
2893 unsafe { provision.complete() }.unwrap();
2895 }
2896
2897 #[test]
2898 fn model_neutral_cut_rejects_owner_memory_table_physical_and_config_mismatch() {
2899 let (mut registry, _, context, request) = fixture();
2900 let other = DetTid::from_raw(42);
2901 for cut in [
2902 NativeExecNeutralBinding {
2903 dettid: other,
2904 ..request.cut
2905 },
2906 NativeExecNeutralBinding {
2907 detpid: other,
2908 ..request.cut
2909 },
2910 NativeExecNeutralBinding {
2911 physical_tid: 42,
2912 ..request.cut
2913 },
2914 NativeExecNeutralBinding {
2915 old_mm: context.prepared_mm,
2916 ..request.cut
2917 },
2918 NativeExecNeutralBinding {
2919 prepared_mm: context.old_mm,
2920 ..request.cut
2921 },
2922 NativeExecNeutralBinding {
2923 old_files: request.cut.old_files.for_exec(context.owner),
2924 ..request.cut
2925 },
2926 NativeExecNeutralBinding {
2927 config_digest: [9; 32],
2928 ..request.cut
2929 },
2930 ] {
2931 assert_eq!(
2932 registry.prepare(
2933 context,
2934 PrepareNativeExec {
2935 cut,
2936 ..request.clone()
2937 }
2938 ),
2939 Err(ExecControlError::Identity)
2940 );
2941 }
2942 assert_eq!(registry.current_files, FilesId::initial(context.owner));
2943 let first = registry.prepare(context, request.clone()).unwrap();
2944 registry
2945 .cancel(context.owner, first, context.prepared_mm)
2946 .unwrap();
2947 assert_eq!(registry.current_files, FilesId::initial(context.owner));
2948 let retry = registry.prepare(context, request).unwrap();
2949 assert_ne!(first, retry);
2950 }
2951
2952 #[test]
2953 fn native_registry_cancel_and_delivery_failure_never_reopen_a_ticket() {
2954 for issued in [false, true] {
2955 let (mut registry, proof, context, request) = fixture();
2956 let ticket = registry.prepare(context, request.clone()).unwrap();
2957 let digest = seal(&mut registry, context, ticket);
2958 if issued {
2959 unsafe {
2962 registry
2963 .begin_provision(&proof.pidfd, ticket)
2964 .unwrap()
2965 .complete()
2966 }
2967 .unwrap();
2968 registry
2969 .issue(context.owner, ticket, context.prepared_mm, digest)
2970 .unwrap();
2971 }
2972 registry
2973 .cancel(context.owner, ticket, context.prepared_mm)
2974 .unwrap();
2975 assert_eq!(registry.stage(ticket), Some(ExecStage::Cancelled));
2976 assert_eq!(
2977 registry.cancel(context.owner, ticket, context.prepared_mm),
2978 Err(ExecControlError::Transition)
2979 );
2980 assert_eq!(
2981 registry.redeem(&proof.pidfd, ticket, context.config_digest, digest),
2982 Err(ExecControlError::Transition)
2983 );
2984 assert_ne!(registry.prepare(context, request).unwrap(), ticket);
2985 }
2986 let (mut registry, proof, context, request) = fixture();
2987 let ticket = registry.prepare(context, request).unwrap();
2988 let digest = seal(&mut registry, context, ticket);
2989 unsafe {
2992 registry
2993 .begin_provision(&proof.pidfd, ticket)
2994 .unwrap()
2995 .complete()
2996 }
2997 .unwrap();
2998 registry
2999 .issue(context.owner, ticket, context.prepared_mm, digest)
3000 .unwrap();
3001 let _undelivered = registry
3002 .redeem(&proof.pidfd, ticket, context.config_digest, digest)
3003 .unwrap();
3004 registry.failed(ticket).unwrap();
3005 assert_eq!(registry.stage(ticket), Some(ExecStage::Failed));
3006 assert_eq!(
3007 registry.redeem(&proof.pidfd, ticket, context.config_digest, digest),
3008 Err(ExecControlError::Dead)
3009 );
3010 assert_eq!(
3011 registry.complete(context.owner, ticket, context.prepared_mm),
3012 Err(ExecControlError::Dead)
3013 );
3014 }
3015
3016 #[test]
3017 fn native_registry_wrong_peer_owner_mm_and_digests_cannot_consume() {
3018 let (mut registry, proof, context, request) = fixture();
3019 let ticket = registry.prepare(context, request).unwrap();
3020 let digest = seal(&mut registry, context, ticket);
3021 let stranger = DetTid::from_raw(42);
3022 assert_eq!(
3023 registry.issue(stranger, ticket, context.prepared_mm, digest),
3024 Err(ExecControlError::Identity)
3025 );
3026 assert_eq!(
3027 registry.issue(context.owner, ticket, context.old_mm, digest),
3028 Err(ExecControlError::Identity)
3029 );
3030 unsafe {
3033 registry
3034 .begin_provision(&proof.pidfd, ticket)
3035 .unwrap()
3036 .complete()
3037 }
3038 .unwrap();
3039 registry
3040 .issue(context.owner, ticket, context.prepared_mm, digest)
3041 .unwrap();
3042 let wrong_peer: OwnedFd = std::fs::File::open("/dev/null").unwrap().into();
3043 assert_eq!(
3044 registry.redeem(&wrong_peer, ticket, context.config_digest, digest),
3045 Err(ExecControlError::Identity)
3046 );
3047 assert_eq!(
3048 registry.redeem(
3049 &proof.pidfd,
3050 ExecTicket([0; 32]),
3051 context.config_digest,
3052 digest
3053 ),
3054 Err(ExecControlError::Identity)
3055 );
3056 assert_eq!(
3057 registry.redeem(&proof.pidfd, ticket, [9; 32], digest),
3058 Err(ExecControlError::Digest)
3059 );
3060 assert_eq!(
3061 registry.redeem(&proof.pidfd, ticket, context.config_digest, [9; 32]),
3062 Err(ExecControlError::Digest)
3063 );
3064 assert_eq!(registry.stage(ticket), Some(ExecStage::Issued));
3065 assert!(
3066 !registry
3067 .redeem(&proof.pidfd, ticket, context.config_digest, digest)
3068 .unwrap()
3069 .is_empty()
3070 );
3071 }
3072
3073 #[test]
3074 fn native_registry_terminal_failure_refuses_every_future_edge() {
3075 for edge in 0..4 {
3076 let (mut registry, proof, context, request) = fixture();
3077 let ticket = registry.prepare(context, request.clone()).unwrap();
3078 let digest = if edge > 0 {
3079 seal(&mut registry, context, ticket)
3080 } else {
3081 [0; 32]
3082 };
3083 if edge > 1 {
3084 unsafe {
3087 registry
3088 .begin_provision(&proof.pidfd, ticket)
3089 .unwrap()
3090 .complete()
3091 }
3092 .unwrap();
3093 registry
3094 .issue(context.owner, ticket, context.prepared_mm, digest)
3095 .unwrap();
3096 }
3097 if edge > 2 {
3098 registry
3099 .redeem(&proof.pidfd, ticket, context.config_digest, digest)
3100 .unwrap();
3101 }
3102 registry.failed(ticket).unwrap();
3103 assert_eq!(registry.stage(ticket), Some(ExecStage::Failed));
3104 assert_eq!(
3105 registry.prepare(context, request.clone()),
3106 Err(ExecControlError::Dead)
3107 );
3108 let next_context = NativeExecContext {
3109 old_mm: context.prepared_mm,
3110 prepared_mm: context.prepared_mm.for_exec(context.owner),
3111 ..context
3112 };
3113 assert_eq!(
3114 registry.prepare(
3115 next_context,
3116 PrepareNativeExec {
3117 prepared_mm: next_context.prepared_mm,
3118 ..request
3119 }
3120 ),
3121 Err(ExecControlError::Dead)
3122 );
3123 assert_eq!(
3124 registry.seal(
3125 context.owner,
3126 ticket,
3127 context.prepared_mm,
3128 Sha256::digest(b"actual-runtime-model").into(),
3129 b"actual-runtime-model"
3130 ),
3131 Err(ExecControlError::Dead)
3132 );
3133 assert_eq!(
3134 registry.issue(context.owner, ticket, context.prepared_mm, digest),
3135 Err(ExecControlError::Dead)
3136 );
3137 assert_eq!(
3138 registry.redeem(&proof.pidfd, ticket, context.config_digest, digest),
3139 Err(ExecControlError::Dead)
3140 );
3141 assert_eq!(
3142 registry.complete(context.owner, ticket, context.prepared_mm),
3143 Err(ExecControlError::Dead)
3144 );
3145 assert_eq!(
3146 registry.cancel(context.owner, ticket, context.prepared_mm),
3147 Err(ExecControlError::Dead)
3148 );
3149 assert_eq!(registry.stage(ticket), Some(ExecStage::Failed));
3150 }
3151 }
3152
3153 #[test]
3154 fn native_registry_mask_must_match_the_independently_captured_cut() {
3155 let (mut registry, _, context, mut request) = fixture();
3156 request.resume_mask ^= 1;
3157 assert_eq!(
3158 registry.prepare(context, request),
3159 Err(ExecControlError::Qualification)
3160 );
3161 assert!(registry.transitions.is_empty());
3162 assert_eq!(registry.next_ticket, 1);
3163 }
3164
3165 #[test]
3166 fn native_registry_death_at_each_live_edge_preserves_tombstones() {
3167 for edge in 0..4 {
3168 let (mut registry, proof, context, request) = fixture();
3169 let ticket = registry.prepare(context, request.clone()).unwrap();
3170 let digest = if edge > 0 {
3171 seal(&mut registry, context, ticket)
3172 } else {
3173 [0; 32]
3174 };
3175 if edge > 1 {
3176 unsafe {
3179 registry
3180 .begin_provision(&proof.pidfd, ticket)
3181 .unwrap()
3182 .complete()
3183 }
3184 .unwrap();
3185 registry
3186 .issue(context.owner, ticket, context.prepared_mm, digest)
3187 .unwrap();
3188 }
3189 if edge > 2 {
3190 registry
3191 .redeem(&proof.pidfd, ticket, context.config_digest, digest)
3192 .unwrap();
3193 }
3194 proof.live.store(false, Ordering::SeqCst);
3195 assert_eq!(
3196 registry.complete(context.owner, ticket, context.prepared_mm),
3197 Err(ExecControlError::Dead)
3198 );
3199 assert_eq!(registry.stage(ticket), Some(ExecStage::Dead));
3200 proof.live.store(true, Ordering::SeqCst);
3201 assert_eq!(
3202 registry.prepare(context, request),
3203 Err(ExecControlError::Dead)
3204 );
3205 assert_eq!(
3206 registry.redeem(&proof.pidfd, ticket, context.config_digest, digest),
3207 Err(ExecControlError::Dead)
3208 );
3209 }
3210 }
3211
3212 #[test]
3213 fn native_registry_bounds_and_qualification_leave_no_partial_preparation() {
3214 for invalid in 0..7 {
3215 let (mut registry, _, context, mut request) = fixture();
3216 match invalid {
3217 0 => request.prepared_mm = context.old_mm,
3218 1 => request.config_digest = [9; 32],
3219 2 => request.snapshot.clear(),
3220 3 => request.snapshot.resize(MAX_STATE_BYTES, 0),
3221 4 => request.snapshot_digest = [9; 32],
3222 5 => request.target_digest = [9; 32],
3223 6 => request.bootstrap_digest = [9; 32],
3224 _ => unreachable!(),
3225 }
3226 assert!(registry.prepare(context, request).is_err());
3227 assert!(registry.transitions.is_empty());
3228 assert_eq!(registry.next_ticket, 1);
3229 }
3230 let (mut registry, _, context, request) = fixture();
3231 let ticket = registry.prepare(context, request).unwrap();
3232 for runtime in [b"".as_slice(), b"unsupported-runtime".as_slice()] {
3233 assert!(
3234 registry
3235 .seal(
3236 context.owner,
3237 ticket,
3238 context.prepared_mm,
3239 Sha256::digest(runtime).into(),
3240 runtime
3241 )
3242 .is_err()
3243 );
3244 assert_eq!(registry.stage(ticket), Some(ExecStage::Prepared));
3245 assert!(registry.transitions[&ticket].state_digest.is_none());
3246 }
3247 assert_eq!(
3248 registry.seal(
3249 context.owner,
3250 ticket,
3251 context.prepared_mm,
3252 [9; 32],
3253 b"actual-runtime-model"
3254 ),
3255 Err(ExecControlError::Digest)
3256 );
3257 seal(&mut registry, context, ticket);
3258 }
3259
3260 #[test]
3261 fn native_registry_state_parser_rejects_all_truncations_and_wrong_lengths() {
3262 let (mut registry, proof, context, request) = fixture();
3263 let ticket = registry.prepare(context, request).unwrap();
3264 let digest = seal(&mut registry, context, ticket);
3265 unsafe {
3268 registry
3269 .begin_provision(&proof.pidfd, ticket)
3270 .unwrap()
3271 .complete()
3272 }
3273 .unwrap();
3274 registry
3275 .issue(context.owner, ticket, context.prepared_mm, digest)
3276 .unwrap();
3277 let bytes = registry
3278 .redeem(&proof.pidfd, ticket, context.config_digest, digest)
3279 .unwrap();
3280 for n in 0..bytes.len() {
3281 assert!(split_state(&bytes[..n]).is_err(), "length {n}");
3282 }
3283 for field in [0, 8, 10, 14] {
3284 let mut bad = bytes.clone();
3285 bad[field] ^= 1;
3286 assert!(split_state(&bad).is_err());
3287 }
3288 let mut extra = bytes.clone();
3289 extra.push(0);
3290 assert!(split_state(&extra).is_err());
3291 extra.resize(MAX_STATE_BYTES + 1, 0);
3292 assert!(split_state(&extra).is_err());
3293 assert!(split_state(&bytes).is_ok());
3294 }
3295
3296 #[test]
3299 fn native_provision_preseal_wrong_peer_and_never_provisioned_issue_refuse() {
3300 let (mut registry, proof, context, request) = fixture();
3301 let ticket = registry.prepare(context, request).unwrap();
3302 assert!(matches!(
3303 registry.begin_provision(&proof.pidfd, ticket),
3304 Err(ExecControlError::Transition)
3305 ));
3306 assert_eq!(
3307 registry.transitions[&ticket].provision,
3308 ProvisionState::Fresh
3309 );
3310 let digest = seal(&mut registry, context, ticket);
3311 let wrong: OwnedFd = std::fs::File::open("/dev/null").unwrap().into();
3312 assert!(matches!(
3313 registry.begin_provision(&wrong, ticket),
3314 Err(ExecControlError::Identity)
3315 ));
3316 assert_eq!(
3317 registry.transitions[&ticket].provision,
3318 ProvisionState::Fresh
3319 );
3320 assert_eq!(
3321 registry.issue(context.owner, ticket, context.prepared_mm, digest),
3322 Err(ExecControlError::Transition)
3323 );
3324 assert_eq!(registry.stage(ticket), Some(ExecStage::Prepared));
3325 }
3326
3327 #[test]
3328 fn native_provision_pending_failure_is_terminal_and_cannot_issue() {
3329 let (mut registry, proof, context, request) = fixture();
3330 let ticket = registry.prepare(context, request.clone()).unwrap();
3331 let digest = seal(&mut registry, context, ticket);
3332 {
3333 let lease = registry.begin_provision(&proof.pidfd, ticket).unwrap();
3334 assert_eq!(lease.binding().context, context);
3335 assert_eq!(lease.state_digest(), digest);
3336 assert_eq!(
3337 lease.registry.transitions[&ticket].provision,
3338 ProvisionState::Consumed
3339 );
3340 assert_eq!(
3341 lease
3342 .registry
3343 .issue(context.owner, ticket, context.prepared_mm, digest),
3344 Err(ExecControlError::Transition)
3345 );
3346 }
3348 assert_eq!(registry.stage(ticket), Some(ExecStage::Failed));
3349 assert!(matches!(
3350 registry.begin_provision(&proof.pidfd, ticket),
3351 Err(ExecControlError::Dead)
3352 ));
3353 assert_eq!(
3354 registry.issue(context.owner, ticket, context.prepared_mm, digest),
3355 Err(ExecControlError::Dead)
3356 );
3357 assert_eq!(
3358 registry.prepare(context, request),
3359 Err(ExecControlError::Dead)
3360 );
3361 }
3362
3363 #[test]
3364 fn native_provision_complete_once_then_issue_and_cancel_tombstone() {
3365 let (mut registry, proof, context, request) = fixture();
3366 let ticket = registry.prepare(context, request.clone()).unwrap();
3367 let digest = seal(&mut registry, context, ticket);
3368 unsafe {
3370 registry
3371 .begin_provision(&proof.pidfd, ticket)
3372 .unwrap()
3373 .complete()
3374 }
3375 .unwrap();
3376 assert_eq!(
3377 registry.transitions[&ticket].provision,
3378 ProvisionState::Delivered
3379 );
3380 assert!(matches!(
3381 registry.begin_provision(&proof.pidfd, ticket),
3382 Err(ExecControlError::Transition)
3383 ));
3384 registry
3385 .issue(context.owner, ticket, context.prepared_mm, digest)
3386 .unwrap();
3387 registry
3388 .cancel(context.owner, ticket, context.prepared_mm)
3389 .unwrap();
3390 assert!(matches!(
3391 registry.begin_provision(&proof.pidfd, ticket),
3392 Err(ExecControlError::Transition)
3393 ));
3394 assert_eq!(registry.stage(ticket), Some(ExecStage::Cancelled));
3395 let next = registry.prepare(context, request).unwrap();
3396 assert_ne!(next, ticket);
3397 registry.provision_transport_failed();
3398 assert_eq!(registry.stage(ticket), Some(ExecStage::Cancelled));
3399 assert_eq!(registry.stage(next), Some(ExecStage::Failed));
3400 }
3401
3402 #[test]
3403 fn native_provision_death_before_and_during_completion_preserves_dead() {
3404 for consumed in [false, true] {
3405 let (mut registry, proof, context, request) = fixture();
3406 let ticket = registry.prepare(context, request).unwrap();
3407 let digest = seal(&mut registry, context, ticket);
3408 if consumed {
3409 let lease = registry.begin_provision(&proof.pidfd, ticket).unwrap();
3410 proof.live.store(false, Ordering::SeqCst);
3411 assert_eq!(unsafe { lease.complete() }, Err(ExecControlError::Dead));
3413 } else {
3414 proof.live.store(false, Ordering::SeqCst);
3415 assert!(matches!(
3416 registry.begin_provision(&proof.pidfd, ticket),
3417 Err(ExecControlError::Dead)
3418 ));
3419 }
3420 assert_eq!(registry.stage(ticket), Some(ExecStage::Dead));
3421 assert_eq!(
3422 registry.issue(context.owner, ticket, context.prepared_mm, digest),
3423 Err(ExecControlError::Dead)
3424 );
3425 }
3426 }
3427 #[derive(Debug)]
3428 struct RefusingCompletion(Arc<Proof>);
3429 unsafe impl NativeExecProof for RefusingCompletion {
3432 fn owner(&self) -> DetTid {
3433 self.0.owner()
3434 }
3435 fn physical_ids(&self) -> Option<(i32, i32)> {
3436 self.0.physical_ids()
3437 }
3438 fn is_live(&self) -> bool {
3439 self.0.is_live()
3440 }
3441 fn matches_peer(&self, peer: &OwnedFd) -> bool {
3442 self.0.matches_peer(peer)
3443 }
3444 fn qualify_target(&self, request: &PrepareNativeExec) -> bool {
3445 self.0.qualify_target(request)
3446 }
3447 fn qualify_runtime(&self, bytes: &[u8]) -> bool {
3448 self.0.qualify_runtime(bytes)
3449 }
3450 }
3451 #[test]
3452 fn model_default_completion_refusal_is_terminal_without_retry() {
3453 let (_, proof, context, request) = fixture();
3454 let mut registry =
3455 NativeExecRegistry::new(Arc::new(RefusingCompletion(proof.clone())), [9; 32]).unwrap();
3456 let ticket = registry.prepare(context, request.clone()).unwrap();
3457 let digest = seal(&mut registry, context, ticket);
3458 unsafe {
3460 registry
3461 .begin_provision(&proof.pidfd, ticket)
3462 .unwrap()
3463 .complete()
3464 }
3465 .unwrap();
3466 registry
3467 .issue(context.owner, ticket, context.prepared_mm, digest)
3468 .unwrap();
3469 registry
3470 .redeem(&proof.pidfd, ticket, context.config_digest, digest)
3471 .unwrap();
3472 assert_eq!(
3473 registry.complete(context.owner, ticket, context.prepared_mm),
3474 Err(ExecControlError::Qualification)
3475 );
3476 assert_eq!(registry.stage(ticket), Some(ExecStage::Failed));
3477 assert_eq!(
3478 registry.complete(context.owner, ticket, context.prepared_mm),
3479 Err(ExecControlError::Dead)
3480 );
3481 assert_eq!(
3482 registry.prepare(context, request),
3483 Err(ExecControlError::Dead)
3484 );
3485 assert_eq!(
3486 registry.cancel(context.owner, ticket, context.prepared_mm),
3487 Err(ExecControlError::Dead)
3488 );
3489 }
3490
3491 #[test]
3492 fn model_connection_close_is_incarnation_specific_and_survives_delayed_eof() {
3493 let (mut registry, proof, context, request) = fixture();
3494 assert_eq!(
3495 registry.permits_connection_close(&proof.pidfd, None),
3496 Ok(false)
3497 );
3498 let wrong: OwnedFd = std::fs::File::open("/dev/null").unwrap().into();
3499 assert_eq!(
3500 registry.permits_connection_close(&wrong, None),
3501 Err(ExecControlError::Identity)
3502 );
3503 let first = registry.prepare(context, request.clone()).unwrap();
3504 assert_eq!(
3505 registry.permits_connection_close(&proof.pidfd, None),
3506 Ok(false)
3507 );
3508 let digest = seal(&mut registry, context, first);
3509 unsafe {
3511 registry
3512 .begin_provision(&proof.pidfd, first)
3513 .unwrap()
3514 .complete()
3515 }
3516 .unwrap();
3517 registry
3518 .issue(context.owner, first, context.prepared_mm, digest)
3519 .unwrap();
3520 assert_eq!(
3521 registry.permits_connection_close(&proof.pidfd, None),
3522 Ok(true)
3523 );
3524 registry
3525 .redeem(&proof.pidfd, first, context.config_digest, digest)
3526 .unwrap();
3527 registry
3528 .complete(context.owner, first, context.prepared_mm)
3529 .unwrap();
3530 assert_eq!(
3531 registry.permits_connection_close(&proof.pidfd, None),
3532 Ok(true)
3533 );
3534 assert_eq!(
3535 registry.permits_connection_close(&proof.pidfd, Some(first)),
3536 Ok(false)
3537 );
3538 let next_context = NativeExecContext {
3539 old_mm: context.prepared_mm,
3540 prepared_mm: context.prepared_mm.for_exec(context.owner),
3541 ..context
3542 };
3543 let next_request = PrepareNativeExec {
3544 cut: NativeExecNeutralBinding {
3545 old_mm: next_context.old_mm,
3546 old_files: request.cut.old_files.for_exec(context.owner),
3547 prepared_mm: next_context.prepared_mm,
3548 ..request.cut
3549 },
3550 prepared_mm: next_context.prepared_mm,
3551 ..request
3552 };
3553 let second = registry.prepare(next_context, next_request).unwrap();
3554 assert_eq!(
3555 registry.permits_connection_close(&proof.pidfd, Some(first)),
3556 Ok(false)
3557 );
3558 let digest = seal(&mut registry, next_context, second);
3559 unsafe {
3561 registry
3562 .begin_provision(&proof.pidfd, second)
3563 .unwrap()
3564 .complete()
3565 }
3566 .unwrap();
3567 registry
3568 .issue(context.owner, second, next_context.prepared_mm, digest)
3569 .unwrap();
3570 registry
3571 .redeem(&proof.pidfd, second, context.config_digest, digest)
3572 .unwrap();
3573 registry
3574 .complete(context.owner, second, next_context.prepared_mm)
3575 .unwrap();
3576 assert_eq!(
3577 registry.permits_connection_close(&proof.pidfd, None),
3578 Ok(true)
3579 );
3580 assert_eq!(
3581 registry.permits_connection_close(&proof.pidfd, Some(first)),
3582 Ok(true)
3583 );
3584 assert_eq!(
3585 registry.permits_connection_close(&proof.pidfd, Some(second)),
3586 Ok(false)
3587 );
3588 }
3589
3590 #[test]
3591 fn model_cancelled_issue_cannot_admit_connection_close() {
3592 let (mut registry, proof, context, request) = fixture();
3593 let ticket = registry.prepare(context, request).unwrap();
3594 let digest = seal(&mut registry, context, ticket);
3595 unsafe {
3597 registry
3598 .begin_provision(&proof.pidfd, ticket)
3599 .unwrap()
3600 .complete()
3601 }
3602 .unwrap();
3603 registry
3604 .issue(context.owner, ticket, context.prepared_mm, digest)
3605 .unwrap();
3606 registry
3607 .cancel(context.owner, ticket, context.prepared_mm)
3608 .unwrap();
3609 assert_eq!(
3610 registry.permits_connection_close(&proof.pidfd, None),
3611 Ok(false)
3612 );
3613 assert_eq!(
3614 registry.permits_connection_close(&proof.pidfd, Some(ticket)),
3615 Err(ExecControlError::Transition)
3616 );
3617 }
3618}