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:
6
crates/apps/cat/manifest.toml
Normal file
6
crates/apps/cat/manifest.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[app]
|
||||
id = "kos.cat"
|
||||
name = "cat"
|
||||
version = "1.0.0"
|
||||
binary = "cat"
|
||||
author = "K-OS"
|
||||
14
crates/apps/counter/Cargo.toml
Normal file
14
crates/apps/counter/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "counter"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "counter"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
|
||||
libkos = { workspace = true }
|
||||
10
crates/apps/counter/manifest.toml
Normal file
10
crates/apps/counter/manifest.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
# Remembers how often it ran, if the owner lets it keep a file.
|
||||
[app]
|
||||
id = "kos.counter"
|
||||
name = "Counter"
|
||||
version = "1.0.0"
|
||||
binary = "counter"
|
||||
author = "K-OS"
|
||||
|
||||
[capabilities]
|
||||
optional = ["storage"]
|
||||
59
crates/apps/counter/src/main.rs
Normal file
59
crates/apps/counter/src/main.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
//! Counts its runs in its own data folder: the `storage` capability, which
|
||||
//! it can do without (then it remembers nothing).
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use libkos::fs::Storage;
|
||||
use libkos::println;
|
||||
use types::address::VirtualAddress;
|
||||
|
||||
const DATA_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x0180_0000);
|
||||
const COUNT: &[u8] = b"count";
|
||||
|
||||
/// The number in the file, 0 if there is none yet.
|
||||
fn read_count(fs: &mut Storage) -> u64 {
|
||||
let Ok(h) = fs.open(COUNT) else { return 0 };
|
||||
let mut buf = [0u8; 20];
|
||||
let n = fs.read(h, &mut buf).unwrap_or(0);
|
||||
let _ = fs.close(h);
|
||||
core::str::from_utf8(&buf[..n]).ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0)
|
||||
}
|
||||
|
||||
fn write_count(fs: &mut Storage, count: u64) -> bool {
|
||||
let mut digits = [0u8; 20];
|
||||
let mut n = 0;
|
||||
let mut v = count;
|
||||
loop {
|
||||
digits[n] = b'0' + (v % 10) as u8;
|
||||
n += 1;
|
||||
v /= 10;
|
||||
if v == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
digits[..n].reverse();
|
||||
let _ = fs.delete(COUNT);
|
||||
let Ok(h) = fs.create(COUNT) else { return false };
|
||||
let ok = fs.write(h, &digits[..n]) == Ok(n);
|
||||
let _ = fs.close(h);
|
||||
ok
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let Some(mut fs) = libkos::start::data().and_then(|d| Storage::attach(d, DATA_BUF_VA).ok()) else {
|
||||
println!("counter: not allowed to keep anything, so this is run 1 as far as it knows");
|
||||
return;
|
||||
};
|
||||
let count = read_count(&mut fs) + 1;
|
||||
if write_count(&mut fs, count) {
|
||||
println!("counter: run {} (kept in its own data folder)", count);
|
||||
} else {
|
||||
println!("counter: run {}, but it could not save that", count);
|
||||
}
|
||||
}
|
||||
|
||||
libkos::entry!(main);
|
||||
6
crates/apps/ls/manifest.toml
Normal file
6
crates/apps/ls/manifest.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[app]
|
||||
id = "kos.ls"
|
||||
name = "ls"
|
||||
version = "1.0.0"
|
||||
binary = "ls"
|
||||
author = "K-OS"
|
||||
@@ -10,4 +10,5 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
manifest = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! The shell: a few built-ins, and anything else is a program on disk.
|
||||
//! `name args...` runs `/bin/name` through init, handing it the terminal as
|
||||
//! its output, read-only storage rooted at the current directory, and what
|
||||
//! its manifest (`/bin/name.manifest`) asks for that the shell can give;
|
||||
//! then waits for it (Ctrl+C kills it) and takes the storage back.
|
||||
//! The shell: a few built-ins, and anything else is an app. `name args...`
|
||||
//! asks the permission manager to run it, passing along the terminal as
|
||||
//! its output and a read-only grant to the current folder, which is all
|
||||
//! the shell itself has to give; the app gets the rest of what it may have
|
||||
//! from the permission manager. Then it waits (Ctrl+C kills it) and takes
|
||||
//! the grant back. The owner manages apps from here too.
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -10,33 +11,28 @@
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
use libkos::apps::{Apps, Refused};
|
||||
use libkos::fs::Storage;
|
||||
use libkos::spawn::{SpawnError, spawn};
|
||||
use types::manifest::Needs;
|
||||
use libkos::{print, println, syscall};
|
||||
use manifest::{Capabilities, Capability};
|
||||
use types::address::VirtualAddress;
|
||||
use types::apps::reply;
|
||||
use types::bounded::CapSlot;
|
||||
use types::budget::Limits;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
use types::start_arg::StartArg;
|
||||
|
||||
/// Where init's manifest put them: `uses`, then the spawn endpoint.
|
||||
/// Where init's manifest put them: `uses`, then storage.
|
||||
const TERMINAL: CapSlot = CapSlot::const_new(0);
|
||||
const STORAGE: CapSlot = CapSlot::const_new(1);
|
||||
const DISPLAY: CapSlot = CapSlot::const_new(2);
|
||||
const INIT: CapSlot = CapSlot::const_new(3);
|
||||
const PERMISSION: CapSlot = CapSlot::const_new(1);
|
||||
/// The owner's files, `/home`: the shell's `/`.
|
||||
const HOME: CapSlot = CapSlot::const_new(2);
|
||||
|
||||
/// Bits on the shell's notification: the program died (its death watch is
|
||||
/// unbadged), or Ctrl+C was typed (the terminal's copy).
|
||||
const DIED: usize = 1;
|
||||
const INTERRUPTED: usize = 1 << 1;
|
||||
/// Longest manifest read.
|
||||
const MAX_MANIFEST: usize = 512;
|
||||
|
||||
/// What each program gets, out of the shell's allowance.
|
||||
const PROGRAM: Limits = Limits { pages: 128, endpoints: 1, notifications: 1, regions: 1 };
|
||||
const MAX_ARGS: usize = 16;
|
||||
/// Where storage's buffer is mapped.
|
||||
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
|
||||
const STORAGE_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x0180_0000);
|
||||
const REQUEST_PAGE_VA: VirtualAddress = VirtualAddress::from_raw(0x0190_0000);
|
||||
|
||||
/// The current directory, as a path on disk: no leading or trailing `/`.
|
||||
struct Cwd {
|
||||
@@ -50,7 +46,7 @@ impl Cwd {
|
||||
}
|
||||
|
||||
/// Where `cd to` from here leads: `/` starts at the root, `..` goes up
|
||||
/// (the shell holds the whole disk, so that is its to resolve).
|
||||
/// (the shell holds all of its root, so that is its to resolve).
|
||||
fn join(&self, to: &str) -> Option<Cwd> {
|
||||
let mut next = match to.starts_with('/') {
|
||||
true => Cwd {
|
||||
@@ -98,87 +94,118 @@ impl core::fmt::Display for Cwd {
|
||||
|
||||
struct Shell {
|
||||
fs: Storage,
|
||||
apps: Apps,
|
||||
cwd: Cwd,
|
||||
done: CapSlot,
|
||||
}
|
||||
|
||||
/// `/bin/<name><suffix>` into `out`, if it fits.
|
||||
fn bin_path<'a>(out: &'a mut [u8], name: &str, suffix: &[u8]) -> Option<&'a [u8]> {
|
||||
let path = out.get_mut(..4 + name.len() + suffix.len())?;
|
||||
path[..4].copy_from_slice(b"bin/");
|
||||
path[4..4 + name.len()].copy_from_slice(name.as_bytes());
|
||||
path[4 + name.len()..].copy_from_slice(suffix);
|
||||
Some(path)
|
||||
}
|
||||
/// `display, storage`.
|
||||
struct Names(Capabilities);
|
||||
|
||||
/// What `name`'s manifest asks for; none without one. `None` if it asks
|
||||
/// for something the shell does not know how to give.
|
||||
fn needs(sh: &mut Shell, name: &str) -> Option<Needs> {
|
||||
let mut path = [0u8; types::storage::MAX_PATH];
|
||||
let path = bin_path(&mut path, name, b".manifest")?;
|
||||
let Ok(file) = sh.fs.open(path) else {
|
||||
return Some(Needs::default());
|
||||
};
|
||||
let mut text = [0u8; MAX_MANIFEST];
|
||||
let read = sh.fs.read(file, &mut text);
|
||||
let _ = sh.fs.close(file);
|
||||
match types::manifest::parse(&text[..read.ok()?]) {
|
||||
Ok(needs) => Some(needs),
|
||||
Err(types::manifest::Unknown(what)) => {
|
||||
println!("shell: {}: its manifest asks for {}", name, core::str::from_utf8(what).unwrap_or("?"));
|
||||
None
|
||||
impl core::fmt::Display for Names {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
for (i, c) in self.0.iter().enumerate() {
|
||||
if i > 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
f.write_str(c.name())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs `/bin/args[0]` with `args` in the current directory, and waits
|
||||
/// for it to finish or Ctrl+C.
|
||||
fn names(bits: usize) -> Names {
|
||||
Names(Capabilities::from_bits(bits as u32))
|
||||
}
|
||||
|
||||
/// Says why the permission manager refused, about `what`.
|
||||
fn refused(what: &str, r: Refused) {
|
||||
match r.code {
|
||||
reply::NOT_INSTALLED => println!("shell: {}: not found", what),
|
||||
reply::MISSING => println!("shell: {}: cannot run without {}", what, names(r.detail)),
|
||||
reply::ALREADY_INSTALLED => println!("shell: {}: already installed", what),
|
||||
reply::UNAVAILABLE => println!("shell: {}: this device has no {}", what, names(r.detail)),
|
||||
reply::DECLINED => println!("shell: {}: not installed", what),
|
||||
reply::NO_MANIFEST => println!("shell: {}: no app here (no manifest.toml)", what),
|
||||
reply::BAD_MANIFEST if r.detail > 0 => println!("shell: {}: bad manifest, line {}", what, r.detail),
|
||||
reply::BAD_MANIFEST => println!("shell: {}: bad manifest", what),
|
||||
reply::NOT_REQUESTED => println!("shell: {}: never asked for that", what),
|
||||
reply::BUSY => println!("shell: no room to run {}", what),
|
||||
_ => println!("shell: {}: failed", what),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs app `args[0]` with `args` in the current folder, and waits for it
|
||||
/// to finish or Ctrl+C.
|
||||
fn run_program(sh: &mut Shell, args: &[&str]) {
|
||||
let mut file = [0u8; types::spawn::MAX_NAME];
|
||||
let Some(file) = bin_path(&mut file, args[0], b"") else {
|
||||
println!("shell: {}: name too long", args[0]);
|
||||
return;
|
||||
};
|
||||
let Some(needs) = needs(sh, args[0]) else {
|
||||
return;
|
||||
};
|
||||
let child = match spawn(INIT, file, PROGRAM) {
|
||||
Ok(c) => c,
|
||||
Err(SpawnError::NotFound) => return println!("shell: {}: not found", args[0]),
|
||||
Err(SpawnError::OverAllowance) => return println!("shell: no room to run {}", args[0]),
|
||||
Err(SpawnError::Failed) => return println!("shell: {}: cannot run", args[0]),
|
||||
};
|
||||
let call = PermissionSet::empty().with(Permission::Execute);
|
||||
let grant = sh.fs.grant(sh.cwd.as_bytes(), false).ok();
|
||||
let arg = StartArg {
|
||||
log: child.give(TERMINAL, call),
|
||||
storage: grant.and_then(|g| child.give(g, call)),
|
||||
args: child.set_args(args),
|
||||
// It subscribes to input by sending the display a notification.
|
||||
display: needs.display.then(|| child.give_sending(DISPLAY, call)).flatten(),
|
||||
console: None,
|
||||
};
|
||||
let folder = sh.fs.grant(sh.cwd.as_bytes(), false).ok();
|
||||
// A Ctrl+C typed since the last program is not for this one.
|
||||
let _ = syscall::poll(sh.done);
|
||||
let watched = syscall::watch_death(child.process, sh.done).is_ok();
|
||||
if child.start_with(arg) && watched {
|
||||
while syscall::wait(sh.done).is_ok_and(|bits| {
|
||||
if bits & INTERRUPTED != 0 {
|
||||
syscall::kill(child.process);
|
||||
match sh.apps.launch(args, Some(TERMINAL), folder) {
|
||||
Ok((process, granted)) => {
|
||||
if syscall::watch_death(process, sh.done).is_ok() {
|
||||
while syscall::wait(sh.done).is_ok_and(|bits| {
|
||||
if bits & INTERRUPTED != 0 {
|
||||
syscall::kill(process);
|
||||
}
|
||||
bits & DIED == 0
|
||||
}) {}
|
||||
} else {
|
||||
let _ = syscall::kill(process);
|
||||
}
|
||||
bits & DIED == 0
|
||||
}) {}
|
||||
} else {
|
||||
let _ = syscall::kill(child.process);
|
||||
println!("shell: {}: cannot start", args[0]);
|
||||
syscall::drop_cap(process);
|
||||
// It drew over the terminal and took the keyboard.
|
||||
if Capabilities::from_bits(granted).contains(Capability::Display) {
|
||||
libkos::terminal::redraw(TERMINAL);
|
||||
}
|
||||
}
|
||||
Err(r) => refused(args[0], r),
|
||||
}
|
||||
syscall::drop_cap(child.process);
|
||||
if let Some(g) = grant {
|
||||
if let Some(g) = folder {
|
||||
let _ = libkos::fs::release(g);
|
||||
}
|
||||
// It drew over the terminal and took the keyboard.
|
||||
if needs.display {
|
||||
libkos::terminal::redraw(TERMINAL);
|
||||
}
|
||||
|
||||
/// Installs the app in folder `from` (its manifest and binary); the owner
|
||||
/// is asked on the terminal.
|
||||
fn install(sh: &mut Shell, from: &str) {
|
||||
let Some(path) = sh.cwd.join(from) else {
|
||||
return println!("install: {}: no such folder", from);
|
||||
};
|
||||
let Ok(source) = sh.fs.grant(path.as_bytes(), false) else {
|
||||
return println!("install: {}: no such folder", from);
|
||||
};
|
||||
match sh.apps.install(source) {
|
||||
Ok(0) => println!("installed"),
|
||||
Ok(bits) => println!("installed, may use: {}", names(bits as usize)),
|
||||
Err(r) => refused(from, r),
|
||||
}
|
||||
let _ = libkos::fs::release(source);
|
||||
}
|
||||
|
||||
fn list(sh: &mut Shell) {
|
||||
let mut text = [0u8; types::apps::PAGE_SIZE];
|
||||
match sh.apps.list(&mut text) {
|
||||
Ok(n) => print!("{}", core::str::from_utf8(&text[..n]).unwrap_or("")),
|
||||
Err(r) => refused("apps", r),
|
||||
}
|
||||
}
|
||||
|
||||
/// `allow <app id> <capability>` or `revoke ...`.
|
||||
fn change(sh: &mut Shell, args: &[&str], allow: bool) {
|
||||
let verb = if allow { "allow" } else { "revoke" };
|
||||
let (Some(id), Some(name)) = (args.get(1), args.get(2)) else {
|
||||
return println!("usage: {} <app id> <capability>", verb);
|
||||
};
|
||||
let Some(cap) = Capability::from_name(name) else {
|
||||
return println!("{}: {}: no such capability", verb, name);
|
||||
};
|
||||
let done = match allow {
|
||||
true => sh.apps.allow(id, cap.index()),
|
||||
false => sh.apps.revoke(id, cap.index()),
|
||||
};
|
||||
if let Err(r) = done {
|
||||
refused(id, r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,10 +228,18 @@ fn run(sh: &mut Shell, line: &str) {
|
||||
None => {}
|
||||
Some("help") => {
|
||||
println!("built-ins: help, echo <text>, clear, cd <dir>, pwd");
|
||||
println!("anything else runs a program from the disk: try ls, cat readme.txt");
|
||||
println!("programs see the current directory, read-only, and nothing above it");
|
||||
println!("apps: apps, install <folder>, allow|revoke <app id> <capability>");
|
||||
println!("anything else runs an app: try ls, cat readme.txt, snake");
|
||||
println!("apps see the current directory, read-only, and only what you allowed");
|
||||
println!("Ctrl+C stops a running program; snake quits with q");
|
||||
}
|
||||
Some("apps") => list(sh),
|
||||
Some("install") => match args.get(1) {
|
||||
Some(from) => install(sh, from),
|
||||
None => println!("usage: install <folder>"),
|
||||
},
|
||||
Some("allow") => change(sh, args, true),
|
||||
Some("revoke") => change(sh, args, false),
|
||||
Some("cd") => cd(sh, args.get(1).copied().unwrap_or("/")),
|
||||
Some("pwd") => println!("{}", sh.cwd),
|
||||
Some("echo") => println!("{}", Joined(&args[1..])),
|
||||
@@ -237,15 +272,20 @@ fn main() {
|
||||
println!("shell: no notification to wait on");
|
||||
return;
|
||||
};
|
||||
let Ok(fs) = Storage::attach(STORAGE, STORAGE_BUF_VA) else {
|
||||
let Ok(fs) = Storage::attach(HOME, STORAGE_BUF_VA) else {
|
||||
println!("shell: no storage");
|
||||
return;
|
||||
};
|
||||
let Some(apps) = Apps::connect(PERMISSION, REQUEST_PAGE_VA) else {
|
||||
println!("shell: no permission manager");
|
||||
return;
|
||||
};
|
||||
if !libkos::terminal::on_interrupt(TERMINAL, done, types::badge::Badge::new(INTERRUPTED)) {
|
||||
println!("shell: Ctrl+C will not work");
|
||||
}
|
||||
let mut sh = Shell {
|
||||
fs,
|
||||
apps,
|
||||
cwd: Cwd {
|
||||
path: [0; types::storage::MAX_PATH],
|
||||
len: 0,
|
||||
|
||||
6
crates/apps/sleep/manifest.toml
Normal file
6
crates/apps/sleep/manifest.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[app]
|
||||
id = "kos.sleep"
|
||||
name = "sleep"
|
||||
version = "1.0.0"
|
||||
binary = "sleep"
|
||||
author = "K-OS"
|
||||
9
crates/apps/snake/manifest.toml
Normal file
9
crates/apps/snake/manifest.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[app]
|
||||
id = "kos.snake"
|
||||
name = "Snake"
|
||||
version = "1.0.0"
|
||||
binary = "snake"
|
||||
author = "K-OS"
|
||||
|
||||
[capabilities]
|
||||
required = ["display"]
|
||||
@@ -10,4 +10,5 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
manifest = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
|
||||
@@ -9,19 +9,21 @@
|
||||
extern crate libkos;
|
||||
|
||||
use libkos::fs::{FsError, Storage};
|
||||
use libkos::spawn::{SpawnError, spawn};
|
||||
use libkos::spawn::{SpawnError, Spawned, spawn};
|
||||
use libkos::syscall;
|
||||
use types::bounded::CapSlot;
|
||||
use types::budget::Limits;
|
||||
|
||||
/// Where init put them (see init's manifest: `uses`, the spawn endpoint,
|
||||
/// then control).
|
||||
const STORAGE: CapSlot = CapSlot::const_new(0);
|
||||
const INIT: CapSlot = CapSlot::const_new(1);
|
||||
const CONTROL: CapSlot = CapSlot::const_new(2);
|
||||
const PERMISSION: CapSlot = CapSlot::const_new(0);
|
||||
const STORAGE: CapSlot = CapSlot::const_new(1);
|
||||
const INIT: CapSlot = CapSlot::const_new(2);
|
||||
const CONTROL: CapSlot = CapSlot::const_new(3);
|
||||
/// Where storage's buffers are mapped: the whole disk's, a narrowed one's.
|
||||
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
|
||||
const NARROW_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0190_0000);
|
||||
const REQUEST_PAGE_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x01C0_0000);
|
||||
/// Read back from the disk image by scripts/test-qemu.sh.
|
||||
const NOTE: &[u8] = b"written by k-os on ext4\n";
|
||||
|
||||
@@ -29,10 +31,18 @@ const NOTE: &[u8] = b"written by k-os on ext4\n";
|
||||
/// the first has died and its limits came back.
|
||||
const HELLO: Limits = Limits { pages: 80, endpoints: 0, notifications: 0, regions: 0 };
|
||||
|
||||
/// Spawns the program at `path`, read through a grant made just for it.
|
||||
fn spawn_file(fs: &mut Storage, path: &[u8], limits: Limits) -> Result<Spawned, SpawnError> {
|
||||
let file = fs.grant(path, false).map_err(|_| SpawnError::Failed)?;
|
||||
let child = spawn(INIT, file, limits);
|
||||
let _ = libkos::fs::release(file);
|
||||
child
|
||||
}
|
||||
|
||||
/// Spawns hello, runs it, and waits for it to die; hands it `grant`
|
||||
/// first, if there is one.
|
||||
fn run_hello(died: CapSlot, grant: Option<CapSlot>) -> Result<(), &'static str> {
|
||||
let child = spawn(INIT, b"bin/hello", HELLO).map_err(|_| "init would not spawn hello")?;
|
||||
fn run_hello(fs: &mut Storage, died: CapSlot, grant: Option<CapSlot>) -> Result<(), &'static str> {
|
||||
let child = spawn_file(fs, b"bin/hello", HELLO).map_err(|_| "init would not spawn hello")?;
|
||||
let call = types::permission::PermissionSet::empty().with(types::permission::Permission::Execute);
|
||||
if grant.is_some_and(|g| child.give(g, call).is_none()) {
|
||||
return Err("a storage grant could not be passed on");
|
||||
@@ -78,7 +88,7 @@ fn narrow_storage(fs: &mut Storage, died: CapSlot) -> Result<(), &'static str> {
|
||||
if widened.and_then(|mut w| w.create(b"x")).is_ok() {
|
||||
return Err("a grant was widened to write");
|
||||
}
|
||||
run_hello(died, Some(grant))?;
|
||||
run_hello(fs, died, Some(grant))?;
|
||||
libkos::fs::release(grant).map_err(|_| "release failed")?;
|
||||
if inner.list_dir(b"", |_| {}).is_ok() {
|
||||
return Err("a released grant still works");
|
||||
@@ -86,6 +96,33 @@ fn narrow_storage(fs: &mut Storage, died: CapSlot) -> Result<(), &'static str> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An app runs through the permission manager with what it was granted;
|
||||
/// revoking that stops it, and it cannot run again until allowed.
|
||||
fn revoke_stops_a_running_app(died: CapSlot) -> Result<(), &'static str> {
|
||||
use manifest::Capability;
|
||||
let apps = libkos::apps::Apps::connect(PERMISSION, REQUEST_PAGE_VA).ok_or("no permission manager")?;
|
||||
let display = Capability::Display.index();
|
||||
let (snake, granted) = apps.launch(&["snake"], None, None).map_err(|_| "snake would not launch")?;
|
||||
if granted & manifest::Capabilities::NONE.with(Capability::Display).bits() == 0 {
|
||||
return Err("snake runs without the display it was granted");
|
||||
}
|
||||
syscall::watch_death(snake, died).map_err(|_| "cannot watch snake")?;
|
||||
apps.revoke("kos.snake", display).map_err(|_| "revoke failed")?;
|
||||
syscall::wait(died).map_err(|_| "wait failed")?;
|
||||
syscall::drop_cap(snake);
|
||||
let refused = apps.launch(&["snake"], None, None);
|
||||
if !matches!(refused, Err(r) if r.code == types::apps::reply::MISSING) {
|
||||
return Err("snake ran without the display it requires");
|
||||
}
|
||||
apps.allow("kos.snake", display).map_err(|_| "allow failed")?;
|
||||
let (snake, _) = apps.launch(&["snake"], None, None).map_err(|_| "snake did not run once allowed again")?;
|
||||
syscall::watch_death(snake, died).map_err(|_| "cannot watch snake")?;
|
||||
syscall::kill(snake);
|
||||
syscall::wait(died).map_err(|_| "wait failed")?;
|
||||
syscall::drop_cap(snake);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check() -> Result<(), &'static str> {
|
||||
let mut fs = Storage::attach(STORAGE, STORAGE_BUF_VA).map_err(|_| "cannot attach to storage")?;
|
||||
write_a_file(&mut fs)?;
|
||||
@@ -95,21 +132,24 @@ fn check() -> Result<(), &'static str> {
|
||||
narrow_storage(&mut fs, died)?;
|
||||
libkos::println!("spawntest: storage grants stay in scope, pass on, release: ok");
|
||||
|
||||
run_hello(died, None)?;
|
||||
run_hello(&mut fs, died, None)?;
|
||||
libkos::println!("spawntest: spawned program runs and its death is heard: ok");
|
||||
|
||||
let greedy = Limits { pages: 200, ..HELLO };
|
||||
if spawn(INIT, b"bin/hello", greedy).err() != Some(SpawnError::OverAllowance) {
|
||||
if spawn_file(&mut fs, b"bin/hello", greedy).err() != Some(SpawnError::OverAllowance) {
|
||||
return Err("init spawned past the allowance");
|
||||
}
|
||||
let tiny = Limits { pages: 1, ..HELLO };
|
||||
if spawn(INIT, b"bin/nothere", tiny).err() != Some(SpawnError::NotFound) {
|
||||
if spawn_file(&mut fs, b"bin/nothere", tiny).err() != Some(SpawnError::NotFound) {
|
||||
return Err("a missing program was not reported");
|
||||
}
|
||||
libkos::println!("spawntest: allowance and missing files refused: ok");
|
||||
|
||||
run_hello(died, None)?;
|
||||
run_hello(&mut fs, died, None)?;
|
||||
libkos::println!("spawntest: allowance comes back when a child dies: ok");
|
||||
|
||||
revoke_stops_a_running_app(died)?;
|
||||
libkos::println!("spawntest: revoking stops a running app, allowing lets it run: ok");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user