fix(userspace): clip display rects, bounds-check session app ids and fat16 handles, reject bad fat16 geometry, session kills via process cap

This commit is contained in:
2026-10-27 12:11:13 +01:00
parent e8a2963b24
commit 8bc5402a29
7 changed files with 426 additions and 109 deletions

View File

@@ -8,9 +8,10 @@ pub mod server;
mod tests;
pub fn run() -> ! {
let create_cap = types::bounded::CapSlot::new(0).unwrap();
let mut srv = server::SessionServer::new(create_cap);
srv.register_app("snake", "/apps/snake.elf");
// Slot 0 holds the endpoint init delegated. Init has no process
// creation authority to hand on yet, so Launch reports an error.
let mut srv = server::SessionServer::new(None);
let _ = srv.register_app("snake", "/apps/snake.elf");
let ep = types::bounded::CapSlot::new(0).unwrap();
server_framework::serve(&mut srv, ep)
}

View File

@@ -1,6 +1,5 @@
use server_framework::Server;
use types::bounded::CapSlot;
use types::id::ProcessId;
use crate::protocol::{AppId, SessionRequest, SessionResponse};
@@ -15,8 +14,8 @@ pub struct AppEntry {
#[derive(Clone, Copy)]
struct RunningApp {
app: AppId,
pid: ProcessId,
/// The capability `Kill` is invoked on.
process: CapSlot,
}
pub struct SessionServer {
@@ -24,11 +23,12 @@ pub struct SessionServer {
app_count: usize,
running: [Option<RunningApp>; MAX_APPS],
active: Option<AppId>,
create_cap: CapSlot,
/// Lets the server create processes; without one, Launch fails.
create_cap: Option<CapSlot>,
}
impl SessionServer {
pub fn new(create_cap: CapSlot) -> Self {
pub fn new(create_cap: Option<CapSlot>) -> Self {
Self {
apps: [const { None }; MAX_APPS],
app_count: 0,
@@ -38,11 +38,17 @@ impl SessionServer {
}
}
pub fn register_app(&mut self, name: &'static str, path: &'static str) -> AppId {
/// Adds an app to the menu; `None` once the table is full.
pub fn register_app(&mut self, name: &'static str, path: &'static str) -> Option<AppId> {
let slot = self.apps.get_mut(self.app_count)?;
*slot = Some(AppEntry {
name,
path,
registered: true,
});
let id = AppId::new(self.app_count as u16);
self.apps[self.app_count] = Some(AppEntry { name, path, registered: true });
self.app_count += 1;
id
Some(id)
}
pub fn app_count(&self) -> usize {
@@ -53,8 +59,10 @@ impl SessionServer {
self.active
}
fn is_running(&self, app: AppId) -> bool {
self.running[app.raw() as usize].is_some()
/// The running-table entry for `app`; `None` for ids past the table,
/// which come straight from clients.
fn running_mut(&mut self, app: AppId) -> Option<&mut Option<RunningApp>> {
self.running.get_mut(app.raw() as usize)
}
fn launch_app(&mut self, app: AppId) -> SessionResponse {
@@ -63,31 +71,27 @@ impl SessionServer {
return SessionResponse::NotFound;
}
if self.is_running(app) {
if self.apps.get(idx).is_none_or(Option::is_none) {
return SessionResponse::NotFound;
}
if self.running_mut(app).is_none_or(|r| r.is_some()) {
return SessionResponse::Error;
}
let _app_entry = match &self.apps[idx] {
Some(entry) => entry,
None => return SessionResponse::NotFound,
let Some(process) = self.create_cap.and_then(libkos::syscall::create_process) else {
return SessionResponse::Error;
};
let process_cap = match libkos::syscall::create_process(self.create_cap) {
Some(cap) => cap,
None => return SessionResponse::Error,
};
let pid = ProcessId::from_raw(process_cap.value() as u16);
self.running[idx] = Some(RunningApp { app, pid });
if let Some(r) = self.running_mut(app) {
*r = Some(RunningApp { process });
}
self.active = Some(app);
SessionResponse::Launched { app }
}
fn kill_app(&mut self, app: AppId) -> SessionResponse {
let idx = app.raw() as usize;
match self.running[idx].take() {
Some(_running) => {
match self.running_mut(app).and_then(Option::take) {
Some(running) => {
let _ = libkos::syscall::kill(running.process);
if self.active == Some(app) {
self.active = None;
}
@@ -106,7 +110,9 @@ impl Server for SessionServer {
match request {
SessionRequest::Launch { app } => self.launch_app(app),
SessionRequest::Kill { app } => self.kill_app(app),
SessionRequest::ListApps => SessionResponse::AppList { count: self.app_count },
SessionRequest::ListApps => SessionResponse::AppList {
count: self.app_count,
},
SessionRequest::ActiveApp => match self.active {
Some(app) => SessionResponse::Active { app },
None => SessionResponse::NotFound,

View File

@@ -4,9 +4,9 @@ use server_framework::Server;
fn test_server() -> SessionServer {
let cap = types::bounded::CapSlot::new(0).unwrap();
let mut s = SessionServer::new(cap);
s.register_app("snake", "/apps/snake.elf");
s.register_app("clock", "/apps/clock.elf");
let mut s = SessionServer::new(Some(cap));
s.register_app("snake", "/apps/snake.elf").unwrap();
s.register_app("clock", "/apps/clock.elf").unwrap();
s
}
@@ -35,7 +35,9 @@ fn launch_sets_active() {
#[test]
fn launch_invalid_app_returns_not_found() {
let mut s = test_server();
let resp = s.handle(SessionRequest::Launch { app: AppId::new(99) });
let resp = s.handle(SessionRequest::Launch {
app: AppId::new(99),
});
assert_eq!(resp, SessionResponse::NotFound);
}
@@ -77,3 +79,36 @@ fn no_active_app_initially() {
let resp = s.handle(SessionRequest::ActiveApp);
assert_eq!(resp, SessionResponse::NotFound);
}
#[test]
fn ids_past_the_table_are_rejected_not_indexed() {
let mut server = test_server();
for raw in [2, 16, 17, u16::MAX] {
let app = AppId::new(raw);
assert_eq!(
server.handle(SessionRequest::Kill { app }),
SessionResponse::NotFound
);
assert_eq!(
server.handle(SessionRequest::Launch { app }),
SessionResponse::NotFound
);
}
}
#[test]
fn the_app_table_fills_up_without_panicking() {
let mut server = SessionServer::new(None);
let added = (0..20)
.filter(|_| server.register_app("x", "/x").is_some())
.count();
assert_eq!(added, 16);
}
#[test]
fn launching_needs_process_creation_authority() {
let mut server = SessionServer::new(None);
server.register_app("snake", "/apps/snake.elf").unwrap();
let resp = server.handle(SessionRequest::Launch { app: AppId::new(0) });
assert_eq!(resp, SessionResponse::Error);
}