1mod authorization;
8mod host_call;
9mod kernel_op;
10mod plugin_internal;
11
12pub use authorization::{AuthorizationError, Authorized};
13pub use host_call::{HostCall, HostFunction, HostResult, ResourceType};
14pub use kernel_op::KernelOp;
15pub use plugin_internal::PluginInternal;
16
17use crate::state::{State, StateError};
18use crate::types::{ActorId, CapId, MemAddr, PluginId, SecurityLevel, Size, WorkflowId};
19
20#[derive(Debug)]
22pub enum PluginPrecondition {
23 Blocked {
25 pid: PluginId,
27 blocked_on: Option<ActorId>,
29 },
30 ReadOutOfBounds {
32 addr: MemAddr,
34 size: Size,
36 bounds: Size,
38 },
39 WriteOutOfBounds {
41 addr: MemAddr,
43 size: Size,
45 bounds: Size,
47 },
48 MailboxEmpty,
50}
51
52impl std::fmt::Display for PluginPrecondition {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 PluginPrecondition::Blocked { pid, blocked_on } => {
56 write!(f, "Plugin {pid} is blocked on {blocked_on:?}")
57 }
58 PluginPrecondition::ReadOutOfBounds { addr, size, bounds } => {
59 write!(
60 f,
61 "Read region {addr}+{size} out of bounds (memory size {bounds})"
62 )
63 }
64 PluginPrecondition::WriteOutOfBounds { addr, size, bounds } => {
65 write!(
66 f,
67 "Write region {addr}+{size} out of bounds (memory size {bounds})"
68 )
69 }
70 PluginPrecondition::MailboxEmpty => {
71 write!(f, "Cannot consume mailbox: mailbox is empty")
72 }
73 }
74 }
75}
76
77#[derive(Debug)]
79pub enum HostCallPrecondition {
80 ReadOutOfBounds {
82 addr: MemAddr,
84 size: Size,
86 },
87 WriteOutOfBounds {
89 addr: MemAddr,
91 size: Size,
93 },
94 NoValidCapability,
96 NoMessage,
98 SourceMismatch,
100 MailboxFull,
102 InvalidSecurityLevel {
104 value: u64,
106 },
107 CannotClassifyUp,
109 NoAddress,
111 NoCapabilityId,
113 FreeFailed {
115 source: StateError,
117 },
118 NotImplemented {
120 operation: &'static str,
122 },
123}
124
125impl std::fmt::Display for HostCallPrecondition {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 match self {
128 HostCallPrecondition::ReadOutOfBounds { addr, size } => {
129 write!(f, "Read region {addr}+{size} out of bounds")
130 }
131 HostCallPrecondition::WriteOutOfBounds { addr, size } => {
132 write!(f, "Write region {addr}+{size} out of bounds")
133 }
134 HostCallPrecondition::NoValidCapability => write!(f, "No valid capability held"),
135 HostCallPrecondition::NoMessage => write!(f, "No message to send"),
136 HostCallPrecondition::SourceMismatch => {
137 write!(f, "Message source doesn't match caller")
138 }
139 HostCallPrecondition::MailboxFull => write!(f, "Destination mailbox full"),
140 HostCallPrecondition::InvalidSecurityLevel { value } => {
141 write!(f, "Invalid security level: {value}")
142 }
143 HostCallPrecondition::CannotClassifyUp => write!(f, "Cannot classify up"),
144 HostCallPrecondition::NoAddress => write!(f, "No address to free"),
145 HostCallPrecondition::NoCapabilityId => write!(f, "No capability ID to revoke"),
146 HostCallPrecondition::FreeFailed { source } => {
147 write!(f, "Free failed: {source:?}")
148 }
149 HostCallPrecondition::NotImplemented { operation } => {
150 write!(
151 f,
152 "{operation} requires Phase 3 State extension (threads/scheduler)"
153 )
154 }
155 }
156 }
157}
158
159#[derive(Debug)]
161pub enum KernelOpError {
162 NoPendingMessages {
164 dst: ActorId,
166 },
167 MailboxAtCapacity {
169 dst: ActorId,
171 },
172 WorkflowNotFound {
174 wid: WorkflowId,
176 },
177 WorkflowNotRunning {
179 wid: WorkflowId,
181 },
182 NotImplemented {
184 operation: &'static str,
186 },
187 CounterOverflow(String),
189}
190
191impl std::fmt::Display for KernelOpError {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 match self {
194 KernelOpError::NoPendingMessages { dst } => {
195 write!(f, "Actor {dst} has no pending messages")
196 }
197 KernelOpError::MailboxAtCapacity { dst } => {
198 write!(f, "Actor {dst} mailbox at capacity")
199 }
200 KernelOpError::WorkflowNotFound { wid } => write!(f, "Workflow {wid} not found"),
201 KernelOpError::WorkflowNotRunning { wid } => {
202 write!(f, "Workflow {wid} is not running")
203 }
204 KernelOpError::NotImplemented { operation } => {
205 write!(
206 f,
207 "{operation} requires Phase 3 State extension (threads/scheduler)"
208 )
209 }
210 KernelOpError::CounterOverflow(msg) => {
211 write!(f, "counter overflow: {msg}")
212 }
213 }
214 }
215}
216
217#[derive(Debug)]
219pub enum InvalidTransitionReason {
220 IncompatibleStates {
222 expected: &'static str,
224 found: &'static str,
226 },
227}
228
229impl std::fmt::Display for InvalidTransitionReason {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 match self {
232 InvalidTransitionReason::IncompatibleStates { expected, found } => {
233 write!(f, "expected {expected}, found {found}")
234 }
235 }
236 }
237}
238
239#[derive(Debug)]
241pub enum StepError {
242 PluginNotFound(PluginId),
244 ActorNotFound(PluginId),
246 AddressAlreadyFreed(MemAddr),
248 CapabilityTargetsAddress(CapId, MemAddr),
250 CapabilityNotFound(CapId),
252 AuthorizationFailed(AuthorizationError),
254 PluginPreconditionFailed(PluginPrecondition),
256 HostCallPreconditionFailed(HostCallPrecondition),
258 KernelOpFailed(KernelOpError),
260 InvalidTransition(InvalidTransitionReason),
262 StateError(StateError),
264 QuotaExceeded(u8, u64, u64, u64),
266}
267
268impl std::fmt::Display for StepError {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 match self {
271 StepError::PluginNotFound(id) => write!(f, "plugin {id} not found"),
272 StepError::ActorNotFound(id) => write!(f, "actor {id} not found"),
273 StepError::AddressAlreadyFreed(addr) => write!(f, "address {addr} already freed"),
274 StepError::CapabilityTargetsAddress(cap, addr) => {
275 write!(f, "capability {cap} targets address {addr}")
276 }
277 StepError::CapabilityNotFound(id) => write!(f, "capability {id} not found"),
278 StepError::AuthorizationFailed(e) => write!(f, "authorization failed: {e}"),
279 StepError::PluginPreconditionFailed(reason) => {
280 write!(f, "plugin precondition failed: {reason}")
281 }
282 StepError::HostCallPreconditionFailed(reason) => {
283 write!(f, "host call precondition failed: {reason}")
284 }
285 StepError::KernelOpFailed(reason) => write!(f, "kernel operation failed: {reason}"),
286 StepError::InvalidTransition(reason) => write!(f, "invalid transition: {reason}"),
287 StepError::StateError(e) => write!(f, "state error: {e}"),
288 StepError::QuotaExceeded(kind, requested, current, quota) => {
289 let kind_name = match kind {
290 0 => "memory",
291 1 => "capability",
292 2 => "ipc_queue",
293 _ => "unknown",
294 };
295 write!(
296 f,
297 "{kind_name} quota exceeded: requested {requested}, current {current}, quota {quota}"
298 )
299 }
300 }
301 }
302}
303
304impl std::error::Error for StepError {}
305
306impl From<AuthorizationError> for StepError {
307 fn from(e: AuthorizationError) -> Self {
308 StepError::AuthorizationFailed(e)
309 }
310}
311
312impl From<StateError> for StepError {
313 fn from(e: StateError) -> Self {
314 StepError::StateError(e)
315 }
316}
317
318#[derive(Debug, Clone)]
320#[allow(clippy::large_enum_variant)]
321#[must_use = "steps must be executed to apply state transitions"]
322pub enum Step {
323 PluginInternal {
325 pid: PluginId,
327 pi: PluginInternal,
329 },
330
331 HostCall {
333 hc: HostCall,
335 auth: Authorized,
337 result: HostResult,
339 },
340
341 KernelInternal {
343 op: KernelOp,
345 },
346
347 MemFree {
349 caller: PluginId,
351 addr: MemAddr,
353 },
354
355 CapRevoke {
357 caller: PluginId,
359 cap_id: CapId,
361 },
362}
363
364impl Step {
365 pub fn host_call_atomic(
372 state: &State,
373 hc: HostCall,
374 cap_id: CapId,
375 ctx: crate::types::PolicyContext,
376 result: HostResult,
377 ) -> Result<Self, StepError> {
378 let cap = state
379 .get_cap(cap_id)
380 .cloned()
381 .ok_or(StepError::CapabilityNotFound(cap_id))?;
382
383 let action = hc.required_action()?;
384 if cap.holder() != hc.caller() {
385 return Err(StepError::AuthorizationFailed(
386 AuthorizationError::HolderMismatch {
387 expected: hc.caller(),
388 actual: cap.holder(),
389 },
390 ));
391 }
392
393 let auth = Authorized::validate_atomic(state, cap, action, ctx)?;
394 Ok(Step::HostCall { hc, auth, result })
395 }
396
397 pub fn is_effectful(&self) -> bool {
399 matches!(self, Step::HostCall { .. })
400 }
401
402 pub fn subject(&self) -> Option<PluginId> {
404 match self {
405 Step::PluginInternal { pid, .. } => Some(*pid),
406 Step::HostCall { hc, .. } => Some(hc.caller),
407 Step::KernelInternal { .. } => None,
408 Step::MemFree { caller, .. } | Step::CapRevoke { caller, .. } => Some(*caller),
409 }
410 }
411
412 pub fn level(&self, state: &State) -> SecurityLevel {
414 match self.subject() {
415 Some(pid) => state.plugin_level(pid).unwrap_or(SecurityLevel::Public),
416 None => SecurityLevel::Secret,
417 }
418 }
419
420 pub fn is_declassify(&self) -> bool {
422 matches!(
423 self,
424 Step::HostCall {
425 hc: HostCall {
426 function: HostFunction::Declassify,
427 ..
428 },
429 ..
430 }
431 )
432 }
433
434 pub fn execute(&self, state: &State) -> Result<State, StepError> {
436 match self {
437 Step::PluginInternal { pid, pi } => execute_plugin_internal(state, *pid, pi),
438 Step::HostCall { hc, auth, result } => execute_host_call(state, hc, auth, result),
439 Step::KernelInternal { op } => execute_kernel_internal(state, op),
440 Step::MemFree { caller: _, addr } => execute_mem_free(state, *addr),
441 Step::CapRevoke { caller: _, cap_id } => execute_cap_revoke(state, *cap_id),
442 }
443 }
444
445 pub fn execute_mut(&self, state: &mut State) -> Result<(), StepError> {
450 match self {
451 Step::PluginInternal { pid, pi } => execute_plugin_internal_mut(state, *pid, pi),
452 Step::HostCall { hc, auth, result } => execute_host_call_mut(state, hc, auth, result),
453 Step::KernelInternal { op } => execute_kernel_internal_mut(state, op),
454 Step::MemFree { caller: _, addr } => execute_mem_free_mut(state, *addr),
455 Step::CapRevoke { caller: _, cap_id } => execute_cap_revoke_mut(state, *cap_id),
456 }
457 }
458}
459
460fn execute_plugin_internal(
461 state: &State,
462 pid: PluginId,
463 pi: &PluginInternal,
464) -> Result<State, StepError> {
465 let plugin = state
466 .get_plugin(pid)
467 .ok_or(StepError::PluginNotFound(pid))?;
468
469 pi.check_preconditions(state, pid)?;
470
471 let mut new_state = state.clone();
472
473 if let Some(_new_plugin) = new_state.get_plugin_mut(pid) {
474 for &(addr, _size) in &pi.writes {
475 if !plugin.memory().addr_in_bounds(addr, 1) {
476 return Err(StepError::PluginPreconditionFailed(
477 PluginPrecondition::WriteOutOfBounds {
478 addr,
479 size: 1,
480 bounds: plugin.memory_bounds(),
481 },
482 ));
483 }
484 }
485
486 if pi.consume_mailbox {
487 if let Some(actor) = new_state.get_actor_mut(pid) {
488 let _ = actor.consume_mut();
489 }
490 }
491 }
492
493 Ok(new_state)
494}
495
496fn execute_host_call(
497 state: &State,
498 hc: &HostCall,
499 auth: &Authorized,
500 result: &HostResult,
501) -> Result<State, StepError> {
502 if auth.action().subject() != hc.caller() {
504 return Err(StepError::AuthorizationFailed(
505 AuthorizationError::HolderMismatch {
506 expected: auth.action().subject(),
507 actual: hc.caller(),
508 },
509 ));
510 }
511
512 let derived = hc.required_action()?;
514 if &derived != auth.action() {
515 return Err(StepError::InvalidTransition(
516 InvalidTransitionReason::IncompatibleStates {
517 expected: "authorized host call",
518 found: "mismatched host call",
519 },
520 ));
521 }
522
523 auth.validate(state)?;
524 hc.check_preconditions(state)?;
525 hc.function.execute(state, hc, result)
526}
527
528fn execute_kernel_internal(state: &State, op: &KernelOp) -> Result<State, StepError> {
529 op.execute(state)
530}
531
532fn execute_mem_free(state: &State, addr: MemAddr) -> Result<State, StepError> {
533 if state.ghost().is_freed(addr) {
534 return Err(StepError::AddressAlreadyFreed(addr));
535 }
536
537 if let Some(&(cap_id, _)) = state
539 .kernel()
540 .revocation()
541 .iter()
542 .find(|(_, cap)| cap.is_valid() && cap.target() == u128::from(addr))
543 {
544 return Err(StepError::CapabilityTargetsAddress(cap_id, addr));
545 }
546
547 state.apply_free(addr).map_err(StepError::StateError)
548}
549
550fn execute_cap_revoke(state: &State, cap_id: CapId) -> Result<State, StepError> {
551 if state.kernel().revocation().get(cap_id).is_none() {
552 return Err(StepError::CapabilityNotFound(cap_id));
553 }
554
555 Ok(state.apply_cap_revoke(cap_id))
556}
557
558fn execute_plugin_internal_mut(
564 state: &mut State,
565 pid: PluginId,
566 pi: &PluginInternal,
567) -> Result<(), StepError> {
568 let plugin = state
569 .get_plugin(pid)
570 .ok_or(StepError::PluginNotFound(pid))?;
571
572 pi.check_preconditions(state, pid)?;
573
574 for &(addr, _size) in &pi.writes {
576 if !plugin.memory().addr_in_bounds(addr, 1) {
577 return Err(StepError::PluginPreconditionFailed(
578 PluginPrecondition::WriteOutOfBounds {
579 addr,
580 size: 1,
581 bounds: plugin.memory_bounds(),
582 },
583 ));
584 }
585 }
586
587 if pi.consume_mailbox {
588 if let Some(actor) = state.get_actor_mut(pid) {
589 let _ = actor.consume_mut();
590 }
591 }
592
593 Ok(())
594}
595
596fn execute_host_call_mut(
597 state: &mut State,
598 hc: &HostCall,
599 auth: &Authorized,
600 result: &HostResult,
601) -> Result<(), StepError> {
602 if auth.action().subject() != hc.caller() {
604 return Err(StepError::AuthorizationFailed(
605 AuthorizationError::HolderMismatch {
606 expected: auth.action().subject(),
607 actual: hc.caller(),
608 },
609 ));
610 }
611
612 let derived = hc.required_action()?;
614 if &derived != auth.action() {
615 return Err(StepError::InvalidTransition(
616 InvalidTransitionReason::IncompatibleStates {
617 expected: "authorized host call",
618 found: "mismatched host call",
619 },
620 ));
621 }
622
623 auth.validate(state)?;
624 hc.check_preconditions(state)?;
625 hc.function.execute_mut(state, hc, result)
626}
627
628fn execute_kernel_internal_mut(state: &mut State, op: &KernelOp) -> Result<(), StepError> {
629 op.execute_mut(state)
630}
631
632fn execute_mem_free_mut(state: &mut State, addr: MemAddr) -> Result<(), StepError> {
633 if state.ghost().is_freed(addr) {
634 return Err(StepError::AddressAlreadyFreed(addr));
635 }
636
637 if let Some(&(cap_id, _)) = state
639 .kernel()
640 .revocation()
641 .iter()
642 .find(|(_, cap)| cap.is_valid() && cap.target() == u128::from(addr))
643 {
644 return Err(StepError::CapabilityTargetsAddress(cap_id, addr));
645 }
646
647 state.apply_free_mut(addr).map_err(StepError::StateError)
648}
649
650fn execute_cap_revoke_mut(state: &mut State, cap_id: CapId) -> Result<(), StepError> {
651 if state.kernel().revocation().get(cap_id).is_none() {
652 return Err(StepError::CapabilityNotFound(cap_id));
653 }
654
655 state
656 .apply_cap_revoke_mut(cap_id)
657 .map_err(|_| StepError::CapabilityNotFound(cap_id))
658}
659
660pub fn reachable(start: &State, end: &State, steps: &[Step]) -> Result<bool, StepError> {
662 let mut current = start.clone();
663 for step in steps {
664 current = step.execute(¤t)?;
665 }
666 Ok(current.time() == end.time())
667}