feat(kernel): kernel-owned memory model

- FrameTable: per-frame owner/use, replaces PagePool + ProcessMemory
- Quota per process, page tables charged to owner
- Regions for shared mem + device MMIO, torn down w/ owner
- Platform: create/destroy AS, map/unmap, zero_frame; drop translate_va
- RequestPages/ReleasePages/MapPage(move) checked vs kernel records
- user window 0x1000..32MiB, private pts by construction
- boot loader + device setup go through kernel api
- kernel built in place, boot allocator bounded
- isolation tests, randomized invariant test, FrameTable kani proofs
- fixes: inflated release, unmapped release, dst va unchecked,
  heap overflow, unbounded pt alloc, unzeroed frames, double free
This commit is contained in:
2026-10-26 19:40:50 +01:00
parent e931485ca7
commit 8d2c40cb1e
43 changed files with 2815 additions and 1172 deletions

View File

@@ -38,17 +38,10 @@ impl<P: Platform> Kernel<P> {
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
};
if let Err(e) = self.grant_cap(target_pid, resource.clone(), restricted) {
if let Err(e) = self.grant_cap(target_pid, resource, restricted) {
return SyscallOutcome::Complete(SyscallResult::Error(e));
}
if let domain::resource::Resource::Memory(region_id) = &resource
&& let Some(region) = self.regions.get(*region_id)
{
self.platform
.map_shared(target_pid, region.base(), region.size(), restricted);
}
SyscallOutcome::Complete(SyscallResult::Ok)
}

View File

