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:
@@ -22,18 +22,20 @@ use types::address::VirtualAddress;
|
||||
use types::bounded::CapSlot;
|
||||
|
||||
/// Where init's manifest put them: its own endpoint, then `uses`, then
|
||||
/// storage, then the spawn endpoint.
|
||||
/// storage (the user's folder, the system's apps), then the spawn endpoint.
|
||||
const EP: CapSlot = CapSlot::const_new(0);
|
||||
const TERMINAL: CapSlot = CapSlot::const_new(1);
|
||||
const DISPLAY: CapSlot = CapSlot::const_new(2);
|
||||
const STORAGE: CapSlot = CapSlot::const_new(3);
|
||||
const INIT: CapSlot = CapSlot::const_new(4);
|
||||
const USER: CapSlot = CapSlot::const_new(3);
|
||||
const SYSTEM: CapSlot = CapSlot::const_new(4);
|
||||
const INIT: CapSlot = CapSlot::const_new(5);
|
||||
|
||||
const STORAGE_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x0180_0000);
|
||||
const USER_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x0180_0000);
|
||||
const SOURCE_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x0190_0000);
|
||||
const SYSTEM_BUF_VA: VirtualAddress = VirtualAddress::from_raw(0x01B0_0000);
|
||||
|
||||
pub fn run() -> ! {
|
||||
let Ok(fs) = Storage::attach(STORAGE, STORAGE_BUF_VA) else {
|
||||
let (Ok(user), Ok(system)) = (Storage::attach(USER, USER_BUF_VA), Storage::attach(SYSTEM, SYSTEM_BUF_VA)) else {
|
||||
libkos::println!("permission: no storage, exiting");
|
||||
syscall::exit();
|
||||
};
|
||||
@@ -41,7 +43,7 @@ pub fn run() -> ! {
|
||||
libkos::println!("permission: no notification, exiting");
|
||||
syscall::exit();
|
||||
};
|
||||
let store = store::DiskStore::new(fs, SOURCE_BUF_VA);
|
||||
let store = store::DiskStore::new(user, system, SOURCE_BUF_VA);
|
||||
let consent = consent::TerminalConsent { terminal: TERMINAL };
|
||||
let mut srv = server::PermissionServer::new(store, consent, INIT, Some(DISPLAY), deaths);
|
||||
libkos::println!("permission: serving");
|
||||
|
||||
@@ -19,7 +19,7 @@ use types::start_arg::StartArg;
|
||||
|
||||
use crate::consent::{CapabilityNames, TerminalConsent, version};
|
||||
use crate::policy::{self, Refusal, Store};
|
||||
use crate::store::{self, DiskStore, PATH_BUF};
|
||||
use crate::store::DiskStore;
|
||||
|
||||
/// What each app gets to run in, out of the permission manager's allowance.
|
||||
const PROGRAM: Limits = Limits { pages: 128, endpoints: 1, notifications: 1, regions: 1 };
|
||||
@@ -164,9 +164,7 @@ impl PermissionServer {
|
||||
let plan = policy::plan_launch(&mut self.store, command, available).map_err(refused)?;
|
||||
let index = self.running.iter().position(Option::is_none).ok_or(code(reply::BUSY))?;
|
||||
|
||||
let mut p = [0u8; PATH_BUF];
|
||||
let binary = store::binary_path(&mut p, &plan.app).ok_or(code(reply::FAILED))?;
|
||||
let file = self.store.storage().grant(binary, false).map_err(|_| code(reply::FAILED))?;
|
||||
let file = self.store.binary_grant(&plan.app).ok_or(code(reply::FAILED))?;
|
||||
let spawned = libkos::spawn::spawn(self.init, file, PROGRAM);
|
||||
let _ = libkos::fs::release(file);
|
||||
let child = spawned.map_err(|e| match e {
|
||||
@@ -176,7 +174,7 @@ impl PermissionServer {
|
||||
|
||||
let call = PermissionSet::empty().with(Permission::Execute);
|
||||
let data = match plan.granted.contains(Capability::Storage) {
|
||||
true => store::data_path(&mut p, &plan.app.id).and_then(|d| self.store.storage().grant(d, true).ok()),
|
||||
true => self.store.data_grant(&plan.app.id),
|
||||
false => None,
|
||||
};
|
||||
let staged = (badge == self.staged.badge).then_some(&self.staged);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! The `Store` port on the storage server: apps in `/apps/<id>/`, their
|
||||
//! data in `/data/<id>/`, the owner's decisions in `/system/permissions/`.
|
||||
//! The `Store` port on the storage server, through two grants: the
|
||||
//! signed-in user's folder (their apps in `apps/<id>/`, each app's data in
|
||||
//! `data/<id>/`, their decisions in `permissions/`) and, read-only, the
|
||||
//! system image's apps (`/apps/<id>/`), which every user shares.
|
||||
|
||||
use libkos::fs::Storage;
|
||||
use manifest::{AppId, Decision, MAX_DECISION_TEXT, Manifest};
|
||||
@@ -10,18 +12,18 @@ use crate::policy::{MAX_MANIFEST, Store};
|
||||
|
||||
const APPS: &str = "apps";
|
||||
const DATA: &str = "data";
|
||||
const DECISIONS: &str = "system/permissions";
|
||||
const DECISIONS: &str = "permissions";
|
||||
const MANIFEST: &str = "manifest.toml";
|
||||
/// Apps `find` looks through.
|
||||
pub const MAX_APPS: usize = 32;
|
||||
const LONGEST_PATH: usize = 160;
|
||||
const COPY_CHUNK: usize = 4096;
|
||||
|
||||
/// `parts` joined with `/`, in `buf`.
|
||||
/// `parts` (the empty ones skipped) joined with `/`, in `buf`.
|
||||
fn path<'a>(buf: &'a mut [u8; LONGEST_PATH], parts: &[&str]) -> Option<&'a [u8]> {
|
||||
let mut len = 0;
|
||||
for (i, p) in parts.iter().enumerate() {
|
||||
let sep = usize::from(i > 0);
|
||||
for p in parts.iter().filter(|p| !p.is_empty()) {
|
||||
let sep = usize::from(len > 0);
|
||||
let end = len + sep + p.len();
|
||||
let room = buf.get_mut(len..end)?;
|
||||
room[..sep].copy_from_slice(&b"/"[..sep]);
|
||||
@@ -63,29 +65,69 @@ fn copy(src: &mut Storage, from: &[u8], dst: &mut Storage, to: &[u8]) -> bool {
|
||||
ok
|
||||
}
|
||||
|
||||
/// Where an app is installed: on the system image, or by the user.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Shelf {
|
||||
System,
|
||||
User,
|
||||
}
|
||||
|
||||
pub struct DiskStore {
|
||||
fs: Storage,
|
||||
/// The signed-in user's folder.
|
||||
user: Storage,
|
||||
/// The system image's apps, read-only.
|
||||
system: Storage,
|
||||
/// Where an offered app folder's grant is mapped while it is read.
|
||||
source_va: VirtualAddress,
|
||||
}
|
||||
|
||||
impl DiskStore {
|
||||
pub fn new(fs: Storage, source_va: VirtualAddress) -> Self {
|
||||
Self { fs, source_va }
|
||||
pub fn new(user: Storage, system: Storage, source_va: VirtualAddress) -> Self {
|
||||
Self { user, system, source_va }
|
||||
}
|
||||
|
||||
/// Storage itself, for grants on the app's behalf.
|
||||
pub fn storage(&mut self) -> &mut Storage {
|
||||
&mut self.fs
|
||||
/// The storage holding `shelf`, and its apps' folder in it.
|
||||
fn on(&mut self, shelf: Shelf) -> (&mut Storage, &'static str) {
|
||||
match shelf {
|
||||
Shelf::System => (&mut self.system, ""),
|
||||
Shelf::User => (&mut self.user, APPS),
|
||||
}
|
||||
}
|
||||
|
||||
/// The ids of the installed apps (folder names in `/apps`).
|
||||
/// The ids of the installed apps (folder names), the system's first.
|
||||
pub fn ids(&mut self, mut each: impl FnMut(AppId)) {
|
||||
let _ = self.fs.list_dir(APPS.as_bytes(), |e| {
|
||||
if let Some(id) = core::str::from_utf8(e.name()).ok().and_then(AppId::new) {
|
||||
each(id);
|
||||
}
|
||||
});
|
||||
for shelf in [Shelf::System, Shelf::User] {
|
||||
let (fs, apps) = self.on(shelf);
|
||||
let _ = fs.list_dir(apps.as_bytes(), |e| {
|
||||
if let Some(id) = core::str::from_utf8(e.name()).ok().and_then(AppId::new) {
|
||||
each(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Which shelf holds `id`'s manifest; the system's wins, so a user
|
||||
/// cannot stand in for a system app.
|
||||
fn shelf(&mut self, id: &AppId) -> Option<Shelf> {
|
||||
[Shelf::System, Shelf::User].into_iter().find(|shelf| {
|
||||
let (fs, apps) = self.on(*shelf);
|
||||
let mut p = [0u8; LONGEST_PATH];
|
||||
path(&mut p, &[apps, id.as_str(), MANIFEST]).is_some_and(|p| fs.open(p).and_then(|h| fs.close(h)).is_ok())
|
||||
})
|
||||
}
|
||||
|
||||
/// A read-only grant to `app`'s binary, for init to load it from.
|
||||
pub fn binary_grant(&mut self, app: &Manifest) -> Option<CapSlot> {
|
||||
let shelf = self.shelf(&app.id)?;
|
||||
let (fs, apps) = self.on(shelf);
|
||||
let mut p = [0u8; LONGEST_PATH];
|
||||
fs.grant(path(&mut p, &[apps, app.id.as_str(), app.binary.as_str()])?, false).ok()
|
||||
}
|
||||
|
||||
/// A read-write grant to `id`'s own data folder, for the app.
|
||||
pub fn data_grant(&mut self, id: &AppId) -> Option<CapSlot> {
|
||||
let mut p = [0u8; LONGEST_PATH];
|
||||
self.user.grant(path(&mut p, &[DATA, id.as_str()])?, true).ok()
|
||||
}
|
||||
|
||||
fn with_source<T>(&self, source: CapSlot, f: impl FnOnce(&mut Storage) -> T) -> Option<T> {
|
||||
@@ -108,9 +150,7 @@ impl Store for DiskStore {
|
||||
}
|
||||
|
||||
fn is_installed(&mut self, id: &AppId) -> bool {
|
||||
let mut p = [0u8; LONGEST_PATH];
|
||||
let Some(p) = path(&mut p, &[APPS, id.as_str(), MANIFEST]) else { return false };
|
||||
self.fs.open(p).and_then(|h| self.fs.close(h)).is_ok()
|
||||
self.shelf(id).is_some()
|
||||
}
|
||||
|
||||
fn install(&mut self, source: &CapSlot, app: &Manifest, data_folder: bool) -> bool {
|
||||
@@ -125,8 +165,8 @@ impl Store for DiskStore {
|
||||
};
|
||||
// There already if an earlier attempt failed part way; an app
|
||||
// counts as installed only once its manifest is in.
|
||||
let _ = self.fs.mkdir(folder);
|
||||
let fs = &mut self.fs;
|
||||
let _ = self.user.mkdir(folder);
|
||||
let fs = &mut self.user;
|
||||
let copied = Storage::attach(*source, self.source_va).ok().map(|mut src| {
|
||||
// The binary first: an app folder with a manifest is installed.
|
||||
let ok = copy(&mut src, app.binary.as_str().as_bytes(), fs, to_binary)
|
||||
@@ -139,14 +179,16 @@ impl Store for DiskStore {
|
||||
}
|
||||
let mut data = [0u8; LONGEST_PATH];
|
||||
// Kept from an earlier install if there is one: the app's data.
|
||||
!data_folder || path(&mut data, &[DATA, id]).is_some_and(|d| self.fs.mkdir(d).is_ok() || self.fs.list_dir(d, |_| {}).is_ok())
|
||||
!data_folder || path(&mut data, &[DATA, id]).is_some_and(|d| self.user.mkdir(d).is_ok() || self.user.list_dir(d, |_| {}).is_ok())
|
||||
}
|
||||
|
||||
fn manifest(&mut self, id: &AppId) -> Option<Manifest> {
|
||||
let shelf = self.shelf(id)?;
|
||||
let (fs, apps) = self.on(shelf);
|
||||
let mut p = [0u8; LONGEST_PATH];
|
||||
let p = path(&mut p, &[APPS, id.as_str(), MANIFEST])?;
|
||||
let p = path(&mut p, &[apps, id.as_str(), MANIFEST])?;
|
||||
let mut text = [0u8; MAX_MANIFEST];
|
||||
let m = Manifest::parse(read_text(&mut self.fs, p, &mut text)?).ok()?;
|
||||
let m = Manifest::parse(read_text(fs, p, &mut text)?).ok()?;
|
||||
// A manifest in the wrong folder is no app.
|
||||
(m.id == *id).then_some(m)
|
||||
}
|
||||
@@ -167,7 +209,7 @@ impl Store for DiskStore {
|
||||
let mut p = [0u8; LONGEST_PATH];
|
||||
let p = path(&mut p, &[DECISIONS, id.as_str()])?;
|
||||
let mut text = [0u8; MAX_DECISION_TEXT];
|
||||
Decision::parse(read_text(&mut self.fs, p, &mut text)?).ok()
|
||||
Decision::parse(read_text(&mut self.user, p, &mut text)?).ok()
|
||||
}
|
||||
|
||||
fn record(&mut self, id: &AppId, decision: &Decision) -> bool {
|
||||
@@ -176,21 +218,10 @@ impl Store for DiskStore {
|
||||
let mut text = [0u8; MAX_DECISION_TEXT];
|
||||
let n = decision.write(&mut text);
|
||||
// Replaced whole: there is no truncate.
|
||||
let _ = self.fs.delete(p);
|
||||
let Ok(h) = self.fs.create(p) else { return false };
|
||||
let written = self.fs.write(h, &text[..n]) == Ok(n);
|
||||
let _ = self.fs.close(h);
|
||||
let _ = self.user.delete(p);
|
||||
let Ok(h) = self.user.create(p) else { return false };
|
||||
let written = self.user.write(h, &text[..n]) == Ok(n);
|
||||
let _ = self.user.close(h);
|
||||
written
|
||||
}
|
||||
}
|
||||
|
||||
/// The path of app `id`'s binary and its data folder, for grants.
|
||||
pub fn binary_path<'a>(buf: &'a mut [u8; LONGEST_PATH], app: &Manifest) -> Option<&'a [u8]> {
|
||||
path(buf, &[APPS, app.id.as_str(), app.binary.as_str()])
|
||||
}
|
||||
|
||||
pub fn data_path<'a>(buf: &'a mut [u8; LONGEST_PATH], id: &AppId) -> Option<&'a [u8]> {
|
||||
path(buf, &[DATA, id.as_str()])
|
||||
}
|
||||
|
||||
pub const PATH_BUF: usize = LONGEST_PATH;
|
||||
|
||||
Reference in New Issue
Block a user