From 456137d015b19fc94f2e4944b02912b27831d365 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Thu, 20 Aug 2026 19:35:18 +0100 Subject: [PATCH 1/6] libkernel: memory: address: GuestVirtual: new Add a new marker types for a guest virutal addresses. --- libkernel/src/memory/address.rs | 10 ++++++++++ libkernel/src/memory/region.rs | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/libkernel/src/memory/address.rs b/libkernel/src/memory/address.rs index 8c86836d..ae161afd 100644 --- a/libkernel/src/memory/address.rs +++ b/libkernel/src/memory/address.rs @@ -57,6 +57,10 @@ pub struct Virtual; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Physical; +/// Marker for a virtual guest memory address type. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct GuestVirtual; + /// Marker for a physical guest memory address type. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct GuestPhysical; @@ -67,11 +71,13 @@ pub struct User; impl sealed::Sealed for Virtual {} impl sealed::Sealed for Physical {} +impl sealed::Sealed for GuestVirtual {} impl sealed::Sealed for GuestPhysical {} impl sealed::Sealed for User {} impl MemKind for Virtual {} impl MemKind for Physical {} +impl MemKind for GuestVirtual {} impl MemKind for GuestPhysical {} impl MemKind for User {} @@ -197,6 +203,8 @@ pub type TVA = Address; pub type TUA = Address; /// A typed guest physical address. pub type TGPA = Address; +/// A typed guest virtual address. +pub type TGVA = Address; /// A typed host physical address. pub type THPA = TPA; @@ -208,6 +216,8 @@ pub type VA = Address; pub type UA = Address; /// An untyped guest physical address. pub type GPA = Address; +/// An untyped guest virtual address. +pub type GVA = Address; /// An untyped host physical address. pub type HPA = PA; diff --git a/libkernel/src/memory/region.rs b/libkernel/src/memory/region.rs index b2ddc86c..9ef666ca 100644 --- a/libkernel/src/memory/region.rs +++ b/libkernel/src/memory/region.rs @@ -31,7 +31,9 @@ use super::{ PAGE_SHIFT, PAGE_SIZE, - address::{Address, AddressTranslator, GuestPhysical, MemKind, Physical, User, Virtual}, + address::{ + Address, AddressTranslator, GuestPhysical, GuestVirtual, MemKind, Physical, User, Virtual, + }, page::PageFrame, }; @@ -442,6 +444,9 @@ pub type UserMemoryRegion = MemoryRegion; /// A memory region of guest-physical memory. pub type GuestPhysMemoryRegion = MemoryRegion; +/// A memory region of guest-virutal memory. +pub type GuestVirtualMemoryRegion = MemoryRegion; + /// A representation of a `MemoryRegion` that has been expanded to be page-aligned. /// /// This struct holds the new, larger, page-aligned region, as well as the From 3c8f6a59b0dd6607d923e527d7bcaf7d866a8d80 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Thu, 20 Aug 2026 22:47:17 +0100 Subject: [PATCH 2/6] libkernel: paging: parametrise translation over address kind Introduce a `Translatable` trait which associates a virtual address kind with the physical kind it translates into (Virtual -> Physical, GuestVirtual -> GuestPhysical). Parametrise `PageTableMapper` over the physical address kind, defaulting to `Physical`. --- libkernel/src/arch/x86_64/memory/pg_walk.rs | 56 +++++++++++++-------- libkernel/src/memory/paging/mod.rs | 8 +-- libkernel/src/memory/paging/walk.rs | 56 +++++++++++++++------ 3 files changed, 78 insertions(+), 42 deletions(-) diff --git a/libkernel/src/arch/x86_64/memory/pg_walk.rs b/libkernel/src/arch/x86_64/memory/pg_walk.rs index f28598ff..db730849 100644 --- a/libkernel/src/arch/x86_64/memory/pg_walk.rs +++ b/libkernel/src/arch/x86_64/memory/pg_walk.rs @@ -4,14 +4,14 @@ use crate::{ error::{MapError, Result}, memory::{ PAGE_SIZE, - address::{PA, TPA, VA}, + address::{Address, TPA, VA}, paging::{ NullTlbInvalidator, PaMapper, PageTableEntry, PageTableMapper, PgTable, PgTableArray, TableMapper, permissions::PtePermissions, - walk::{RecursiveWalker, Translator, WalkContext}, + walk::{RecursiveWalker, Translatable, Translator, WalkContext}, }, - region::{PhysMemoryRegion, VirtMemoryRegion}, + region::{MemoryRegion, VirtMemoryRegion}, }, }; @@ -115,17 +115,21 @@ pub fn get_pte( } impl Translator for PML4Table { - fn translate( - table_pa: TPA>, - va: VA, + fn translate>( + table_pa: Address>, + va: Address, ctx: &mut WalkContext, - ) -> Result> { + ) -> crate::error::Result, usize, PtePermissions)>> { let desc = unsafe { - ctx.mapper - .with_page_table(table_pa, |pgtable| Self::from_ptr(pgtable).get_desc(va))? + ctx.mapper.with_page_table(table_pa, |pgtable| { + Self::from_ptr(pgtable).get_desc(VA::from_value(va.value())) + })? }; match desc.next_table_address() { - Some(next_pa) => PDPTable::translate(next_pa, va, ctx), + Some(next_pa) => { + let next_pa = Address::from_value(next_pa.value()); + PDPTable::translate(next_pa, va, ctx) + } None if desc.is_valid() => Err(MapError::InvalidDescriptor.into()), None => Ok(None), } @@ -133,19 +137,20 @@ impl Translator for PML4Table { } impl Translator for PTable { - fn translate( - table_pa: TPA>, - va: VA, + fn translate>( + table_pa: Address>, + va: Address, ctx: &mut WalkContext, - ) -> Result> { + ) -> crate::error::Result, usize, PtePermissions)>> { let desc = unsafe { - ctx.mapper - .with_page_table(table_pa, |pgtable| Self::from_ptr(pgtable).get_desc(va))? + ctx.mapper.with_page_table(table_pa, |pgtable| { + Self::from_ptr(pgtable).get_desc(VA::from_value(va.value())) + })? }; match desc.mapped_address() { Some(pa) => Ok(Some(( - pa, + Address::from_value(pa.value()), 1 << Self::Descriptor::MAP_SHIFT, desc.permissions(), ))), @@ -155,12 +160,17 @@ impl Translator for PTable { } } +/// Result of a translation, returning the region which is mapped (could be +/// longer than [PAGE_SIZE] for block mappings), the offset of the virtual +/// address into the region and the permissions of the mapping. +pub type TranslationResult = (MemoryRegion, usize, PtePermissions); + /// Translates the VA into a physical region plus an offset and permissions. -pub fn translate( - pml4_table: TPA>, - va: VA, +pub fn translate>( + pml4_table: Address>, + va: Address, mapper: &mut PM, -) -> Result> { +) -> Result>> { let mut walk_ctx = WalkContext { mapper, // Safe to not invalidate the TLB, as we are not modifying any PTEs. @@ -172,7 +182,9 @@ pub fn translate( let offset = va.value() & (blk_sz - 1); - Ok(Some((PhysMemoryRegion::new(pa, blk_sz), offset, perms))) + let rgn = MemoryRegion::new(pa, blk_sz); + + Ok(Some((rgn, offset, perms))) } else { Ok(None) } diff --git a/libkernel/src/memory/paging/mod.rs b/libkernel/src/memory/paging/mod.rs index 0e4093e1..49f08e28 100644 --- a/libkernel/src/memory/paging/mod.rs +++ b/libkernel/src/memory/paging/mod.rs @@ -2,7 +2,7 @@ use super::{ PAGE_SIZE, - address::{PA, TPA, TVA, VA}, + address::{Address, MemKind, PA, Physical, TPA, TVA, VA}, region::PhysMemoryRegion, }; use core::marker::PhantomData; @@ -159,7 +159,7 @@ impl Default for PgTableArray { } /// Trait for temporarily mapping and modifying a page table located at a -/// physical address. +/// (guest/host) physical address. /// /// During early boot, there are multiple mechanisms for accessing page table memory: /// - Identity mapping (idmap): active very early when VA = PA @@ -175,7 +175,7 @@ impl Default for PgTableArray { /// This function is `unsafe` because the caller must ensure: /// - The given physical address `pa` is valid and correctly aligned for type `T`. /// - The contents at that physical address represent a valid page table of type `T`. -pub trait PageTableMapper { +pub trait PageTableMapper { /// Map a physical address to a usable reference of the page table, run the /// closure, and unmap. /// @@ -185,7 +185,7 @@ pub trait PageTableMapper { /// - The contents at that physical address represent a valid page table of type `T`. unsafe fn with_page_table( &mut self, - pa: TPA>, + pa: Address>, f: impl FnOnce(TVA>) -> R, ) -> crate::error::Result; } diff --git a/libkernel/src/memory/paging/walk.rs b/libkernel/src/memory/paging/walk.rs index 34b88539..62d21038 100644 --- a/libkernel/src/memory/paging/walk.rs +++ b/libkernel/src/memory/paging/walk.rs @@ -3,7 +3,7 @@ use crate::{ error::MapError, memory::{ - address::{PA, TPA, VA}, + address::{Address, GuestPhysical, GuestVirtual, MemKind, Physical, TPA, VA, Virtual}, region::VirtMemoryRegion, }, }; @@ -13,11 +13,22 @@ use super::{ TableMapperTable, permissions::PtePermissions, }; +/// A virtual address kind that can be translated through page tables. +pub trait Translatable: MemKind { + /// The physical-side kind this translates into. + type Phys: MemKind; +} + +impl Translatable for Virtual { + type Phys = Physical; +} + +impl Translatable for GuestVirtual { + type Phys = GuestPhysical; +} + /// A collection of context required to modify page tables. -pub struct WalkContext<'a, PM> -where - PM: PageTableMapper + 'a, -{ +pub struct WalkContext<'a, PM> { /// The mapper used to temporarily access page tables by physical address. pub mapper: &'a mut PM, /// The TLB invalidator invoked after modifying page table entries. @@ -97,12 +108,14 @@ where } } +pub(crate) type TranslatorResult

