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:
@@ -1,9 +1,14 @@
|
||||
use crate::hw::uart::Uart;
|
||||
use crate::hw::virtio_blk::VirtioBlk;
|
||||
use crate::{boot_fs, mmu};
|
||||
use types::address::VirtualAddress;
|
||||
use types::id::{ProcessId, ThreadId};
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
const PAGE_SIZE: usize = mmu::PAGE_SIZE;
|
||||
const USER_VA_BASE: usize = 0x10000;
|
||||
/// The stack sits just below the image and stops short of the null page.
|
||||
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
|
||||
const MAX_FILE_SIZE: usize = 64 * 1024;
|
||||
|
||||
pub struct LoadedProcess {
|
||||
@@ -15,11 +20,23 @@ fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||
unsafe { &mut *crate::kernel_ptr() }
|
||||
}
|
||||
|
||||
pub fn load_init(
|
||||
uart: &Uart,
|
||||
blk: &mut VirtioBlk,
|
||||
kernel_pt: &mmu::PageTable,
|
||||
) -> (LoadedProcess, ThreadId) {
|
||||
/// Scratch space for reading ELF files, allocated once from the boot heap.
|
||||
fn elf_buffer() -> &'static mut [u8] {
|
||||
static mut BUF: usize = 0;
|
||||
// SAFETY: boot is single threaded; the buffer is kernel-only memory
|
||||
// from the boot allocator and is never handed to a process.
|
||||
unsafe {
|
||||
if BUF == 0 {
|
||||
BUF = mmu::allocator::alloc_zeroed_page();
|
||||
for _ in 1..MAX_FILE_SIZE / PAGE_SIZE {
|
||||
mmu::allocator::alloc_zeroed_page();
|
||||
}
|
||||
}
|
||||
core::slice::from_raw_parts_mut(BUF as *mut u8, MAX_FILE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> (LoadedProcess, ThreadId) {
|
||||
let (pid, tid) = kern()
|
||||
.boot(
|
||||
types::bounded::Priority::DEFAULT,
|
||||
@@ -27,10 +44,7 @@ pub fn load_init(
|
||||
)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "boot failed"));
|
||||
|
||||
let entry = load_elf(blk, kernel_pt, pid, b"init.elf");
|
||||
if entry == 0 {
|
||||
crate::halt(uart, "init.elf not found");
|
||||
}
|
||||
let entry = load_elf(uart, blk, pid, b"init.elf");
|
||||
setup_thread(tid, entry);
|
||||
uart.write_str("k-os: init loaded\n");
|
||||
|
||||
@@ -40,17 +54,13 @@ pub fn load_init(
|
||||
pub fn load_server(
|
||||
uart: &Uart,
|
||||
blk: &mut VirtioBlk,
|
||||
kernel_pt: &mmu::PageTable,
|
||||
filename: &[u8],
|
||||
label: &str,
|
||||
) -> LoadedProcess {
|
||||
let pid = kern()
|
||||
.create_process_internal()
|
||||
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
||||
let entry = load_elf(blk, kernel_pt, pid, filename);
|
||||
if entry == 0 {
|
||||
crate::halt(uart, "elf not found");
|
||||
}
|
||||
let entry = load_elf(uart, blk, pid, filename);
|
||||
let tid = kern()
|
||||
.spawn_thread_internal(pid)
|
||||
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
|
||||
@@ -98,100 +108,89 @@ fn setup_thread(tid: ThreadId, entry: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_elf(
|
||||
blk: &mut VirtioBlk,
|
||||
kernel_pt: &mmu::PageTable,
|
||||
pid: ProcessId,
|
||||
filename: &[u8],
|
||||
) -> usize {
|
||||
let buf_base = mmu::allocator::alloc_zeroed_page();
|
||||
let file_pages = MAX_FILE_SIZE / 4096;
|
||||
for _ in 1..file_pages {
|
||||
mmu::allocator::alloc_zeroed_page();
|
||||
/// Copies `data` into `pid`'s already mapped pages starting at `va`.
|
||||
fn copy_to_user(pid: ProcessId, va: usize, data: &[u8]) -> bool {
|
||||
let mut done = 0;
|
||||
while done < data.len() {
|
||||
let addr = va + done;
|
||||
let page = addr & !(PAGE_SIZE - 1);
|
||||
let offset = addr - page;
|
||||
let Some(pa) = kern().user_page_frame(pid, VirtualAddress::from_raw(page)) else {
|
||||
return false;
|
||||
};
|
||||
let len = (PAGE_SIZE - offset).min(data.len() - done);
|
||||
// SAFETY: `pa` is a pool frame the kernel just mapped for `pid`;
|
||||
// pool frames are identity mapped kernel RAM.
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
data.as_ptr().add(done),
|
||||
(pa.raw() as *mut u8).add(offset),
|
||||
len,
|
||||
);
|
||||
}
|
||||
done += len;
|
||||
}
|
||||
let buf = unsafe { core::slice::from_raw_parts_mut(buf_base as *mut u8, MAX_FILE_SIZE) };
|
||||
true
|
||||
}
|
||||
|
||||
fn segment_perms(flags: elf::SegmentFlags) -> Option<PagePerms> {
|
||||
match (flags.write, flags.execute) {
|
||||
(true, true) => None,
|
||||
(false, true) => Some(PagePerms::ReadExecute),
|
||||
(true, false) => Some(PagePerms::ReadWrite),
|
||||
(false, false) => Some(PagePerms::ReadOnly),
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads `filename` into `pid` through the kernel's own mapping API, so
|
||||
/// every page is tracked, charged and checked like any other. Halts on any
|
||||
/// malformed or overlapping image: boot servers are trusted to be sane.
|
||||
fn load_elf(uart: &Uart, blk: &mut VirtioBlk, pid: ProcessId, filename: &[u8]) -> usize {
|
||||
let buf = elf_buffer();
|
||||
let file_size = boot_fs::read_file_from_disk(blk, filename, buf).unwrap_or(0);
|
||||
if file_size == 0 {
|
||||
return 0;
|
||||
crate::halt(uart, "ELF not found");
|
||||
}
|
||||
|
||||
let elf_info = match elf::parse(&buf[..file_size]) {
|
||||
Ok(info) => info,
|
||||
Err(_) => return 0,
|
||||
};
|
||||
|
||||
let user_pt = mmu::create_user_page_table(
|
||||
kernel_pt,
|
||||
types::address::PhysicalAddress::from_raw(0),
|
||||
0,
|
||||
types::address::VirtualAddress::from_raw(USER_VA_BASE),
|
||||
);
|
||||
let file = &buf[..file_size];
|
||||
let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF"));
|
||||
|
||||
for segment in elf_info.segments() {
|
||||
let page_start = segment.vaddr & !0xFFF;
|
||||
let offset_in_page = segment.vaddr & 0xFFF;
|
||||
let total_size = offset_in_page + segment.mem_size;
|
||||
let seg_pages = (total_size + 4095) / 4096;
|
||||
let perms =
|
||||
segment_perms(segment.flags).unwrap_or_else(|| crate::halt(uart, "W+X segment"));
|
||||
let page_start = segment.vaddr & !(PAGE_SIZE - 1);
|
||||
let offset_in_page = segment.vaddr - page_start;
|
||||
let pages = offset_in_page
|
||||
.checked_add(segment.mem_size)
|
||||
.map(|total| total.div_ceil(PAGE_SIZE))
|
||||
.unwrap_or_else(|| crate::halt(uart, "segment too large"));
|
||||
if pages == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
for p in 0..seg_pages {
|
||||
let page = mmu::allocator::alloc_zeroed_page();
|
||||
let page_va = page_start + p * 4096;
|
||||
let seg_file_start = if p == 0 { 0 } else { p * 4096 - offset_in_page };
|
||||
let dest_off = if p == 0 { offset_in_page } else { 0 };
|
||||
let copy_len = if seg_file_start < segment.file_size {
|
||||
(segment.file_size - seg_file_start).min(4096 - dest_off)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
kern()
|
||||
.map_fresh_range(pid, VirtualAddress::from_raw(page_start), pages, perms)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "cannot map segment"));
|
||||
|
||||
if copy_len > 0 {
|
||||
let src = segment.file_offset + seg_file_start;
|
||||
if src + copy_len <= file_size {
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
buf.as_ptr().add(src),
|
||||
(page as *mut u8).add(dest_off),
|
||||
copy_len,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let flags = if segment.flags.execute {
|
||||
mmu::Sv39Flags::user_rx()
|
||||
} else if segment.flags.write {
|
||||
mmu::Sv39Flags::user_rw()
|
||||
} else {
|
||||
mmu::Sv39Flags::user_rx()
|
||||
};
|
||||
mmu::map(
|
||||
user_pt,
|
||||
mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(page_va),
|
||||
pa: types::address::PhysicalAddress::from_raw(page),
|
||||
pages: 1,
|
||||
flags,
|
||||
},
|
||||
);
|
||||
let copy_len = segment.file_size.min(segment.mem_size);
|
||||
let data = segment
|
||||
.file_offset
|
||||
.checked_add(copy_len)
|
||||
.and_then(|end| file.get(segment.file_offset..end))
|
||||
.unwrap_or_else(|| crate::halt(uart, "segment outside file"));
|
||||
if !copy_to_user(pid, segment.vaddr, data) {
|
||||
crate::halt(uart, "segment copy failed");
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..16usize {
|
||||
let sp = mmu::allocator::alloc_zeroed_page();
|
||||
mmu::map(
|
||||
user_pt,
|
||||
mmu::Mapping {
|
||||
va: types::address::VirtualAddress::from_raw(USER_VA_BASE - (16 - i) * 4096),
|
||||
pa: types::address::PhysicalAddress::from_raw(sp),
|
||||
pages: 1,
|
||||
flags: mmu::Sv39Flags::user_rw(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let stack_bottom = USER_VA_BASE - USER_STACK_PAGES * PAGE_SIZE;
|
||||
kern()
|
||||
.platform_mut()
|
||||
.set_page_table(pid, user_pt as *const mmu::PageTable as usize);
|
||||
.map_fresh_range(
|
||||
pid,
|
||||
VirtualAddress::from_raw(stack_bottom),
|
||||
USER_STACK_PAGES,
|
||||
PagePerms::ReadWrite,
|
||||
)
|
||||
.unwrap_or_else(|_| crate::halt(uart, "cannot map stack"));
|
||||
|
||||
elf_info.entry_point
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user