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:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user