= (Address, usize, PtePermissions); + pub(crate) trait Translator: PgTable + Sized { - fn translate( - table_pa: TPA>, - va: VA, + fn translate>( + table_pa: Address>, + va: Address, ctx: &mut WalkContext, - ) -> crate::error::Result>; + ) -> crate::error::Result>>; } impl Translator for T @@ -111,21 +124,32 @@ where T::Descriptor: PaMapper, ::NextLevel: Translator, { - fn translate( - table_pa: TPA>, - va: VA, + fn translate>( + table_pa: Address>, + va: Address, ctx: &mut WalkContext, - ) -> crate::error::Result> { + ) -> crate::error::Result, usize, PtePermissions)>> { let desc = unsafe { - ctx.mapper - .with_page_table(table_pa, |pgtable| T::from_ptr(pgtable).get_desc(va))? + ctx.mapper.with_page_table(table_pa, |pgtable| { + // Re-tag to a normal VA here. This is safe since `get_desc()` + // simply calculates the index into the table and returns the + // descriptor at that point. Given we've already forced pgtable + // to be a TVA, access to the table should be sound. + T::from_ptr(pgtable).get_desc(VA::from_value(va.value())) + })? }; if let Some(next_pa) = desc.next_table_address() { + // next_table_address() returns a hard-coded PA-type pointer. Recast + // this to the `M::Phys` address-space for the next-level lookup. + let next_pa = Address::from_value(next_pa.value()); ::NextLevel::translate(next_pa, va, ctx) } else if let Some(block_pa) = desc.mapped_address() { + // mapped_address() returns a hard-coded PA-type pointer. Recast + // this to the `M::Phys` address-space for final translation result. + let pa = Address::from_value(block_pa.value()); let block_size = 1usize << T::Descriptor::MAP_SHIFT; - Ok(Some((block_pa, block_size, desc.permissions().unwrap()))) + Ok(Some((pa, block_size, desc.permissions().unwrap()))) } else if desc.is_valid() { Err(MapError::InvalidDescriptor)? } else { From b49ac16abb67bcad1e263c51bc8ee6ef7821465a Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Fri, 21 Aug 2026 19:47:07 +0100 Subject: [PATCH 3/6] libkernel: memory: address: add to_untyped for guest addr types Add `to_untyped` for guest address-types. --- libkernel/src/memory/address.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libkernel/src/memory/address.rs b/libkernel/src/memory/address.rs index ae161afd..ccc488da 100644 --- a/libkernel/src/memory/address.rs +++ b/libkernel/src/memory/address.rs @@ -249,6 +249,20 @@ impl TPA { } } +impl TGPA { + /// Convert to an untyped guest physical address. + pub fn to_untyped(self) -> GPA { + GPA::from_value(self.value()) + } +} + +impl TGVA { + /// Convert to an untyped guest virtual address. + pub fn to_untyped(self) -> GVA { + GVA::from_value(self.value()) + } +} + impl Display for TPA { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Px{:08x}", self.inner) From 61c034f09a49596ea2275aa992cc274cb9fe5504 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Fri, 21 Aug 2026 20:59:33 +0100 Subject: [PATCH 4/6] arch: arm64: linker: ensure initcalls are aligned Ensure the function pointers to the initcalls are properly aligned in the binary. If the pointers aren't properly aligned we fault very early in kernel boot, prior to the console being inialised. --- src/arch/arm64/boot/linker.ld | 1 + 1 file changed, 1 insertion(+) diff --git a/src/arch/arm64/boot/linker.ld b/src/arch/arm64/boot/linker.ld index 3fe1aa23..6b92f39b 100644 --- a/src/arch/arm64/boot/linker.ld +++ b/src/arch/arm64/boot/linker.ld @@ -25,6 +25,7 @@ SECTIONS .data : { *(.data*) } .rodata : { *(.rodata*) + . = ALIGN(8); __driver_inits_start = .; *(.driver_inits*) __driver_inits_end = .; From c4861241e168ffeda45542b9d7ca74280858467e Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Fri, 21 Aug 2026 21:24:16 +0100 Subject: [PATCH 5/6] usertest: use volatile accesses in fault-inducing tests The latest Rust nightly (1.100.0-nightly 2026-08-20) elides the unused ptr::read of an invalid pointer even in debug builds, so the segfault children in test_segfault_read never faulted and exited normally, failing the WIFSIGNALED assertion in CI. Use read_volatile/write_volatile (and black_box the result) so the compiler cannot remove the faulting accesses. Also make the page-touch in test_mincore volatile for the same reason. --- usertest/src/main.rs | 5 +++-- usertest/src/signals.rs | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/usertest/src/main.rs b/usertest/src/main.rs index e9fb6fe7..1c4b02e3 100644 --- a/usertest/src/main.rs +++ b/usertest/src/main.rs @@ -222,8 +222,9 @@ fn test_mincore() { panic!("mmap failed: {}", std::io::Error::last_os_error()); } - // Touch the page to fault it in - ptr::write(addr as *mut u8, 42); + // Touch the page to fault it in. Use a volatile write so the + // compiler can't elide the store. + ptr::write_volatile(addr as *mut u8, 42); let mut vec_byte: u8 = 0; let ret = libc::mincore(addr as *mut _, page_size, &mut vec_byte as *mut u8); diff --git a/usertest/src/signals.rs b/usertest/src/signals.rs index 09dc8b68..b51920ff 100644 --- a/usertest/src/signals.rs +++ b/usertest/src/signals.rs @@ -181,12 +181,13 @@ fn segfault_child(inner: impl FnOnce()) { fn test_segfault_read() { segfault_child(|| { let addr: *const u8 = std::hint::black_box(std::ptr::null()); - let _ = unsafe { std::ptr::read(addr) }; + // Use a volatile read so the compiler can't elide the (dead) load. + let _ = std::hint::black_box(unsafe { std::ptr::read_volatile(addr) }); }); segfault_child(|| { // Ensure reading from kernel stack fails - let addr = 0xffff_ba00_0000_0000 as *const u8; - let _ = unsafe { std::ptr::read(addr) }; + let addr = std::hint::black_box(0xffff_ba00_0000_0000 as *const u8); + let _ = std::hint::black_box(unsafe { std::ptr::read_volatile(addr) }); }); } @@ -195,11 +196,12 @@ register_test!(test_segfault_read); fn test_segfault_write() { segfault_child(|| { let addr: *mut u8 = std::hint::black_box(std::ptr::null_mut()); - unsafe { std::ptr::write(addr, 42) }; + // Use a volatile write so the compiler can't elide the UB store. + unsafe { std::ptr::write_volatile(addr, 42) }; }); segfault_child(|| { - let addr = 0xffff_ba00_0000_0000 as *mut u8; - unsafe { std::ptr::write(addr, 42) }; + let addr = std::hint::black_box(0xffff_ba00_0000_0000 as *mut u8); + unsafe { std::ptr::write_volatile(addr, 42) }; }); } From 5cedc9778ec04fd168e91a148601a1b596241b46 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Fri, 21 Aug 2026 20:42:22 +0100 Subject: [PATCH 6/6] deps: bump async-trait --- Cargo.lock | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a223db8..74bce957 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,13 +48,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -65,7 +65,7 @@ checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -220,7 +220,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -266,7 +266,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -374,7 +374,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -574,7 +574,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -638,7 +638,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -720,7 +720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -742,7 +742,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -870,7 +870,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -955,6 +955,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -972,7 +983,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1037,7 +1048,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1198,7 +1209,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1214,7 +1225,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1273,7 +1284,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]]