blob: 135a79ae037b1011fb38387e83df18882755c641 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Cantrip OS process management support
#![cfg_attr(not(test), no_std)]
extern crate alloc;
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use cantrip_memory_interface::ObjDescBundle;
use cantrip_os_common::camkes;
use cantrip_os_common::sel4_sys;
use cantrip_security_interface::SecurityRequestError;
use core::str;
use log::trace;
use num_enum::{FromPrimitive, IntoPrimitive};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use camkes::*;
use sel4_sys::seL4_CPtr;
mod bundle_image;
pub use bundle_image::*;
pub const PROC_CTRL_REQUEST_DATA_SIZE: usize = 128;
pub const PKG_MGMT_REQUEST_DATA_SIZE: usize = 2048;
pub type BundleIdArray = Vec<String>;
/// Bundle state tracks start/stop operations, and whether or not a Bundle has
/// exited cleanly or was killed by way of a fault.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum BundleState {
/// Bundle was never started (initial case), or it was an involuntary manual
/// termination caused by user or system control
#[default]
Stopped,
/// Bundle is currently running.
Running,
/// Voluntary bundle termination with a result code.
Exited(u8),
/// Involuntary bundle termination caused by an execution fault.
Faulted,
}
// Size of the data buffer used to pass a serialized BundleIdArray between Rust <> C.
// The data structure size is bounded by the camkes ipc buffer (120 bytes!)
// and also by it being allocated on the stack of the rpc glue code.
// So we need to balance these against being able to return all values.
pub const RAW_BUNDLE_ID_DATA_SIZE: usize = 100;
pub type RawBundleIdData = [u8; RAW_BUNDLE_ID_DATA_SIZE];
// BundleId capacity before spillover to the heap.
// TODO(sleffler): hide this; it's part of the implementation
pub const DEFAULT_BUNDLE_ID_CAPACITY: usize = 64;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Bundle {
// NB: application & ML binaries use well-known paths relative to bundle_id
// NB: ProcessManager owns loaded application's memory
// Bundle id extracted from manifest
pub app_id: String,
// Size (bytes) of loaded application
pub app_memory_size: u32,
}
impl Bundle {
pub fn new(bundle_id: &str) -> Self {
Bundle {
app_id: String::from(bundle_id),
app_memory_size: 0u32,
}
}
}
// Interface to underlying Bundle implementations. Mainly
// used to inject fakes for unit tests.
pub trait BundleImplInterface {
fn start(&mut self) -> Result<(), ProcessManagerError>;
fn stop(&mut self) -> Result<(), ProcessManagerError>;
fn suspend(&self) -> Result<(), ProcessManagerError>;
fn resume(&self) -> Result<(), ProcessManagerError>;
fn capscan(&self) -> Result<(), ProcessManagerError>;
}
#[repr(usize)]
#[derive(Debug, Default, Eq, PartialEq, FromPrimitive, IntoPrimitive)]
pub enum ProcessManagerError {
Success = 0,
BundleIdInvalid,
PackageBufferLenInvalid,
BundleNotFound,
BundleFound,
BundleRunning,
BundleNotRunning,
#[default]
UnknownError,
DeserializeError,
SerializeError,
ObjCapInvalid,
// Generic errors, mostly for unit tests.
InstallFailed,
UninstallFailed,
StartFailed,
StopFailed,
// TODO(sleffler): for use if/when ProcessManagerInterface grows
SuspendFailed,
ResumeFailed,
CapScanFailed,
}
impl From<ProcessManagerError> for Result<(), ProcessManagerError> {
fn from(err: ProcessManagerError) -> Result<(), ProcessManagerError> {
if err == ProcessManagerError::Success {
Ok(())
} else {
Err(err)
}
}
}
// Interface to underlying facilities (StorageManager, seL4); also
// used to inject fakes for unit tests.
pub trait ProcessManagerInterface {
type BundleImpl: BundleImplInterface;
fn install(&mut self, pkg_contents: &ObjDescBundle) -> Result<String, ProcessManagerError>;
fn install_app(
&mut self,
app_id: &str,
pkg_contents: &ObjDescBundle,
) -> Result<(), ProcessManagerError>;
fn uninstall(&mut self, bundle_id: &str) -> Result<(), ProcessManagerError>;
fn start(&mut self, bundle: &Bundle) -> Result<Self::BundleImpl, ProcessManagerError>;
fn stop(&mut self, bundle_impl: &mut Self::BundleImpl) -> Result<(), ProcessManagerError>;
fn capscan(&self, bundle_impl: &Self::BundleImpl) -> Result<(), ProcessManagerError>;
}
pub trait PackageManagementInterface {
fn install(&mut self, pkg_contents: &ObjDescBundle) -> Result<String, ProcessManagerError>;
fn install_app(
&mut self,
app_id: &str,
pkg_contents: &ObjDescBundle,
) -> Result<(), ProcessManagerError>;
fn uninstall(&mut self, bundle_id: &str) -> Result<(), ProcessManagerError>;
}
pub trait ProcessControlInterface {
fn start(&mut self, bundle_id: &str) -> Result<(), ProcessManagerError>;
fn stop(&mut self, bundle_id: &str) -> Result<(), ProcessManagerError>;
fn get_running_bundles(&self) -> Result<BundleIdArray, ProcessManagerError>;
fn get_bundle_state(&self, bundle_id: &str) -> Result<BundleState, ProcessManagerError>;
fn capscan(&self, bundle_id: &str) -> Result<(), ProcessManagerError>;
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PackageManagementRequest<'a> {
Install(Cow<'a, ObjDescBundle>), // Install package (returns bundle_id)
InstallApp {
// Install application
app_id: &'a str,
pkg_contents: Cow<'a, ObjDescBundle>,
},
Uninstall(&'a str), // Uninstall package
}
impl<'a> PackageManagementRequest<'a> {
fn get_container_cap(&self) -> Option<seL4_CPtr> {
match self {
PackageManagementRequest::Install(pkg_contents)
| PackageManagementRequest::InstallApp {
app_id: _,
pkg_contents,
} => Some(pkg_contents.cnode),
PackageManagementRequest::Uninstall(_) => None,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct InstallResponse {
pub bundle_id: String,
}
#[inline]
fn cantrip_pkg_mgmt_request<T: DeserializeOwned>(
request: &PackageManagementRequest,
) -> Result<T, ProcessManagerError> {
trace!(
"cantrip_pkg_mgmt_request {:?} cap {:?}",
&request,
request.get_container_cap()
);
let (request_slice, reply_slice) =
rpc_shared_buffer_mut!(pkg_mgmt).split_at_mut(PKG_MGMT_REQUEST_DATA_SIZE);
let _ =
postcard::to_slice(request, request_slice).or(Err(ProcessManagerError::SerializeError))?;
match rpc_shared_send!(pkg_mgmt, request.get_container_cap()).into() {
ProcessManagerError::Success => {
let reply =
postcard::from_bytes(reply_slice).or(Err(ProcessManagerError::DeserializeError))?;
Ok(reply)
}
err => Err(err),
}
}
#[inline]
pub fn cantrip_pkg_mgmt_install(
pkg_contents: &ObjDescBundle,
) -> Result<String, ProcessManagerError> {
Camkes::debug_assert_slot_cnode(
"cantrip_pkg_mgmt_install",
&Camkes::top_level_path(pkg_contents.cnode),
);
cantrip_pkg_mgmt_request(&PackageManagementRequest::Install(Cow::Borrowed(pkg_contents)))
.map(|reply: InstallResponse| reply.bundle_id)
}
#[inline]
pub fn cantrip_pkg_mgmt_install_app(
app_id: &str,
pkg_contents: &ObjDescBundle,
) -> Result<(), ProcessManagerError> {
Camkes::debug_assert_slot_cnode(
"cantrip_pkg_mgmt_install_app",
&Camkes::top_level_path(pkg_contents.cnode),
);
cantrip_pkg_mgmt_request(&PackageManagementRequest::InstallApp {
app_id,
pkg_contents: Cow::Borrowed(pkg_contents),
})
}
#[inline]
pub fn cantrip_pkg_mgmt_uninstall(bundle_id: &str) -> Result<(), ProcessManagerError> {
cantrip_pkg_mgmt_request(&PackageManagementRequest::Uninstall(bundle_id))
}
impl From<SecurityRequestError> for ProcessManagerError {
fn from(err: SecurityRequestError) -> ProcessManagerError {
match err {
SecurityRequestError::Success => ProcessManagerError::Success,
SecurityRequestError::BundleIdInvalid => ProcessManagerError::BundleIdInvalid,
SecurityRequestError::BundleNotFound => ProcessManagerError::BundleNotFound,
SecurityRequestError::PackageBufferLenInvalid => {
ProcessManagerError::PackageBufferLenInvalid
}
SecurityRequestError::InstallFailed => ProcessManagerError::InstallFailed,
SecurityRequestError::UninstallFailed => ProcessManagerError::UninstallFailed,
// NB: other errors "cannot happen" so just return something unique
_ => ProcessManagerError::UnknownError,
}
}
}
impl From<cstr_core::NulError> for ProcessManagerError {
fn from(_err: cstr_core::NulError) -> ProcessManagerError {
ProcessManagerError::BundleIdInvalid
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ProcessControlRequest<'a> {
Start(&'a str),
Stop(&'a str),
GetRunningBundles, // -> bundle_ids
GetBundleState(&'a str), // -> bundle_state
CapScan,
CapScanBundle(&'a str),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetRunningBundlesResponse {
pub bundle_ids: BundleIdArray,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetBundleStateResponse {
pub bundle_state: BundleState,
}
#[inline]
fn cantrip_proc_ctrl_request<T: DeserializeOwned>(
request: &ProcessControlRequest,
) -> Result<T, ProcessManagerError> {
trace!("cantrip_proc_ctrl_request {:?}", &request);
let (request_buffer, reply_slice) =
rpc_basic_buffer!().split_at_mut(PROC_CTRL_REQUEST_DATA_SIZE);
let request_slice =
postcard::to_slice(request, request_buffer).or(Err(ProcessManagerError::SerializeError))?;
// XXX not needed
// NB: guard against a received badge being treated as an
// outbound capability. This is needed because the code CAmkES
// generates for pkg_mgmt_request always enables possible xmit
// of 1 capability.
Camkes::clear_request_cap();
match rpc_basic_send!(proc_ctrl, request_slice.len()).0.into() {
ProcessManagerError::Success => {
let reply =
postcard::from_bytes(reply_slice).or(Err(ProcessManagerError::DeserializeError))?;
Ok(reply)
}
err => Err(err),
}
}
#[inline]
pub fn cantrip_proc_ctrl_get_bundle_state(
bundle_id: &str,
) -> Result<BundleState, ProcessManagerError> {
cantrip_proc_ctrl_request(&ProcessControlRequest::GetBundleState(bundle_id))
.map(|reply: GetBundleStateResponse| reply.bundle_state)
}
#[inline]
pub fn cantrip_proc_ctrl_get_running_bundles() -> Result<BundleIdArray, ProcessManagerError> {
cantrip_proc_ctrl_request(&ProcessControlRequest::GetRunningBundles)
.map(|reply: GetRunningBundlesResponse| reply.bundle_ids)
}
#[inline]
pub fn cantrip_proc_ctrl_start(bundle_id: &str) -> Result<(), ProcessManagerError> {
cantrip_proc_ctrl_request(&ProcessControlRequest::Start(bundle_id))
}
#[inline]
pub fn cantrip_proc_ctrl_stop(bundle_id: &str) -> Result<(), ProcessManagerError> {
cantrip_proc_ctrl_request(&ProcessControlRequest::Stop(bundle_id))
}
#[inline]
pub fn cantrip_proc_ctrl_capscan() -> Result<(), ProcessManagerError> {
cantrip_proc_ctrl_request(&ProcessControlRequest::CapScan)
}
#[inline]
pub fn cantrip_proc_ctrl_capscan_bundle(bundle_id: &str) -> Result<(), ProcessManagerError> {
cantrip_proc_ctrl_request(&ProcessControlRequest::CapScanBundle(bundle_id))
}
// TODO(sleffler): move out of interface?
#[cfg(test)]
mod tests {
use super::*;
use postcard;
#[test]
fn test_raw_bundle_id_data_empty() {
let bid_array = BundleIdArray::new();
// Marhshall/unmarshall empty bid_array.
let mut raw_data = [0u8; RAW_BUNDLE_ID_DATA_SIZE];
assert!(postcard::to_slice(&bid_array, &mut raw_data).is_ok());
assert_eq!(
postcard::from_bytes::<BundleIdArray>(raw_data.as_ref()).unwrap(),
bid_array
);
}
#[test]
fn test_raw_bundle_id_data_simple() {
let mut bid_array = BundleIdArray::new();
bid_array.push(String::from("zero"));
bid_array.push(String::from("one"));
bid_array.push(String::from("two"));
// Marhshall/unmarshall bid_array.
let mut raw_data = [0u8; RAW_BUNDLE_ID_DATA_SIZE];
assert!(postcard::to_slice(&bid_array, &mut raw_data).is_ok());
assert_eq!(
postcard::from_bytes::<BundleIdArray>(raw_data.as_ref()).unwrap(),
bid_array
);
}
#[test]
fn test_raw_bundle_id_data_out_of_space() {
// Marshall an array with >255 id's; serialize will fail because
// there's not enough space.
let mut bid_array = BundleIdArray::new();
for bid in 0..256 {
bid_array.push(bid.to_string());
}
let mut raw_data = [0u8; RAW_BUNDLE_ID_DATA_SIZE];
assert!(postcard::to_slice(&bid_array, &mut raw_data).is_err());
}
#[test]
fn test_raw_bundle_id_data_too_long() {
// Marshall an id with length >255; serialize will fail because
// there's not enough space.
let mut bid_array = BundleIdArray::new();
bid_array.push("0123456789".repeat(26));
let mut raw_data = [0u8; RAW_BUNDLE_ID_DATA_SIZE];
assert!(postcard::to_slice(&bid_array, &mut raw_data).is_err());
}
}