feat(apps): permission manager equips apps (manifest+consent+decisions in /system), launches via init w/ file grant; shell unprivileged (/home); install/apps/allow/revoke; revoke stops running app; app-image tool; counter app
This commit is contained in:
116
crates/libkos/src/apps.rs
Normal file
116
crates/libkos/src/apps.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
//! The permission manager's client (protocol: `types::apps`): run, install
|
||||
//! and manage apps. Text goes through a request page this process owns and
|
||||
//! lends with each call.
|
||||
|
||||
use crate::syscall::{self, Give, Received};
|
||||
use crate::window::{self, Window};
|
||||
use types::address::{ByteOffset, VirtualAddress};
|
||||
use types::apps::{self as wire, PAGE_SIZE, reply};
|
||||
use types::bounded::{CapSlot, PageCount};
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
/// What the permission manager refused with: a `types::apps::reply` code
|
||||
/// and its word (the capability bits it is about, or a manifest line).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Refused {
|
||||
pub code: usize,
|
||||
pub detail: usize,
|
||||
}
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Refused>;
|
||||
|
||||
const FAILED: Refused = Refused { code: reply::FAILED, detail: 0 };
|
||||
|
||||
pub struct Apps {
|
||||
ep: CapSlot,
|
||||
page_cap: CapSlot,
|
||||
page: Window,
|
||||
}
|
||||
|
||||
impl Apps {
|
||||
/// Talks to the permission manager behind `ep`, with a request page
|
||||
/// mapped at `va`.
|
||||
pub fn connect(ep: CapSlot, va: VirtualAddress) -> Option<Self> {
|
||||
let page_cap = syscall::create_region(PageCount::new(1)?).ok()?;
|
||||
let page = window::map(page_cap, va, PagePerms::ReadWrite).ok()?;
|
||||
Some(Self { ep, page_cap, page })
|
||||
}
|
||||
|
||||
fn call(&self, words: [usize; 4], cap: Option<Give>) -> Result<(usize, Option<CapSlot>)> {
|
||||
let r = syscall::call(self.ep, words, cap).map_err(|_| FAILED)?;
|
||||
let ([code, detail, _, _], cap) = match r {
|
||||
Received::Message { words, cap, .. } | Received::Call { words, cap, .. } => (words, cap),
|
||||
Received::Signal { .. } => return Err(FAILED),
|
||||
};
|
||||
match code {
|
||||
reply::OK => Ok((detail, cap)),
|
||||
code => Err(Refused { code, detail }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lends the request page for one call.
|
||||
fn with_page(&self, op: usize, word: usize) -> Result<(usize, Option<CapSlot>)> {
|
||||
self.call([op, word, 0, 0], Some(self.page_cap.into()))
|
||||
}
|
||||
|
||||
/// `parts`, NUL-separated and ending in an empty one, into the page.
|
||||
fn put(&self, parts: &[&str]) -> Result<()> {
|
||||
let need: usize = parts.iter().map(|p| p.len() + 1).sum::<usize>() + 1;
|
||||
if need > PAGE_SIZE || parts.iter().any(|p| p.contains('\0')) {
|
||||
return Err(FAILED);
|
||||
}
|
||||
let mut at = ByteOffset::ZERO;
|
||||
for p in parts {
|
||||
self.page.copy_from(at, p.as_bytes());
|
||||
self.page.write(at + p.len(), 0u8);
|
||||
at = at + p.len() + 1;
|
||||
}
|
||||
self.page.write(at, 0u8);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs `args[0]` with `args`, printing to `output`, working in
|
||||
/// `folder`; the running process (to wait on and kill) and the bits of
|
||||
/// what it was granted. Both caps go with Delegate kept, so the
|
||||
/// permission manager can hand them on.
|
||||
pub fn launch(&self, args: &[&str], output: Option<CapSlot>, folder: Option<CapSlot>) -> Result<(CapSlot, u32)> {
|
||||
for (op, cap) in [(wire::PASS_OUTPUT, output), (wire::PASS_FOLDER, folder)] {
|
||||
if let Some(cap) = cap {
|
||||
self.call([op, 0, 0, 0], Some(Give::keeping_delegate(cap)))?;
|
||||
}
|
||||
}
|
||||
self.put(args)?;
|
||||
match self.with_page(wire::LAUNCH, 0)? {
|
||||
(granted, Some(process)) => Ok((process, granted as u32)),
|
||||
(_, None) => Err(FAILED),
|
||||
}
|
||||
}
|
||||
|
||||
/// Installs the app in `source` (a read-only grant to its folder),
|
||||
/// asking the owner; the bits of what it was granted.
|
||||
pub fn install(&self, source: CapSlot) -> Result<u32> {
|
||||
self.call([wire::INSTALL, 0, 0, 0], Some(source.into())).map(|(bits, _)| bits as u32)
|
||||
}
|
||||
|
||||
/// One line per installed app, into `out`; how much.
|
||||
pub fn list(&self, out: &mut [u8]) -> Result<usize> {
|
||||
let (len, _) = self.with_page(wire::LIST, 0)?;
|
||||
let len = len.min(out.len()).min(PAGE_SIZE);
|
||||
self.page.copy_to(ByteOffset::ZERO, &mut out[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
/// Takes the capability with catalog index `capability` away from app
|
||||
/// `id` (stopping it if it runs with it).
|
||||
pub fn revoke(&self, id: &str, capability: usize) -> Result<()> {
|
||||
self.put(&[id])?;
|
||||
self.with_page(wire::REVOKE, capability).map(|_| ())
|
||||
}
|
||||
|
||||
/// Gives app `id` the capability with catalog index `capability`, which
|
||||
/// it asked for.
|
||||
pub fn allow(&self, id: &str, capability: usize) -> Result<()> {
|
||||
self.put(&[id])?;
|
||||
self.with_page(wire::ALLOW, capability).map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,8 @@ impl DirEntry {
|
||||
pub struct Storage {
|
||||
ep: CapSlot,
|
||||
buf: &'static mut [u8],
|
||||
/// What maps `buf`; dropping it unmaps it.
|
||||
region: CapSlot,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
@@ -66,7 +68,12 @@ impl Storage {
|
||||
};
|
||||
let mapped = crate::window::map_buffer(region, va).map_err(|_| FsError::Failed)?;
|
||||
let buf = mapped.get_mut(..size).ok_or(FsError::Failed)?;
|
||||
Ok(Self { ep, buf })
|
||||
Ok(Self { ep, buf, region })
|
||||
}
|
||||
|
||||
/// Unmaps the buffer, freeing its address for another connection.
|
||||
pub fn detach(self) {
|
||||
syscall::drop_cap(self.region);
|
||||
}
|
||||
|
||||
fn call(&self, words: [usize; 4]) -> Result<[usize; 4]> {
|
||||
|
||||
@@ -6,6 +6,9 @@ use core::fmt;
|
||||
struct Writer {
|
||||
buf: [u8; BATCH],
|
||||
len: usize,
|
||||
/// Where it goes: an endpoint speaking the log protocol, else where
|
||||
/// `print!` goes.
|
||||
to: Option<types::bounded::CapSlot>,
|
||||
}
|
||||
|
||||
/// A few whole text messages' worth.
|
||||
@@ -13,7 +16,10 @@ const BATCH: usize = 5 * types::log::TEXT_BYTES;
|
||||
|
||||
impl Writer {
|
||||
fn flush(&mut self) {
|
||||
write_bytes(&self.buf[..self.len]);
|
||||
match self.to {
|
||||
Some(ep) => write_to(ep, &self.buf[..self.len]),
|
||||
None => write_bytes(&self.buf[..self.len]),
|
||||
}
|
||||
self.len = 0;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +37,22 @@ impl fmt::Write for Writer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Bytes to `ep`, an endpoint speaking the log protocol (a terminal, the
|
||||
/// log server), whatever this process's own log is.
|
||||
pub fn write_to(ep: types::bounded::CapSlot, bytes: &[u8]) {
|
||||
for chunk in bytes.chunks(types::log::TEXT_BYTES) {
|
||||
let _ = crate::syscall::call(ep, types::log::text_words(chunk), None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats to `ep` (see `write_to`).
|
||||
pub fn print_to(ep: types::bounded::CapSlot, args: fmt::Arguments) {
|
||||
use fmt::Write;
|
||||
let mut w = Writer { buf: [0; BATCH], len: 0, to: Some(ep) };
|
||||
w.write_fmt(args).ok();
|
||||
w.flush();
|
||||
}
|
||||
|
||||
/// Bytes as they are to where `print!` goes: the log endpoint (the log
|
||||
/// server, or a terminal), else the console; nowhere without either.
|
||||
pub fn write_bytes(bytes: &[u8]) {
|
||||
@@ -48,7 +70,7 @@ pub fn write_bytes(bytes: &[u8]) {
|
||||
|
||||
pub fn _print(args: fmt::Arguments) {
|
||||
use fmt::Write;
|
||||
let mut w = Writer { buf: [0; BATCH], len: 0 };
|
||||
let mut w = Writer { buf: [0; BATCH], len: 0, to: None };
|
||||
w.write_fmt(args).ok();
|
||||
w.flush();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
extern crate alloc;
|
||||
|
||||
pub mod alloc_impl;
|
||||
pub mod apps;
|
||||
pub mod device;
|
||||
mod arch;
|
||||
pub mod display;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Running a program through init (protocol: `types::spawn`). Init loads it
|
||||
//! Running a program through init (protocol: `types::spawn`). The caller
|
||||
//! hands init a read-only storage grant to the program file; init loads it
|
||||
//! and gives it a log endpoint; the caller hands it any other capabilities
|
||||
//! (`delegate` to the process) and starts it.
|
||||
|
||||
@@ -25,9 +26,11 @@ pub struct Spawned {
|
||||
log: Option<CapSlot>,
|
||||
}
|
||||
|
||||
pub fn spawn(init: CapSlot, name: &[u8], limits: Limits) -> Result<Spawned, SpawnError> {
|
||||
let words = request(name, limits).ok_or(SpawnError::Failed)?;
|
||||
let reply = syscall::call(init, words, None).map_err(|_| SpawnError::Failed)?;
|
||||
/// Loads the program `binary` (a read-only storage grant to its file) into
|
||||
/// a new process, not started.
|
||||
pub fn spawn(init: CapSlot, binary: CapSlot, limits: Limits) -> Result<Spawned, SpawnError> {
|
||||
let words = request(limits).ok_or(SpawnError::Failed)?;
|
||||
let reply = syscall::call(init, words, Some(binary.into())).map_err(|_| SpawnError::Failed)?;
|
||||
let [tag, entry, log, _] = reply.words();
|
||||
let cap = match reply {
|
||||
syscall::Received::Message { cap, .. } | syscall::Received::Call { cap, .. } => cap,
|
||||
|
||||
@@ -38,6 +38,12 @@ pub fn storage() -> Option<CapSlot> {
|
||||
StartArg::decode(arg()).storage
|
||||
}
|
||||
|
||||
/// The app's own data folder (a storage grant, read-write), if it was
|
||||
/// granted `storage`.
|
||||
pub fn data() -> Option<CapSlot> {
|
||||
StartArg::decode(arg()).data
|
||||
}
|
||||
|
||||
/// The display server endpoint this process was given, if any.
|
||||
pub fn display() -> Option<CapSlot> {
|
||||
StartArg::decode(arg()).display
|
||||
|
||||
Reference in New Issue
Block a user