feat(users): per-user sealed folders users/<id>/{home,apps,data,permissions}; storage SET_USER/LOCK/PURGE; init sign-in/sign-out loop (purge fresh/removed/guest, seed from factory, session start/stop); shell logout; pm user+system app shelves; terminal SIGNED_OUT; per-program stack (gatekeeper 12p); 128K load scratch
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
//! The encrypted areas' starting point. The image ships starter content
|
||||
//! (the owner's first files, the pre-approved decisions) in plaintext under
|
||||
//! `factory/`; with a new master key (a fresh device, or one just wiped)
|
||||
//! init empties the encrypted areas and seeds them from it, storage
|
||||
//! sealing what is written. A device just wiped so looks factory-fresh,
|
||||
//! not full of files no key opens. An area simply missing (a disk image
|
||||
//! rebuilt) is seeded too.
|
||||
//! A user's folder's starting point. The image ships starter content (a
|
||||
//! user's first files, the pre-approved decisions) in plaintext under
|
||||
//! `factory/`; each area of the signed-in user's folder that is missing (a
|
||||
//! new user, a guest, a disk image rebuilt) is made and seeded from it,
|
||||
//! storage sealing what is written.
|
||||
|
||||
use libkos::fs::Storage;
|
||||
use types::storage::MAX_PATH;
|
||||
|
||||
const AREAS: [&str; 3] = ["home", "system", "data"];
|
||||
/// Each area of a user's folder, and what it starts with.
|
||||
const AREAS: [(&str, &str); 4] = [("home", "factory/home"), ("permissions", "factory/permissions"), ("data", "factory/data"), ("apps", "factory/apps")];
|
||||
/// Entries handled per listing; a folder with more is listed again.
|
||||
const BATCH: usize = 32;
|
||||
const NAME: usize = types::storage::DIR_NAME_SIZE;
|
||||
@@ -61,25 +60,6 @@ fn entries(fs: &mut Storage, dir: &Path) -> Option<([([u8; NAME], usize, bool);
|
||||
Some((out, n))
|
||||
}
|
||||
|
||||
/// Removes everything inside `dir`, keeping `dir`.
|
||||
fn empty(fs: &mut Storage, dir: &Path) -> Result<(), &'static str> {
|
||||
loop {
|
||||
let (list, n) = entries(fs, dir).ok_or("cannot list an area to empty it")?;
|
||||
if n == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
for (name, len, is_dir) in &list[..n] {
|
||||
let child = dir.join(&name[..*len]).ok_or("path too long")?;
|
||||
if *is_dir {
|
||||
empty(fs, &child)?;
|
||||
fs.rmdir(child.as_bytes()).map_err(|_| "cannot remove a folder")?;
|
||||
} else {
|
||||
fs.delete(child.as_bytes()).map_err(|_| "cannot remove a file")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the file `from` to a new file `to`.
|
||||
fn copy_file(fs: &mut Storage, from: &Path, to: &Path, buf: &mut [u8]) -> Result<(), &'static str> {
|
||||
let src = fs.open(from.as_bytes()).map_err(|_| "cannot open a factory file")?;
|
||||
@@ -115,23 +95,23 @@ fn copy_tree(fs: &mut Storage, from: &Path, to: &Path, buf: &mut [u8]) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Readies the encrypted areas once storage has the key: `fresh` when the
|
||||
/// master key is new.
|
||||
pub(crate) fn prepare(fs: &mut Storage, fresh: bool, buf: &mut [u8]) -> Result<(), &'static str> {
|
||||
for area in AREAS {
|
||||
let path = Path::new(area);
|
||||
let present = fs.list_dir(path.as_bytes(), |_| {}).is_ok();
|
||||
if present && !fresh {
|
||||
/// Readies `folder`'s areas once storage has its key.
|
||||
pub(crate) fn prepare(fs: &mut Storage, folder: types::user::UserFolder, buf: &mut [u8]) -> Result<(), &'static str> {
|
||||
let name = core::str::from_utf8(folder.as_bytes()).map_err(|_| "a user folder named oddly")?;
|
||||
let base = Path::new("users").join(name.as_bytes()).ok_or("path too long")?;
|
||||
for dir in [Path::new("users"), base] {
|
||||
if fs.list_dir(dir.as_bytes(), |_| {}).is_err() {
|
||||
fs.mkdir(dir.as_bytes()).map_err(|_| "cannot make a user's folder")?;
|
||||
}
|
||||
}
|
||||
for (area, factory) in AREAS {
|
||||
let path = base.join(area.as_bytes()).ok_or("path too long")?;
|
||||
if fs.list_dir(path.as_bytes(), |_| {}).is_ok() {
|
||||
continue;
|
||||
}
|
||||
if present {
|
||||
empty(fs, &path)?;
|
||||
} else {
|
||||
fs.mkdir(path.as_bytes()).map_err(|_| "cannot make an area")?;
|
||||
}
|
||||
let factory = Path::new("factory").join(area.as_bytes()).ok_or("path too long")?;
|
||||
copy_tree(fs, &factory, &path, buf)?;
|
||||
libkos::println!("init: {} {}", if fresh { "set up" } else { "seeded" }, area);
|
||||
fs.mkdir(path.as_bytes()).map_err(|_| "cannot make an area")?;
|
||||
copy_tree(fs, &Path::new(factory), &path, buf)?;
|
||||
libkos::println!("init: set up {}", area);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ use types::badge::Badge;
|
||||
use types::bounded::CapSlot;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
use types::user::UserFolder;
|
||||
|
||||
use crate::devices::Devices;
|
||||
use crate::load::{self, LoadedProcess};
|
||||
use crate::manifest::{Device, Program};
|
||||
use crate::manifest::{Device, Program, Scope};
|
||||
use crate::spawn::Spawner;
|
||||
|
||||
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(0x10000);
|
||||
@@ -34,6 +36,22 @@ pub(crate) struct Launcher<'a> {
|
||||
/// Bound to init's thread: spawned children's deaths, a bit each.
|
||||
pub(crate) deaths: CapSlot,
|
||||
pub(crate) spawner: Spawner,
|
||||
/// Who is signed in, and what runs for them (stopped at sign-out).
|
||||
pub(crate) user: Option<crate::session::User>,
|
||||
pub(crate) running: [Option<CapSlot>; MAX_SESSION],
|
||||
/// What a session runs after `SESSION`: the desktop, or the tests.
|
||||
pub(crate) desktop: &'static [Program],
|
||||
}
|
||||
|
||||
/// Programs one session runs at once, at most.
|
||||
pub(crate) const MAX_SESSION: usize = 4;
|
||||
|
||||
/// What became of starting a program.
|
||||
pub(crate) struct Launched {
|
||||
pub(crate) note: &'static str,
|
||||
pub(crate) process: CapSlot,
|
||||
/// Its endpoint, if it serves one.
|
||||
pub(crate) ep: Option<CapSlot>,
|
||||
}
|
||||
|
||||
impl<'a> Launcher<'a> {
|
||||
@@ -47,25 +65,46 @@ impl<'a> Launcher<'a> {
|
||||
// Storage, started by boot, is not among them: it is reached
|
||||
// through grants (a program's `storage` scope).
|
||||
let served = [None; MAX_SERVED];
|
||||
Ok(Self { storage, scratch, devices, served, next_badge: 1, spawn_ep, deaths, spawner: Spawner::new() })
|
||||
Ok(Self {
|
||||
storage,
|
||||
scratch,
|
||||
devices,
|
||||
served,
|
||||
next_badge: 1,
|
||||
spawn_ep,
|
||||
deaths,
|
||||
spawner: Spawner::new(),
|
||||
user: None,
|
||||
running: [None; MAX_SESSION],
|
||||
desktop: &[],
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn storage(&mut self) -> (&mut Storage, &mut [u8]) {
|
||||
(self.storage, self.scratch)
|
||||
}
|
||||
|
||||
/// The endpoint of the running server `name`.
|
||||
pub(crate) fn served(&self, name: &str) -> Option<CapSlot> {
|
||||
self.endpoint(name)
|
||||
}
|
||||
|
||||
fn endpoint(&self, name: &str) -> Option<CapSlot> {
|
||||
self.served.iter().flatten().find(|(n, _)| *n == name).map(|(_, ep)| *ep)
|
||||
}
|
||||
|
||||
/// What became of the program's device, if it has one.
|
||||
pub(crate) fn launch(&mut self, p: &Program) -> Result<&'static str, &'static str> {
|
||||
pub(crate) fn launch(&mut self, p: &Program) -> Result<Launched, &'static str> {
|
||||
let loaded =
|
||||
load::load_binary(self.storage, self.scratch, p.file, p.limits).ok_or("cannot load")?;
|
||||
load::load_binary(self.storage, self.scratch, p.file, p.limits, p.stack_pages).ok_or("cannot load")?;
|
||||
let child = loaded.process_cap;
|
||||
let ep = match p.serves {
|
||||
true => {
|
||||
let ep = syscall::create_endpoint().ok_or("no endpoint")?;
|
||||
// Started again (a new sign-in): its clients went with the
|
||||
// last one, so the endpoint is reused.
|
||||
let ep = match self.endpoint(p.name) {
|
||||
Some(ep) => ep,
|
||||
None => syscall::create_endpoint().ok_or("no endpoint")?,
|
||||
};
|
||||
// Read to receive on it; init keeps the original.
|
||||
let server = perms(&[Permission::Read, Permission::Execute]);
|
||||
syscall::delegate(ep, child, server).ok_or("cannot give its endpoint")?;
|
||||
@@ -87,8 +126,10 @@ impl<'a> Launcher<'a> {
|
||||
given.ok_or("cannot give an endpoint")?;
|
||||
}
|
||||
// Storage issues its own badges, each naming a scope.
|
||||
if let Some(scope) = p.storage {
|
||||
let grant = self.storage.grant(scope.path, scope.write).map_err(|_| "storage refused a grant")?;
|
||||
for scope in p.storage {
|
||||
let mut path = [0u8; types::storage::MAX_PATH];
|
||||
let path = self.scope_path(scope, &mut path).ok_or("a storage scope for nobody")?;
|
||||
let grant = self.storage.grant(path, scope.write).map_err(|_| "storage refused a grant")?;
|
||||
let given = syscall::delegate(grant, child, perms(&[Permission::Execute, Permission::Delegate]));
|
||||
syscall::drop_cap(grant);
|
||||
given.ok_or("cannot give storage")?;
|
||||
@@ -100,6 +141,14 @@ impl<'a> Launcher<'a> {
|
||||
let control = crate::devices::system_control().ok_or("no SystemControl to give")?;
|
||||
syscall::delegate(control, child, perms(&[Permission::Execute])).ok_or("cannot give SystemControl")?;
|
||||
}
|
||||
if p.signs_out {
|
||||
let badge = Badge::new(types::spawn::SESSION_BADGE);
|
||||
// Delegate only to hand it over: the shell's copy cannot pass it on.
|
||||
let copy = syscall::mint(self.spawn_ep, badge, perms(&[Permission::Execute, Permission::Delegate])).map_err(|_| "cannot mint")?;
|
||||
let given = syscall::delegate(copy, child, perms(&[Permission::Execute]));
|
||||
syscall::drop_cap(copy);
|
||||
given.ok_or("cannot give signing out")?;
|
||||
}
|
||||
let console = libkos::start::console()
|
||||
.filter(|_| p.console)
|
||||
.and_then(|c| syscall::delegate(c, child, perms(&[Permission::Write])));
|
||||
@@ -107,23 +156,34 @@ impl<'a> Launcher<'a> {
|
||||
|
||||
let mut note = "";
|
||||
if let Some(ep) = ep {
|
||||
if let Some(free) = self.served.iter_mut().find(|s| s.is_none()) {
|
||||
let known = self.served.iter().flatten().any(|(n, _)| *n == p.name);
|
||||
if let Some(free) = self.served.iter_mut().find(|s| s.is_none()).filter(|_| !known) {
|
||||
*free = Some((p.name, ep));
|
||||
}
|
||||
for device in p.devices {
|
||||
note = self.give(*device, ep);
|
||||
}
|
||||
if p.unlocks {
|
||||
let fresh = unlock(ep)?;
|
||||
crate::factory::prepare(self.storage, fresh, self.scratch)?;
|
||||
note = if fresh { ", the owner is in (a new key)" } else { ", the owner is in" };
|
||||
}
|
||||
if console.is_some() {
|
||||
hand_over_console(ep);
|
||||
note = ", console handed over";
|
||||
}
|
||||
}
|
||||
Ok(note)
|
||||
Ok(Launched { note, process: child, ep })
|
||||
}
|
||||
|
||||
/// The path on disk `scope` names: inside the user's folder if it is a
|
||||
/// user's scope.
|
||||
fn scope_path<'p>(&self, scope: &Scope, buf: &'p mut [u8; types::storage::MAX_PATH]) -> Option<&'p [u8]> {
|
||||
if !scope.user {
|
||||
return Some(scope.path);
|
||||
}
|
||||
let folder: UserFolder = self.user?.folder;
|
||||
let mut len = 0;
|
||||
for part in [&b"users/"[..], folder.as_bytes(), if scope.path.is_empty() { b"" } else { b"/" }, scope.path] {
|
||||
buf.get_mut(len..len + part.len())?.copy_from_slice(part);
|
||||
len += part.len();
|
||||
}
|
||||
Some(&buf[..len])
|
||||
}
|
||||
|
||||
/// A badged copy of init's endpoint: the badge names the client and so
|
||||
@@ -154,29 +214,6 @@ impl<'a> Launcher<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits for the gatekeeper behind `ep` to authenticate the owner, then
|
||||
/// passes the master key and a fresh seed on to storage, which alone keeps
|
||||
/// them. Whether the key is new.
|
||||
fn unlock(ep: CapSlot) -> Result<bool, &'static str> {
|
||||
use types::gatekeeper::{KEY, SEED, UNLOCK, reply};
|
||||
let fresh = match syscall::request(ep, [UNLOCK, 0, 0, 0]) {
|
||||
Ok([reply::OK, fresh, ..]) => fresh != 0,
|
||||
_ => return Err("the gatekeeper could not unlock"),
|
||||
};
|
||||
for (ask, give) in [(KEY, types::storage::op::SET_KEY), (SEED, types::storage::op::SET_SEED)] {
|
||||
let mut secret = syscall::request(ep, [ask, 0, 0, 0]).map_err(|_| "the gatekeeper gave no key")?;
|
||||
let given = [0, 1].iter().all(|&half| {
|
||||
let words = [give, half, secret[half * 2], secret[half * 2 + 1]];
|
||||
syscall::request(crate::STORAGE_EP, words).is_ok_and(|r| r[0] == types::storage::reply::OK)
|
||||
});
|
||||
secret.fill(0);
|
||||
if !given {
|
||||
return Err("storage would not take the key");
|
||||
}
|
||||
}
|
||||
Ok(fresh)
|
||||
}
|
||||
|
||||
/// From here on init prints through the log server, like everyone else,
|
||||
/// and holds no console of its own.
|
||||
fn hand_over_console(log: CapSlot) {
|
||||
|
||||
@@ -9,6 +9,7 @@ mod launch;
|
||||
mod load;
|
||||
mod manifest;
|
||||
mod selftest;
|
||||
mod session;
|
||||
mod spawn;
|
||||
|
||||
use libkos::fs::Storage;
|
||||
@@ -59,15 +60,20 @@ pub fn run() -> ! {
|
||||
// The test image carries the SMP self-test; it and the tests run
|
||||
// instead of the desktop, and the last one halts the machine.
|
||||
let (storage, scratch) = launcher.storage();
|
||||
if storage
|
||||
launcher.desktop = if storage
|
||||
.open(b"bin/smptest")
|
||||
.is_ok_and(|h| storage.close(h).is_ok())
|
||||
{
|
||||
libkos::println!("init: running the SMP self-test");
|
||||
selftest::run(storage, scratch);
|
||||
launch_all(&mut launcher, manifest::TESTS);
|
||||
manifest::TESTS
|
||||
} else {
|
||||
launch_all(&mut launcher, manifest::DESKTOP);
|
||||
manifest::DESKTOP
|
||||
};
|
||||
// Nothing that touches a user's files starts before someone is in.
|
||||
if let Err(why) = session::sign_in(&mut launcher) {
|
||||
libkos::println!("init: STOP ({})", why);
|
||||
idle();
|
||||
}
|
||||
|
||||
libkos::println!("init: all loaded, serving spawns");
|
||||
@@ -80,12 +86,7 @@ fn launch_all(launcher: &mut launch::Launcher, programs: &[manifest::Program]) {
|
||||
for p in programs {
|
||||
libkos::print!("init: loading {}... ", p.name);
|
||||
match launcher.launch(p) {
|
||||
Ok(note) => libkos::println!("ok{}", note),
|
||||
// Without the owner, nothing that touches user data may start.
|
||||
Err(why) if p.unlocks => {
|
||||
libkos::println!("STOP ({})", why);
|
||||
idle();
|
||||
}
|
||||
Ok(started) => libkos::println!("ok{}", started.note),
|
||||
Err(why) => libkos::println!("SKIP ({})", why),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@ use types::budget::Limits;
|
||||
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
const USER_CODE_BASE: usize = 0x10000;
|
||||
const USER_STACK_PAGES: usize = 4;
|
||||
/// A program's stack, below its image, unless its manifest says more.
|
||||
pub(crate) const STACK_PAGES: usize = 4;
|
||||
/// The most there is room for under the image (page 0 stays unmapped).
|
||||
const MAX_STACK_PAGES: usize = USER_CODE_BASE / PAGE_SIZE - 1;
|
||||
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
||||
/// Where a program file is read to before it is loaded (16 pages: the
|
||||
/// largest program init will load).
|
||||
const SCRATCH_VA: VirtualAddress = VirtualAddress::from_raw(0x30000);
|
||||
const SCRATCH_PAGES: u16 = 16;
|
||||
/// Where a program file is read to before it is loaded (32 pages: the
|
||||
/// largest program init will load; the gatekeeper is past 16).
|
||||
const SCRATCH_VA: VirtualAddress = VirtualAddress::from_raw(0x0120_0000);
|
||||
const SCRATCH_PAGES: u16 = 32;
|
||||
|
||||
pub(crate) struct LoadedProcess {
|
||||
pub(crate) process_cap: CapSlot,
|
||||
@@ -35,7 +38,9 @@ pub(crate) fn load_binary(
|
||||
scratch: &mut [u8],
|
||||
filename: &[u8],
|
||||
limits: Limits,
|
||||
stack_pages: usize,
|
||||
) -> Option<LoadedProcess> {
|
||||
let stack_pages = stack_pages.min(MAX_STACK_PAGES);
|
||||
let file_size = read_file(storage, scratch, filename)?;
|
||||
let elf_data = &scratch[..file_size];
|
||||
let elf_info = match elf::parse(elf_data) {
|
||||
@@ -71,10 +76,10 @@ pub(crate) fn load_binary(
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..USER_STACK_PAGES {
|
||||
for i in 0..stack_pages {
|
||||
let page_count = PageCount::new(1)?;
|
||||
let stack_va = syscall::request_pages(page_count)?;
|
||||
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (USER_STACK_PAGES - i) * PAGE_SIZE);
|
||||
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (stack_pages - i) * PAGE_SIZE);
|
||||
syscall::map_page(process_cap, stack_va, dst, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,35 +12,45 @@ pub enum Device {
|
||||
}
|
||||
|
||||
/// A storage grant: a folder (or the whole disk, `b""`) and whether it may
|
||||
/// be changed.
|
||||
/// be changed. With `user`, the path is inside the signed-in user's folder.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Scope {
|
||||
pub path: &'static [u8],
|
||||
pub write: bool,
|
||||
pub user: bool,
|
||||
}
|
||||
|
||||
const fn disk(path: &'static [u8], write: bool) -> Scope {
|
||||
Scope { path, write, user: false }
|
||||
}
|
||||
|
||||
const fn users(path: &'static [u8], write: bool) -> Scope {
|
||||
Scope { path, write, user: true }
|
||||
}
|
||||
|
||||
pub struct Program {
|
||||
pub name: &'static str,
|
||||
pub file: &'static [u8],
|
||||
pub limits: Limits,
|
||||
/// Its stack, in pages.
|
||||
pub stack_pages: usize,
|
||||
/// Serves an endpoint, which it finds in slot 0.
|
||||
pub serves: bool,
|
||||
/// Handed over in this order, after it starts.
|
||||
pub devices: &'static [Device],
|
||||
/// Authenticates the owner: init waits for the master key before
|
||||
/// starting anything that touches user data.
|
||||
pub unlocks: bool,
|
||||
/// Earlier programs' endpoints it may call, in its next slots; each
|
||||
/// program's copy carries its own badge, so a server can tell them apart.
|
||||
pub uses: &'static [&'static str],
|
||||
/// Storage it may reach, in the slot after `uses`.
|
||||
pub storage: Option<Scope>,
|
||||
/// Storage it may reach, in the slots after `uses`.
|
||||
pub storage: &'static [Scope],
|
||||
/// Takes the console over from init; everyone else prints through it.
|
||||
pub console: bool,
|
||||
/// May have init spawn programs, whose limits together stay within this.
|
||||
pub spawns: Option<Limits>,
|
||||
/// May halt the machine.
|
||||
pub system_control: bool,
|
||||
/// May sign the user out (`types::spawn::SIGN_OUT`), in the last slot.
|
||||
pub signs_out: bool,
|
||||
}
|
||||
|
||||
/// Enough for a small program: its image, stack and a little heap.
|
||||
@@ -56,14 +66,15 @@ const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Prog
|
||||
name,
|
||||
file,
|
||||
limits,
|
||||
stack_pages: crate::load::STACK_PAGES,
|
||||
serves: true,
|
||||
devices: &[],
|
||||
unlocks: false,
|
||||
uses: &[],
|
||||
storage: None,
|
||||
storage: &[],
|
||||
console: false,
|
||||
spawns: None,
|
||||
system_control: false,
|
||||
signs_out: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,46 +91,52 @@ pub const SERVERS: &[Program] = &[
|
||||
},
|
||||
// Text on the display, lines from the keyboard (it takes input focus).
|
||||
Program { uses: &["display"], ..server("terminal", b"bin/terminal", SMALL) },
|
||||
// Nothing that touches user data starts before the owner is in. It
|
||||
// holds the screen (through the terminal), its keystore and randomness,
|
||||
// and exits once it has handed over the key.
|
||||
Program {
|
||||
uses: &["terminal"],
|
||||
devices: &[Device::Keystore, Device::Random],
|
||||
unlocks: true,
|
||||
// argon2's 16 MiB, its image and a little more.
|
||||
..server("gatekeeper", b"bin/gatekeeper", Limits { pages: 4096 + 160, regions: 6, ..SMALL })
|
||||
},
|
||||
// The only one that equips apps (docs/apps.md): it reaches every app's
|
||||
// files and the owner's decisions, and runs apps through init.
|
||||
Program {
|
||||
uses: &["terminal", "display"],
|
||||
storage: Some(Scope { path: b"", write: true }),
|
||||
// Room for a few apps at a time.
|
||||
spawns: Some(Limits { pages: 512, endpoints: 8, notifications: 8, regions: 8 }),
|
||||
..server("permission", b"bin/permission", Limits { pages: 96, ..SMALL })
|
||||
},
|
||||
server("session", b"bin/session", SMALL),
|
||||
];
|
||||
|
||||
/// Started for each sign-in, before anything that touches a user's files.
|
||||
/// It holds the screen (through the terminal), its keystore and randomness,
|
||||
/// and exits once it has handed over who is in and their key.
|
||||
pub const GATEKEEPER: Program = Program {
|
||||
uses: &["terminal"],
|
||||
devices: &[Device::Keystore, Device::Random],
|
||||
// A user's record is KiBs, and it handles a few at once.
|
||||
stack_pages: 12,
|
||||
// argon2's 16 MiB, its image and a little more.
|
||||
..server("gatekeeper", b"bin/gatekeeper", Limits { pages: 4096 + 160, regions: 6, ..SMALL })
|
||||
};
|
||||
|
||||
/// Started for each signed-in user, and stopped when they sign out. The
|
||||
/// permission manager is the only one that equips apps (docs/apps.md): it
|
||||
/// reaches the user's apps, their data and decisions, and the system's apps
|
||||
/// (read-only), and runs apps through init.
|
||||
pub const SESSION: &[Program] = &[Program {
|
||||
uses: &["terminal", "display"],
|
||||
storage: &[users(b"", true), disk(b"apps", false)],
|
||||
// Room for a few apps at a time.
|
||||
spawns: Some(Limits { pages: 512, endpoints: 8, notifications: 8, regions: 8 }),
|
||||
..server("permission", b"bin/permission", Limits { pages: 96, ..SMALL })
|
||||
}];
|
||||
|
||||
/// Run on the test image after the SMP self-test, instead of the desktop:
|
||||
/// spawns through init, then reports and halts.
|
||||
pub const TESTS: &[Program] = &[Program {
|
||||
serves: false,
|
||||
// The permission manager, to run an app the way the shell does.
|
||||
uses: &["permission"],
|
||||
storage: Some(Scope { path: b"", write: true }),
|
||||
storage: &[disk(b"", true), users(b"home", true)],
|
||||
spawns: Some(Limits { pages: 128, endpoints: 2, notifications: 2, regions: 2 }),
|
||||
system_control: true,
|
||||
..server("spawntest", b"bin/spawntest", SMALL)
|
||||
}];
|
||||
|
||||
/// The desktop; the test image runs the SMP self-test instead. The shell
|
||||
/// holds the owner's files and asks the permission manager to run apps;
|
||||
/// it holds nothing to give them beyond that.
|
||||
/// holds the user's files and asks the permission manager to run apps;
|
||||
/// it holds nothing to give them beyond that, and may sign the user out.
|
||||
pub const DESKTOP: &[Program] = &[Program {
|
||||
serves: false,
|
||||
uses: &["terminal", "permission"],
|
||||
storage: Some(Scope { path: b"home", write: true }),
|
||||
storage: &[users(b"home", true)],
|
||||
signs_out: true,
|
||||
..server("shell", b"bin/shell", SMALL)
|
||||
}];
|
||||
|
||||
@@ -39,7 +39,7 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot, words
|
||||
for w in words {
|
||||
w.store(0, Ordering::SeqCst);
|
||||
}
|
||||
let spinner = crate::load::load_binary(storage, scratch, b"bin/smptest", crate::manifest::SMALL).ok_or("cannot load bin/smptest")?;
|
||||
let spinner = crate::load::load_binary(storage, scratch, b"bin/smptest", crate::manifest::SMALL, crate::load::STACK_PAGES).ok_or("cannot load bin/smptest")?;
|
||||
let rw = PermissionSet::empty()
|
||||
.with(Permission::Read)
|
||||
.with(Permission::Write)
|
||||
|
||||
163
crates/init/src/session.rs
Normal file
163
crates/init/src/session.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
//! Signing in and out (see the vision's Users note). The gatekeeper says
|
||||
//! who is in; init clears away what must go (everyone's files on a new
|
||||
//! keystore, a removed user's, the last guest's), hands storage the user
|
||||
//! and their key, readies their folder and starts their session. Signing
|
||||
//! out stops everything the session ran, locks storage, and the gatekeeper
|
||||
//! runs again.
|
||||
|
||||
use libkos::fs::Storage;
|
||||
use libkos::syscall;
|
||||
use types::bounded::CapSlot;
|
||||
use types::gatekeeper::{KEY, NAME, REMOVED, SEED, UNLOCK, reply, unlocked};
|
||||
use types::user::UserFolder;
|
||||
|
||||
use crate::launch::Launcher;
|
||||
use crate::manifest::{GATEKEEPER, SESSION};
|
||||
|
||||
/// Users a keystore holds, so removed at one sign-in at most.
|
||||
const MAX_REMOVED: usize = 8;
|
||||
const LONGEST_NAME: usize = 16;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct User {
|
||||
pub(crate) folder: UserFolder,
|
||||
pub(crate) guest: bool,
|
||||
}
|
||||
|
||||
/// All the gatekeeper hands over.
|
||||
struct Handover {
|
||||
flags: usize,
|
||||
id: u64,
|
||||
key: [usize; 4],
|
||||
seed: [usize; 4],
|
||||
name: [u8; LONGEST_NAME],
|
||||
name_len: usize,
|
||||
removed: [Option<u64>; MAX_REMOVED],
|
||||
}
|
||||
|
||||
impl Drop for Handover {
|
||||
fn drop(&mut self) {
|
||||
self.key.fill(0);
|
||||
self.seed.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits for the gatekeeper behind `ep` to let someone in.
|
||||
fn ask(ep: CapSlot) -> Result<Handover, &'static str> {
|
||||
let [reply::OK, flags, id, _role] = syscall::request(ep, [UNLOCK, 0, 0, 0]).map_err(|_| "the gatekeeper is gone")? else {
|
||||
return Err("the gatekeeper could not unlock");
|
||||
};
|
||||
let mut h = Handover { flags, id: id as u64, key: [0; 4], seed: [0; 4], name: [0; LONGEST_NAME], name_len: 0, removed: [None; MAX_REMOVED] };
|
||||
h.key = syscall::request(ep, [KEY, 0, 0, 0]).map_err(|_| "the gatekeeper gave no key")?;
|
||||
h.seed = syscall::request(ep, [SEED, 0, 0, 0]).map_err(|_| "the gatekeeper gave no seed")?;
|
||||
let name = syscall::request(ep, [NAME, 0, 0, 0]).map_err(|_| "the gatekeeper gave no name")?;
|
||||
if let Some((text, len)) = types::log::text_of(name) {
|
||||
h.name_len = len.min(LONGEST_NAME);
|
||||
h.name[..h.name_len].copy_from_slice(&text[..h.name_len]);
|
||||
}
|
||||
for slot in h.removed.iter_mut() {
|
||||
match syscall::request(ep, [REMOVED, 0, 0, 0]) {
|
||||
Ok([reply::OK, id, ..]) => *slot = Some(id as u64),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
fn user_path(folder: UserFolder, buf: &mut [u8; 32]) -> &[u8] {
|
||||
buf[..6].copy_from_slice(b"users/");
|
||||
let f = folder.as_bytes();
|
||||
buf[6..6 + f.len()].copy_from_slice(f);
|
||||
&buf[..6 + f.len()]
|
||||
}
|
||||
|
||||
/// Removes `folder`'s files; none there is fine.
|
||||
fn purge(fs: &mut Storage, folder: UserFolder) -> Result<(), &'static str> {
|
||||
fs.purge(user_path(folder, &mut [0; 32])).map_err(|_| "cannot remove a user's files")
|
||||
}
|
||||
|
||||
/// Runs the gatekeeper until someone is in and starts their session.
|
||||
pub(crate) fn sign_in(l: &mut Launcher) -> Result<(), &'static str> {
|
||||
libkos::print!("init: loading gatekeeper... ");
|
||||
let gatekeeper = l.launch(&GATEKEEPER)?;
|
||||
libkos::println!("ok{}", gatekeeper.note);
|
||||
let handover = gatekeeper.ep.ok_or("the gatekeeper serves nothing").and_then(ask);
|
||||
// It exits on its own once it has handed over.
|
||||
syscall::drop_cap(gatekeeper.process);
|
||||
let h = handover?;
|
||||
let guest = h.flags & unlocked::GUEST != 0;
|
||||
let folder = if guest { UserFolder::GUEST } else { UserFolder::of(h.id) };
|
||||
let (fs, scratch) = l.storage();
|
||||
// While storage is locked: it removes trees only then.
|
||||
if h.flags & unlocked::FRESH != 0 {
|
||||
fs.purge(b"users").map_err(|_| "cannot clear the users of an old keystore")?;
|
||||
libkos::println!("init: a new keystore, every earlier user's files removed");
|
||||
}
|
||||
for id in h.removed.iter().flatten() {
|
||||
purge(fs, UserFolder::of(*id))?;
|
||||
libkos::println!("init: a removed user's files removed");
|
||||
}
|
||||
if guest {
|
||||
purge(fs, UserFolder::GUEST)?;
|
||||
}
|
||||
let handed = fs.set_user(h.id, guest).is_ok()
|
||||
&& [(false, h.key), (true, h.seed)].iter().all(|(seed, w)| (0..2).all(|half| fs.set_half(*seed, half, [w[half * 2], w[half * 2 + 1]]).is_ok()));
|
||||
if !handed {
|
||||
return Err("storage would not take the key");
|
||||
}
|
||||
crate::factory::prepare(fs, folder, scratch)?;
|
||||
let name = core::str::from_utf8(&h.name[..h.name_len]).unwrap_or("someone");
|
||||
libkos::println!("init: {} is in", name);
|
||||
l.user = Some(User { folder, guest });
|
||||
start_session(l);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Starts what runs for a signed-in user, keeping each process to stop
|
||||
/// at sign-out.
|
||||
fn start_session(l: &mut Launcher) {
|
||||
let desktop = l.desktop;
|
||||
for p in SESSION.iter().chain(desktop) {
|
||||
libkos::print!("init: loading {}... ", p.name);
|
||||
match l.launch(p) {
|
||||
Ok(started) => {
|
||||
libkos::println!("ok{}", started.note);
|
||||
match l.running.iter_mut().find(|r| r.is_none()) {
|
||||
Some(free) => *free = Some(started.process),
|
||||
None => {
|
||||
// Untracked, it could not be stopped at sign-out.
|
||||
let _ = syscall::kill(started.process);
|
||||
syscall::drop_cap(started.process);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => libkos::println!("SKIP ({})", why),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the session, locks storage, and lets the next one in.
|
||||
pub(crate) fn sign_out(l: &mut Launcher) {
|
||||
l.spawner.clear();
|
||||
for process in l.running.iter_mut().filter_map(Option::take) {
|
||||
let _ = syscall::kill(process);
|
||||
syscall::drop_cap(process);
|
||||
}
|
||||
if let Some(terminal) = l.served("terminal") {
|
||||
let _ = syscall::request(terminal, [types::terminal::SIGNED_OUT, 0, 0, 0]);
|
||||
}
|
||||
let user = l.user.take();
|
||||
let (fs, _) = l.storage();
|
||||
let locked = fs.lock().is_ok();
|
||||
// A guest leaves nothing behind.
|
||||
let cleared = !user.is_some_and(|u| u.guest) || purge(fs, UserFolder::GUEST).is_ok();
|
||||
libkos::println!("init: signed out");
|
||||
let next = match locked && cleared {
|
||||
true => sign_in(l),
|
||||
false => Err("storage would not lock"),
|
||||
};
|
||||
if let Err(why) = next {
|
||||
libkos::println!("init: STOP ({})", why);
|
||||
crate::idle();
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,17 @@ impl Spawner {
|
||||
}
|
||||
}
|
||||
|
||||
impl Spawner {
|
||||
/// Sign-out: every client goes, and every child is stopped.
|
||||
pub(crate) fn clear(&mut self) {
|
||||
for child in self.children.iter_mut().filter_map(Option::take) {
|
||||
let _ = syscall::kill(child.process);
|
||||
syscall::drop_cap(child.process);
|
||||
}
|
||||
self.allowances = [None; MAX_CLIENTS];
|
||||
}
|
||||
}
|
||||
|
||||
/// Where init maps a spawn request's file grant while it reads it.
|
||||
const PROGRAM_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x01A0_0000);
|
||||
|
||||
@@ -115,7 +126,7 @@ impl Launcher<'_> {
|
||||
// The grant is the file: its path inside it is empty.
|
||||
let found = file.open(b"").and_then(|h| file.close(h)).is_ok();
|
||||
let (_, scratch) = self.storage();
|
||||
let loaded = found.then(|| load::load_binary(&mut file, scratch, b"", r.limits)).flatten();
|
||||
let loaded = found.then(|| load::load_binary(&mut file, scratch, b"", r.limits, load::STACK_PAGES)).flatten();
|
||||
file.detach();
|
||||
let Some(loaded) = loaded else {
|
||||
return refused(if found { reply::ERROR } else { reply::NOT_FOUND });
|
||||
@@ -170,6 +181,12 @@ impl Server for Launcher<'_> {
|
||||
}
|
||||
|
||||
fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<libkos::syscall::Give>) {
|
||||
if badge == types::spawn::SESSION_BADGE {
|
||||
if words[0] == types::spawn::SIGN_OUT {
|
||||
crate::session::sign_out(self);
|
||||
}
|
||||
return ([MALFORMED, 0, 0, 0], None);
|
||||
}
|
||||
match (Request::decode(words), cap) {
|
||||
(Some(r), Some(binary)) => {
|
||||
let (reply, process) = self.spawn(badge, &r, binary);
|
||||
|
||||
Reference in New Issue
Block a user