use crate::state::{Message, State};
use crate::types::{Action, Capability, MemAddr, PluginId, Right, Rights, SecurityLevel, Size};
use super::{HostCallPrecondition, StepError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HostFunction {
CapCall,
CapSend,
KernelAlloc,
MemFree,
CapRevoke,
Declassify,
ThreadCreate,
ThreadConfigure,
ThreadResume,
ThreadSuspend,
ThreadDestroy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResourceType {
Memory {
size: u64,
},
Capability {
payload: u64,
},
Actor {
config: u64,
},
}
impl HostFunction {
#[deprecated(note = "Use HostFunction::CapCall instead (EROS consolidation)")]
pub const CAP_INVOKE: HostFunction = HostFunction::CapCall;
#[deprecated(note = "Use HostFunction::CapSend instead (EROS consolidation)")]
pub const IPC_SEND: HostFunction = HostFunction::CapSend;
#[deprecated(note = "Use HostFunction::KernelAlloc instead (EROS consolidation)")]
pub const MEM_ALLOC: HostFunction = HostFunction::KernelAlloc;
}
impl HostFunction {
#[inline]
pub fn is_effectful(&self) -> bool {
true
}
#[inline]
pub fn is_declassify(&self) -> bool {
matches!(self, HostFunction::Declassify)
}
#[inline]
pub fn is_cap_operation(&self) -> bool {
matches!(self, HostFunction::CapCall | HostFunction::CapRevoke)
}
#[inline]
pub fn is_mem_operation(&self) -> bool {
matches!(self, HostFunction::KernelAlloc | HostFunction::MemFree)
}
pub fn execute(
&self,
state: &State,
hc: &HostCall,
result: &HostResult,
) -> Result<State, StepError> {
match self {
HostFunction::CapCall => execute_cap_call(state, hc, result),
HostFunction::CapSend => execute_cap_send(state, hc, result),
HostFunction::KernelAlloc => execute_kernel_alloc(state, hc),
HostFunction::MemFree => execute_mem_free(state, hc),
HostFunction::CapRevoke => execute_cap_revoke(state, hc),
HostFunction::Declassify => execute_declassify(state, hc),
HostFunction::ThreadCreate
| HostFunction::ThreadConfigure
| HostFunction::ThreadResume
| HostFunction::ThreadSuspend
| HostFunction::ThreadDestroy => Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NotImplemented {
operation: "thread operations",
},
)),
}
}
pub fn execute_mut(
&self,
state: &mut State,
hc: &HostCall,
result: &HostResult,
) -> Result<(), StepError> {
match self {
HostFunction::CapCall => execute_cap_call_mut(state, hc, result),
HostFunction::CapSend => execute_cap_send_mut(state, hc, result),
HostFunction::KernelAlloc => execute_kernel_alloc_mut(state, hc),
HostFunction::MemFree => execute_mem_free_mut(state, hc),
HostFunction::CapRevoke => execute_cap_revoke_mut(state, hc),
HostFunction::Declassify => execute_declassify_mut(state, hc),
HostFunction::ThreadCreate
| HostFunction::ThreadConfigure
| HostFunction::ThreadResume
| HostFunction::ThreadSuspend
| HostFunction::ThreadDestroy => Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NotImplemented {
operation: "thread operations",
},
)),
}
}
#[inline]
pub fn is_thread_operation(&self) -> bool {
matches!(
self,
HostFunction::ThreadCreate
| HostFunction::ThreadConfigure
| HostFunction::ThreadResume
| HostFunction::ThreadSuspend
| HostFunction::ThreadDestroy
)
}
}
#[derive(Debug, Clone)]
pub struct HostCall {
pub(crate) caller: PluginId,
pub(crate) function: HostFunction,
pub(crate) args: Vec<u64>,
pub(crate) reads: Vec<(MemAddr, Size)>,
pub(crate) writes: Vec<(MemAddr, Size)>,
}
impl HostCall {
pub fn new(
caller: PluginId,
function: HostFunction,
args: Vec<u64>,
reads: Vec<(MemAddr, Size)>,
writes: Vec<(MemAddr, Size)>,
) -> Self {
HostCall {
caller,
function,
args,
reads,
writes,
}
}
#[inline]
pub fn caller(&self) -> PluginId {
self.caller
}
#[inline]
pub fn function(&self) -> HostFunction {
self.function
}
#[inline]
pub fn args(&self) -> &[u64] {
&self.args
}
#[inline]
pub fn reads(&self) -> &[(MemAddr, Size)] {
&self.reads
}
#[inline]
pub fn writes(&self) -> &[(MemAddr, Size)] {
&self.writes
}
pub fn required_action(&self) -> Result<Action, StepError> {
let map_policy_err =
|_| StepError::HostCallPreconditionFailed(HostCallPrecondition::NoCapabilityId);
match self.function {
HostFunction::CapCall => {
let target: u64 =
self.args
.first()
.copied()
.ok_or(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoCapabilityId,
))?;
Action::new(
self.caller,
target.into(),
Rights::singleton(Right::Read),
"cap_call".to_string(),
)
.map_err(map_policy_err)
}
HostFunction::KernelAlloc => Action::new(
self.caller,
0,
Rights::singleton(Right::Create),
"kernel_alloc".to_string(),
)
.map_err(map_policy_err),
HostFunction::MemFree => {
let addr: u64 =
self.args
.first()
.copied()
.ok_or(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoAddress,
))?;
Action::new(
self.caller,
addr.into(),
Rights::singleton(Right::Delete),
"mem_free".to_string(),
)
.map_err(map_policy_err)
}
HostFunction::CapRevoke => {
let cap_id: u64 =
self.args
.first()
.copied()
.ok_or(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoCapabilityId,
))?;
Action::new(
self.caller,
cap_id.into(),
Rights::singleton(Right::Revoke),
"cap_revoke".to_string(),
)
.map_err(map_policy_err)
}
HostFunction::Declassify => Action::new(
self.caller,
0,
Rights::singleton(Right::Declassify),
"declassify".to_string(),
)
.map_err(map_policy_err),
HostFunction::CapSend => Action::new(
self.caller,
0,
Rights::singleton(Right::Send),
"cap_send".to_string(),
)
.map_err(map_policy_err),
_ => Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NotImplemented {
operation: "thread operations",
},
)),
}
}
pub fn check_preconditions(&self, state: &State) -> Result<(), StepError> {
let plugin = state
.get_plugin(self.caller)
.ok_or(StepError::PluginNotFound(self.caller))?;
if let Some(err) = self.find_oob_read(plugin) {
return Err(err);
}
if let Some(err) = self.find_oob_write(plugin) {
return Err(err);
}
Ok(())
}
fn find_oob_read(&self, plugin: &crate::state::PluginState) -> Option<StepError> {
self.reads.iter().find_map(|&(addr, size)| {
if plugin.memory().addr_in_bounds(addr, size) {
None
} else {
Some(StepError::HostCallPreconditionFailed(
HostCallPrecondition::ReadOutOfBounds { addr, size },
))
}
})
}
fn find_oob_write(&self, plugin: &crate::state::PluginState) -> Option<StepError> {
self.writes.iter().find_map(|&(addr, size)| {
if plugin.memory().addr_in_bounds(addr, size) {
None
} else {
Some(StepError::HostCallPreconditionFailed(
HostCallPrecondition::WriteOutOfBounds { addr, size },
))
}
})
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "host call results must be consumed"]
pub struct HostResult {
pub(crate) new_caps: Vec<Capability>,
pub(crate) new_msgs: Vec<Message>,
}
impl HostResult {
pub fn empty() -> Self {
HostResult {
new_caps: Vec::new(),
new_msgs: Vec::new(),
}
}
pub fn with_caps(caps: Vec<Capability>) -> Self {
HostResult {
new_caps: caps,
new_msgs: Vec::new(),
}
}
pub fn with_msgs(msgs: Vec<Message>) -> Self {
HostResult {
new_caps: Vec::new(),
new_msgs: msgs,
}
}
#[inline]
pub fn new_caps(&self) -> &[Capability] {
&self.new_caps
}
#[inline]
pub fn new_msgs(&self) -> &[Message] {
&self.new_msgs
}
}
fn execute_cap_call(
state: &State,
hc: &HostCall,
_result: &HostResult,
) -> Result<State, StepError> {
let plugin = state
.get_plugin(hc.caller)
.ok_or(StepError::PluginNotFound(hc.caller))?;
let has_valid_cap = plugin
.held_caps_ref()
.iter()
.any(|&id| state.kernel().revocation().is_valid(id));
if !has_valid_cap {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoValidCapability,
));
}
Ok(state.clone())
}
fn execute_cap_send(state: &State, hc: &HostCall, result: &HostResult) -> Result<State, StepError> {
if result.new_msgs.is_empty() {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoMessage,
));
}
for msg in &result.new_msgs {
if msg.src() != hc.caller {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::SourceMismatch,
));
}
let dst_actor = state
.get_actor(msg.dst())
.ok_or(StepError::ActorNotFound(msg.dst()))?;
if dst_actor.mailbox_len() >= dst_actor.capacity() {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::MailboxFull,
));
}
}
Ok(state.clone())
}
fn execute_kernel_alloc(state: &State, hc: &HostCall) -> Result<State, StepError> {
let size = hc.args.first().copied().unwrap_or(0);
let plugin = state
.get_plugin(hc.caller)
.ok_or(StepError::PluginNotFound(hc.caller))?;
if !plugin.can_alloc_memory(size) {
return Err(StepError::QuotaExceeded(
0, size,
plugin.memory_used(),
plugin.memory_quota(),
));
}
let mut new_state = state.apply_alloc(hc.caller, size);
if let Some(p) = new_state.get_plugin_mut(hc.caller) {
p.memory_used = p.memory_used.saturating_add(size);
}
Ok(new_state)
}
fn execute_mem_free(state: &State, hc: &HostCall) -> Result<State, StepError> {
let addr: MemAddr = hc
.args
.first()
.copied()
.ok_or(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoAddress,
))?;
if state.ghost().is_freed(addr) {
return Err(StepError::AddressAlreadyFreed(addr));
}
if let Some(&(cap_id, _)) = state
.kernel()
.revocation()
.iter()
.find(|(_, cap)| cap.is_valid() && cap.target() == u128::from(addr))
{
return Err(StepError::CapabilityTargetsAddress(cap_id, addr));
}
state.apply_free(addr).map_err(|e| {
StepError::HostCallPreconditionFailed(HostCallPrecondition::FreeFailed { source: e })
})
}
fn execute_cap_revoke(state: &State, hc: &HostCall) -> Result<State, StepError> {
let cap_id: crate::types::CapId =
hc.args
.first()
.copied()
.map(u128::from)
.ok_or(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoCapabilityId,
))?;
if state.kernel().revocation().get(cap_id).is_none() {
return Err(StepError::CapabilityNotFound(cap_id));
}
Ok(state.apply_cap_revoke(cap_id))
}
fn execute_declassify(state: &State, hc: &HostCall) -> Result<State, StepError> {
let new_level_val = hc.args.first().copied().unwrap_or(0);
let new_level = match new_level_val {
0 => SecurityLevel::Public,
1 => SecurityLevel::Internal,
2 => SecurityLevel::Confidential,
3 => SecurityLevel::Secret,
other => {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::InvalidSecurityLevel { value: other },
));
}
};
let plugin = state
.get_plugin(hc.caller)
.ok_or(StepError::PluginNotFound(hc.caller))?;
if new_level > plugin.level() {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::CannotClassifyUp,
));
}
let mut new_state = state.clone();
if let Some(plugin) = new_state.get_plugin_mut(hc.caller) {
plugin.level = new_level;
}
Ok(new_state)
}
fn execute_cap_call_mut(
state: &mut State,
hc: &HostCall,
_result: &HostResult,
) -> Result<(), StepError> {
let plugin = state
.get_plugin(hc.caller)
.ok_or(StepError::PluginNotFound(hc.caller))?;
let has_valid_cap = plugin
.held_caps_ref()
.iter()
.any(|&id| state.kernel().revocation().is_valid(id));
if !has_valid_cap {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoValidCapability,
));
}
Ok(())
}
fn execute_cap_send_mut(
state: &mut State,
hc: &HostCall,
result: &HostResult,
) -> Result<(), StepError> {
if result.new_msgs.is_empty() {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoMessage,
));
}
for msg in &result.new_msgs {
if msg.src() != hc.caller {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::SourceMismatch,
));
}
let dst_actor = state
.get_actor(msg.dst())
.ok_or(StepError::ActorNotFound(msg.dst()))?;
if dst_actor.mailbox_len() >= dst_actor.capacity() {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::MailboxFull,
));
}
}
Ok(())
}
fn execute_kernel_alloc_mut(state: &mut State, hc: &HostCall) -> Result<(), StepError> {
let size = hc.args.first().copied().unwrap_or(0);
let plugin = state
.get_plugin(hc.caller)
.ok_or(StepError::PluginNotFound(hc.caller))?;
if !plugin.can_alloc_memory(size) {
return Err(StepError::QuotaExceeded(
0, size,
plugin.memory_used(),
plugin.memory_quota(),
));
}
let _addr = state.apply_alloc_mut(hc.caller, size);
if let Some(p) = state.get_plugin_mut(hc.caller) {
p.memory_used = p.memory_used.saturating_add(size);
}
Ok(())
}
fn execute_mem_free_mut(state: &mut State, hc: &HostCall) -> Result<(), StepError> {
let addr: MemAddr = hc
.args
.first()
.copied()
.ok_or(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoAddress,
))?;
if state.ghost().is_freed(addr) {
return Err(StepError::AddressAlreadyFreed(addr));
}
if let Some(&(cap_id, _)) = state
.kernel()
.revocation()
.iter()
.find(|(_, cap)| cap.is_valid() && cap.target() == u128::from(addr))
{
return Err(StepError::CapabilityTargetsAddress(cap_id, addr));
}
state.apply_free_mut(addr).map_err(|e| {
StepError::HostCallPreconditionFailed(HostCallPrecondition::FreeFailed { source: e })
})
}
fn execute_cap_revoke_mut(state: &mut State, hc: &HostCall) -> Result<(), StepError> {
let cap_id: crate::types::CapId =
hc.args
.first()
.copied()
.map(u128::from)
.ok_or(StepError::HostCallPreconditionFailed(
HostCallPrecondition::NoCapabilityId,
))?;
if state.kernel().revocation().get(cap_id).is_none() {
return Err(StepError::CapabilityNotFound(cap_id));
}
state
.apply_cap_revoke_mut(cap_id)
.map_err(|_| StepError::CapabilityNotFound(cap_id))?;
Ok(())
}
fn execute_declassify_mut(state: &mut State, hc: &HostCall) -> Result<(), StepError> {
let new_level_val = hc.args.first().copied().unwrap_or(0);
let new_level = match new_level_val {
0 => SecurityLevel::Public,
1 => SecurityLevel::Internal,
2 => SecurityLevel::Confidential,
3 => SecurityLevel::Secret,
other => {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::InvalidSecurityLevel { value: other },
));
}
};
let plugin = state
.get_plugin(hc.caller)
.ok_or(StepError::PluginNotFound(hc.caller))?;
if new_level > plugin.level() {
return Err(StepError::HostCallPreconditionFailed(
HostCallPrecondition::CannotClassifyUp,
));
}
if let Some(plugin) = state.get_plugin_mut(hc.caller) {
plugin.level = new_level;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::PluginState;
#[test]
fn test_host_function_is_effectful() {
assert!(HostFunction::CapCall.is_effectful());
assert!(HostFunction::KernelAlloc.is_effectful());
assert!(HostFunction::Declassify.is_effectful());
}
#[test]
fn test_host_function_is_declassify() {
assert!(!HostFunction::CapCall.is_declassify());
assert!(!HostFunction::KernelAlloc.is_declassify());
assert!(HostFunction::Declassify.is_declassify());
}
#[test]
fn test_host_function_is_cap_operation() {
assert!(HostFunction::CapCall.is_cap_operation());
assert!(HostFunction::CapRevoke.is_cap_operation());
assert!(!HostFunction::CapSend.is_cap_operation());
assert!(!HostFunction::KernelAlloc.is_cap_operation());
}
#[test]
fn test_host_function_is_mem_operation() {
assert!(HostFunction::KernelAlloc.is_mem_operation());
assert!(HostFunction::MemFree.is_mem_operation());
assert!(!HostFunction::CapCall.is_mem_operation());
}
#[test]
fn test_host_call_check_preconditions_no_plugin() {
let state = State::empty();
let hc = HostCall::new(1, HostFunction::CapCall, vec![], vec![], vec![]);
let result = hc.check_preconditions(&state);
assert!(matches!(result, Err(StepError::PluginNotFound(1))));
}
#[test]
fn test_host_call_check_preconditions_out_of_bounds() {
let mut state = State::empty();
let _ = state.insert_plugin(1, PluginState::empty(SecurityLevel::Public, 100));
let hc = HostCall::new(1, HostFunction::CapCall, vec![], vec![(200, 50)], vec![]);
let result = hc.check_preconditions(&state);
assert!(matches!(
result,
Err(StepError::HostCallPreconditionFailed(_))
));
}
#[test]
fn test_execute_kernel_alloc() {
let mut state = State::empty();
let _ = state.insert_plugin(1, PluginState::empty(SecurityLevel::Public, 1024));
let hc = HostCall::new(1, HostFunction::KernelAlloc, vec![256], vec![], vec![]);
let result = HostResult::empty();
let new_state = HostFunction::KernelAlloc
.execute(&state, &hc, &result)
.expect("kernel_alloc should succeed");
assert!(new_state.ghost().resource_count() > state.ghost().resource_count());
}
#[test]
fn test_execute_declassify() {
let mut state = State::empty();
let _ = state.insert_plugin(1, PluginState::empty(SecurityLevel::Secret, 0));
let hc = HostCall::new(1, HostFunction::Declassify, vec![0], vec![], vec![]);
let result = HostResult::empty();
let new_state = HostFunction::Declassify
.execute(&state, &hc, &result)
.expect("declassify should succeed");
assert_eq!(new_state.plugin_level(1), Some(SecurityLevel::Public));
}
#[test]
fn test_execute_declassify_up_fails() {
let mut state = State::empty();
let _ = state.insert_plugin(1, PluginState::empty(SecurityLevel::Public, 0));
let hc = HostCall::new(1, HostFunction::Declassify, vec![3], vec![], vec![]);
let result = HostResult::empty();
let err = HostFunction::Declassify
.execute(&state, &hc, &result)
.expect_err("classify up should fail");
assert!(matches!(err, StepError::HostCallPreconditionFailed(_)));
}
#[test]
fn test_execute_declassify_invalid_level_rejected() {
let mut state = State::empty();
let _ = state.insert_plugin(1, PluginState::empty(SecurityLevel::Secret, 0));
for invalid_level in [4, 5, 100, u64::MAX] {
let hc = HostCall::new(
1,
HostFunction::Declassify,
vec![invalid_level],
vec![],
vec![],
);
let result = HostResult::empty();
let err = HostFunction::Declassify
.execute(&state, &hc, &result)
.expect_err(&format!("level {invalid_level} should be rejected"));
assert!(
matches!(
err,
StepError::HostCallPreconditionFailed(
HostCallPrecondition::InvalidSecurityLevel { .. }
)
),
"level {invalid_level}: expected InvalidSecurityLevel error, got {err:?}"
);
}
}
#[test]
fn test_execute_declassify_all_valid_levels() {
let mut state = State::empty();
let _ = state.insert_plugin(1, PluginState::empty(SecurityLevel::Secret, 0));
let expected = [
(0, SecurityLevel::Public),
(1, SecurityLevel::Internal),
(2, SecurityLevel::Confidential),
(3, SecurityLevel::Secret),
];
for (level_val, expected_level) in expected {
let hc = HostCall::new(1, HostFunction::Declassify, vec![level_val], vec![], vec![]);
let result = HostResult::empty();
let new_state = HostFunction::Declassify
.execute(&state, &hc, &result)
.unwrap_or_else(|e| panic!("level {level_val} should succeed: {e:?}"));
assert_eq!(new_state.plugin_level(1), Some(expected_level));
}
}
}