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

@@ -1,11 +1,9 @@
use domain::memory::MemoryRegion;
use domain::process::MAX_PROCESSES;
use platform::Platform;
use platform::{FrameSource, MapError, Platform};
use types::{
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
bounded::PageCount,
id::{CoreId, IrqId, ProcessId},
permission::{Permission, PermissionSet},
mapping::PagePerms,
time::{Ticks, TimerDuration},
};
@@ -17,6 +15,7 @@ use crate::trap;
pub struct RiscVPlatform {
num_cores: u8,
kernel_pt: usize,
/// Root table of each process's address space, 0 if it has none.
page_tables: [usize; MAX_PROCESSES],
}
@@ -33,40 +32,76 @@ impl RiscVPlatform {
self.page_tables[pid.raw() as usize]
}
pub fn set_page_table(&mut self, pid: ProcessId, addr: usize) {
self.page_tables[pid.raw() as usize] = addr;
}
fn ensure_page_table(&mut self, pid: ProcessId) -> &mut mmu::PageTable {
let idx = pid.raw() as usize;
if self.page_tables[idx] == 0 {
let kernel_pt = unsafe { &*(self.kernel_pt as *const mmu::PageTable) };
let user_pt = mmu::create_user_page_table(
kernel_pt,
PhysicalAddress::from_raw(0),
0,
VirtualAddress::from_raw(0x10000),
);
self.page_tables[idx] = user_pt as *const mmu::PageTable as usize;
fn root(&self, pid: ProcessId) -> Option<usize> {
match self.page_tables.get(pid.raw() as usize) {
Some(&0) | None => None,
Some(&root) => Some(root),
}
unsafe { &mut *(self.page_tables[idx] as *mut mmu::PageTable) }
}
}
impl Platform for RiscVPlatform {
fn configure_protection(&mut self, process: ProcessId, regions: &[MemoryRegion]) {
let pt = self.ensure_page_table(process);
for region in regions {
mmu::map(
pt,
mmu::Mapping {
va: VirtualAddress::from_raw(region.base().raw()),
pa: region.base(),
pages: region.size().value() as usize,
flags: mmu::Sv39Flags::user(true, true, true),
},
);
const USER_VA_START: usize = mmu::USER_VA_START;
const USER_VA_END: usize = mmu::USER_VA_END;
fn create_address_space(
&mut self,
process: ProcessId,
frames: &mut dyn FrameSource,
) -> Result<(), MapError> {
let slot = self
.page_tables
.get_mut(process.raw() as usize)
.ok_or(MapError::NoAddressSpace)?;
// SAFETY: the kernel page table is identity mapped and lives forever.
let kernel_root = unsafe { &*(self.kernel_pt as *const mmu::PageTable) };
*slot = mmu::create_user_root(kernel_root, frames)?;
Ok(())
}
fn destroy_address_space(&mut self, process: ProcessId) {
let Some(root) = self.root(process) else {
return;
};
// The caller may be tearing down the space we are running on; get
// off it before the kernel frees its frames.
if mmu::active_root() == root {
// SAFETY: the kernel page table is identity mapped and lives forever.
mmu::activate(unsafe { &*(self.kernel_pt as *const mmu::PageTable) });
}
self.page_tables[process.raw() as usize] = 0;
// SAFETY: flushing translations has no other effect.
unsafe { core::arch::asm!("sfence.vma zero, zero") };
}
fn map_page(
&mut self,
process: ProcessId,
va: VirtualAddress,
pa: PhysicalAddress,
perms: PagePerms,
frames: &mut dyn FrameSource,
) -> Result<(), MapError> {
let root = self.root(process).ok_or(MapError::NoAddressSpace)?;
mmu::map_user_page(
root,
va.raw(),
pa.raw(),
mmu::Sv39Flags::user(perms),
frames,
)
}
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress) {
if let Some(root) = self.root(process) {
mmu::unmap_user_page(root, va.raw());
}
}
fn zero_frame(&mut self, pa: PhysicalAddress) {
// SAFETY: pool frames are identity mapped kernel RAM and the kernel
// only zeroes frames it has just allocated.
unsafe { core::ptr::write_bytes(pa.raw() as *mut u8, 0, mmu::PAGE_SIZE) };
}
fn enable_user_mode(&mut self, entry: EntryPoint, stack: StackPointer) -> ! {
@@ -117,63 +152,10 @@ impl Platform for RiscVPlatform {
self.num_cores
}
fn map_shared(
&mut self,
process: ProcessId,
base: PhysicalAddress,
size: PageCount,
permissions: PermissionSet,
) {
let pt = self.ensure_page_table(process);
mmu::map(
pt,
mmu::Mapping {
va: VirtualAddress::from_raw(base.raw()),
pa: base,
pages: size.value() as usize,
flags: mmu::Sv39Flags::user(
permissions.allows(Permission::Read),
permissions.allows(Permission::Write),
permissions.allows(Permission::Execute),
),
},
);
}
fn debug_putchar(&self, byte: u8) {
crate::hw::uart::Uart::new().put_char(byte);
}
fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
let pt_addr = self.page_tables[process.raw() as usize];
if pt_addr == 0 {
return None;
}
let pt = unsafe { &*(pt_addr as *const mmu::PageTable) };
mmu::translate(pt, va.raw()).map(PhysicalAddress::from_raw)
}
fn map_user_page(
&mut self,
process: ProcessId,
va: VirtualAddress,
pa: PhysicalAddress,
writable: bool,
executable: bool,
) {
let pt = self.ensure_page_table(process);
let flags = mmu::Sv39Flags::user(true, writable, executable);
mmu::map(
pt,
mmu::Mapping {
va,
pa,
pages: 1,
flags,
},
);
}
fn halt(&self) -> ! {
unsafe {
core::arch::asm!(