From f66debdd01c7cffc13569705448bbbfb7f9aafe1 Mon Sep 17 00:00:00 2001 From: bunnie Date: Wed, 15 May 2024 02:31:40 +0800 Subject: [PATCH] make susres page ownership not hard coded; mark PTs as wired also marks the ProcessImpl page as wired too --- loader/src/bootconfig.rs | 11 ++++++++ loader/src/phase1.rs | 2 +- loader/src/phase2.rs | 39 ++++++++++++++++++++++---- loader/src/platform/atsama5d27/boot.rs | 6 ++++ loader/src/swap.rs | 11 ++++++-- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/loader/src/bootconfig.rs b/loader/src/bootconfig.rs index 04de5b36e..af0e9ef17 100644 --- a/loader/src/bootconfig.rs +++ b/loader/src/bootconfig.rs @@ -206,6 +206,15 @@ impl BootConfig { panic!("Tried to change region {:08x} that isn't in defined memory!", addr); } + #[cfg(feature = "swap")] + pub fn mark_as_wired(&mut self, paddr: usize) { + if paddr >= self.sram_start as usize && paddr < self.sram_start as usize + self.sram_size { + self.runtime_page_tracker[(paddr - self.sram_start as usize) / PAGE_SIZE].set_wired(); + } else { + panic!("Tried to wire address {:08x} that isn't in main RAM!", paddr); + } + } + /// Map the given page to the specified process table. If necessary, /// allocate a new page. /// @@ -355,6 +364,8 @@ impl BootConfig { FLG_R | FLG_W | FLG_VALID, owner, ); + #[cfg(feature = "swap")] + self.mark_as_wired(addr.get()); // page table entries should never be swapped. if VDBG { println!("<<< Done mapping new address"); } diff --git a/loader/src/phase1.rs b/loader/src/phase1.rs index 32f4f3c22..6474caa9f 100644 --- a/loader/src/phase1.rs +++ b/loader/src/phase1.rs @@ -716,7 +716,7 @@ fn copy_processes(cfg: &mut BootConfig) { // the section load address. last_copy_vaddr = dst_page_vaddr; - if SDBG { + if SDBG && VVDBG { println!("Looping to the next section (swap)"); println!( " swap_free_page: {:x}, dst_page_vaddr: {:x}, src_swap_img_addr: {:x}", diff --git a/loader/src/phase2.rs b/loader/src/phase2.rs index ae402cef3..105a31e02 100644 --- a/loader/src/phase2.rs +++ b/loader/src/phase2.rs @@ -271,10 +271,13 @@ pub fn phase_2(cfg: &mut BootConfig, fs_prehash: &[u8; 64]) { { let swap_size_usable = crate::swap::derive_usable_swap(swap_spec.swap_len as usize); let mut swap_count_start = 0; - for offset in (0..(swap_size_usable / PAGE_SIZE) * core::mem::size_of::()).step_by(4096) { + for offset in + (0..(swap_size_usable / PAGE_SIZE) * core::mem::size_of::()).step_by(4096).rev() + { let count_page = cfg.alloc() as usize; // this also zeroes the page // we want the start page to keep refreshing, because allocations grow *down*, so the - // last iteration actually contains the base pointer of the count tracker + // last iteration actually contains the base pointer of the count tracker (due to the .rev() + // on the iterator) swap_count_start = count_page; cfg.map_page( root, @@ -290,6 +293,7 @@ pub fn phase_2(cfg: &mut BootConfig, fs_prehash: &[u8; 64]) { let counts: &mut [u32] = unsafe { core::slice::from_raw_parts_mut(swap_count_start as *mut u32, swap_size_usable / PAGE_SIZE) }; + println!("Claiming 0x{:x} swap pages as used", cfg.last_swap_page); for i in 0..cfg.last_swap_page { counts[i] = loader::FLG_SWAP_USED; } @@ -317,11 +321,32 @@ pub fn phase_2(cfg: &mut BootConfig, fs_prehash: &[u8; 64]) { println!(); } } + + // Mark pages used by suspend/resume, otherwise they will be handed out to userspace. + // However, when not doing suspend/resume, it's safe to hand this out because it's always zeroized + // before use and never re-used on a resume cycle. + #[cfg(feature = "resume")] + { + // Mark all of stack as owned by the kernel, because this region contains the backup arguments. + // tampering in userspace of the loader stack prior to a resume can be a potent attack vector. + let rtp_len = cfg.runtime_page_tracker.len(); + for page in cfg.runtime_page_tracker[1 + rtp_len - GUARD_MEMORY_BYTES / PAGE_SIZE..].iter_mut() { + *page = XousAlloc::from(1); + } + // allow susres to claim the suspend/resume marker, assumed to be at the limit of the guard memory + cfg.runtime_page_tracker[rtp_len - GUARD_MEMORY_BYTES / PAGE_SIZE] = XousAlloc::from(0); + } + println!("Runtime Page Tracker: {} bytes", cfg.runtime_page_tracker.len()); - // mark pages used by suspend/resume according to their needs - cfg.runtime_page_tracker[cfg.sram_size / PAGE_SIZE - 1] = XousAlloc::from(1); // claim the loader stack -- do not allow tampering, as it contains backup kernel args - cfg.runtime_page_tracker[cfg.sram_size / PAGE_SIZE - 2] = XousAlloc::from(1); // 8k in total (to allow for digital signatures to be computed) - cfg.runtime_page_tracker[cfg.sram_size / PAGE_SIZE - 3] = XousAlloc::from(0); // allow clean suspend page to be mapped in Xous + #[cfg(feature = "swap")] + if SDBG { + println!("Occupied RPT entries:"); + for (i, entry) in cfg.runtime_page_tracker.iter().enumerate() { + if entry.raw_vpn() != 0 { + println!(" {:x}: {:x} [{}]", i, entry.raw_vpn(), entry.timestamp()); + } + } + } } /// This describes the kernel as well as initially-loaded processes @@ -402,6 +427,8 @@ impl ProgramDescription { FLG_R | FLG_W | FLG_VALID, pid as XousPid, ); + #[cfg(feature = "swap")] + allocator.mark_as_wired(satp_address); // don't allow the root PT to be swapped in any process // Allocate context for this process let thread_address = allocator.alloc() as usize; diff --git a/loader/src/platform/atsama5d27/boot.rs b/loader/src/platform/atsama5d27/boot.rs index 51ac3b04b..eba1482f7 100644 --- a/loader/src/platform/atsama5d27/boot.rs +++ b/loader/src/platform/atsama5d27/boot.rs @@ -85,6 +85,8 @@ impl BootConfig { let mut num_alloc_pages = 0; for _ in 0..4 { let mut allocated_page_ptr = self.alloc(); + #[cfg(feature = "swap")] + self.mark_as_wired(allocated_page_ptr); num_alloc_pages += 1; let is_aligned = allocated_page_ptr as usize & (ALIGNMENT_16K - 1) == 0; self.change_owner(pid, allocated_page_ptr as usize); @@ -104,6 +106,8 @@ impl BootConfig { pid ); allocated_page_ptr = self.alloc(); + #[cfg(feature = "swap")] + self.mark_as_wired(allocated_page_ptr); self.change_owner(pid, allocated_page_ptr as usize); } @@ -207,6 +211,8 @@ impl BootConfig { dprintln!("Previously unmapped L1 entry"); let na = self.alloc(); + #[cfg(feature = "swap")] + self.mark_as_wired(na); let phys = PhysicalAddress::from_ptr(na); let entry_flags = u32::from(PAGE_TABLE_FLAGS::VALID::Enable) | u32::from(PAGE_TABLE_FLAGS::DOMAIN.val(0xf)); diff --git a/loader/src/swap.rs b/loader/src/swap.rs index 344d2adf1..7e7e7d0dd 100644 --- a/loader/src/swap.rs +++ b/loader/src/swap.rs @@ -134,17 +134,24 @@ impl SwapAlloc { | if (pid == 1) || (pid == SWAPPER_PID) { SWAP_FLG_WIRED } else { 0 }; } + /// Sets the wired bit. Used for marking page table elements as unswappable. + pub fn set_wired(&mut self) { self.vpn |= SWAP_FLG_WIRED; } + /// This is a slight abuse of the naming system to provide us cross-compatibility with the case where the /// structure is defined as an overload of the `u8` type pub fn to_le(&self) -> u8 { self.vpn as u8 } - pub fn is_wired(&self) -> bool { self.vpn & SWAP_FLG_WIRED != 0 } + pub fn is_wired(&self) -> bool { (self.vpn & SWAP_FLG_WIRED) != 0 } - pub fn is_valid(&self) -> bool { self.timestamp != 0 || self.vpn != 0 } + pub fn is_valid(&self) -> bool { self.vpn != 0 } pub fn raw_pid(&self) -> u8 { self.vpn as u8 } pub fn vaddr(&self) -> usize { (self.vpn & !0xFFFu32) as usize } + + pub fn raw_vpn(&self) -> u32 { self.vpn } + + pub fn timestamp(&self) -> u32 { self.timestamp } } impl fmt::Debug for SwapAlloc {