@@ -57,11 +57,10 @@ impl<P: Platform> Kernel<P> {
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
};
self.process_memory[new_pid.raw() as usize] =
Some(domain::memory::process_memory::ProcessMemory::new(
new_pid,
domain::memory::process_memory::ProcessMemory::DEFAULT_PAGE_LIMIT,
));
if let Err(e) = self.create_address_space(new_pid) {
let _ = self.processes.destroy(new_pid);
return SyscallOutcome::Complete(SyscallResult::Error(e));
}
match self.grant_cap(
caller_pid,
@@ -73,8 +72,8 @@ impl<P: Platform> Kernel<P> {
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
Err(e) => {
// Rollback: destroy orphaned process
self.teardown_memory(new_pid);
let _ = self.processes.destroy(new_pid);
self.process_memory[new_pid.raw() as usize] = None;
SyscallOutcome::Complete(SyscallResult::Error(e))
}
}

View File

@@ -1,168 +1,78 @@
use platform::Platform;
use types::address::{PhysicalAddress, VirtualAddress};
use types::{
address::VirtualAddress, bounded::CapSlot, bounded::PageCount, id::ProcessId,
mapping::PagePerms,
};
use super::{SyscallOutcome, SyscallResult};
use crate::Kernel;
use domain::{error::KernelError, resource::Resource};
use types::permission::Permission;
const USER_VA_LIMIT: usize = 0x80000000;
fn complete(result: Result<SyscallResult, KernelError>) -> SyscallOutcome {
SyscallOutcome::Complete(result.unwrap_or_else(SyscallResult::Error))
}
impl<P: Platform> Kernel<P> {
pub(crate) fn handle_request_pages(
&mut self,
pid: types::id::ProcessId,
count: types::bounded::PageCount,
pid: ProcessId,
count: PageCount,
) -> SyscallOutcome {
let pages = count.value() as usize;
match &mut self.process_memory[pid.raw() as usize] {
Some(pm) => {
if pm.grant(pages).is_err() {
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::OutOfMemory,
));
}
}
None => {
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::InvalidProcess(pid),
));
}
}
let phys_addr = match self.page_pool.allocate(pages) {
Some(a) => a,
None => {
if let Some(pm) = &mut self.process_memory[pid.raw() as usize] {
let _ = pm.release(pages);
}
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::OutOfMemory,
));
}
};
let user_va = match &mut self.process_memory[pid.raw() as usize] {
Some(pm) => {
let va = pm.alloc_user_va(pages);
pm.record_allocation(phys_addr, pages);
va
}
None => {
self.page_pool.free(phys_addr, pages);
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::InvalidProcess(pid),
));
}
};
for i in 0..pages {
let page_va = VirtualAddress::from_raw(user_va.raw() + i * 4096);
let page_pa = PhysicalAddress::from_raw(phys_addr.raw() + i * 4096);
self.platform
.map_user_page(pid, page_va, page_pa, true, false);
}
let region = domain::memory::MemoryRegion::new(
types::id::RegionId::from_raw(0),
pid,
phys_addr,
count,
types::generation::Generation::initial(),
);
let _ = self.regions.insert(region);
SyscallOutcome::Complete(SyscallResult::Value(user_va.raw()))
}
pub(crate) fn handle_map_page(
&mut self,
caller_pid: types::id::ProcessId,
process_cap: types::bounded::CapSlot,
src_va: VirtualAddress,
dst_va: VirtualAddress,
executable: bool,
) -> SyscallOutcome {
// Prevent mapping kernel memory into userspace
if src_va.raw() >= USER_VA_LIMIT {
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::PermissionDenied {
slot: process_cap,
needed: types::permission::Permission::Read,
},
));
}
let target_pid = match self.check_cap(
caller_pid,
process_cap,
types::permission::Permission::Execute,
) {
Ok(cap) => match cap.resource() {
domain::resource::Resource::Process(pid) => *pid,
_ => {
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::PermissionDenied {
slot: process_cap,
needed: types::permission::Permission::Execute,
},
));
}
},
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
};
let pa = match self.platform.translate_va(caller_pid, src_va) {
Some(pa) => pa,
None => {
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::OutOfMemory,
));
}
};
self.platform
.map_user_page(target_pid, dst_va, pa, !executable, executable);
SyscallOutcome::Complete(SyscallResult::Ok)
complete(
self.find_free_heap_range(pid, pages)
.ok_or(KernelError::OutOfMemory)
.and_then(|va| {
self.map_fresh_range(pid, va, pages, PagePerms::ReadWrite)
.map(|()| SyscallResult::Value(va.raw()))
}),
)
}
pub(crate) fn handle_release_pages(
&mut self,
pid: types::id::ProcessId,
pid: ProcessId,
addr: VirtualAddress,
count: types::bounded::PageCount,
count: PageCount,
) -> SyscallOutcome {
let pages = count.value() as usize;
let phys = match self.platform.translate_va(pid, addr) {
Some(pa) => pa,
None => {
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::OutOfMemory,
));
}
complete(
self.release_range(pid, addr, count.value() as usize)
.map(|()| SyscallResult::Ok),
)
}
/// Moves one of the caller's pages into the process named by
/// `process_cap`. Executable pages are mapped read-execute, others
/// read-write.
pub(crate) fn handle_map_page(
&mut self,
caller_pid: ProcessId,
process_cap: CapSlot,
src_va: VirtualAddress,
dst_va: VirtualAddress,
executable: bool,
) -> SyscallOutcome {
let target = match self.check_cap(caller_pid, process_cap, Permission::Execute) {
Ok(cap) => match cap.resource() {
Resource::Process(pid) => *pid,
_ => {
return complete(Err(KernelError::PermissionDenied {
slot: process_cap,
needed: Permission::Execute,
}));
}
},
Err(e) => return complete(Err(e)),
};
let region_id = match self.regions.find_by_base(pid, phys) {
Some((rid, _)) => rid,
None => {
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::OutOfMemory,
));
}
let perms = if executable {
PagePerms::ReadExecute
} else {
PagePerms::ReadWrite
};
if !self.page_pool.free(phys, pages) {
return SyscallOutcome::Complete(SyscallResult::Error(
domain::error::KernelError::OutOfMemory,
));
}
let _ = self.regions.remove(region_id);
if let Some(pm) = &mut self.process_memory[pid.raw() as usize] {
let _ = pm.release(pages);
}
SyscallOutcome::Complete(SyscallResult::Ok)
complete(
self.move_page(caller_pid, src_va, target, dst_va, perms)
.map(|()| SyscallResult::Ok),
)
}
}