From 2c9a6c06bb59c6ed0920ec24108bee699daec113 Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Fri, 13 Feb 2026 11:49:42 -0800 Subject: [PATCH 1/9] Define MEMORY_SP bit in paging crate This change adds the special-purpose memory region bit in paging crate. This bit may be used for specific device drivers or applications. Signed-off-by: Kun Qin --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 300835f..4d8e7f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,7 +151,7 @@ bitflags! { const ReadProtect = 0x00000000_00002000u64; // Maps to Present bit on X64 const ExecuteProtect = 0x00000000_00004000u64; // Maps to NX bit on X64 const ReadOnly = 0x00000000_00020000u64; // Maps to Read/Write bit on X64 - + const SpecialPurpose = 0x00000000_00040000u64; // Special-purpose memory const CacheAttributesMask = Self::Uncached.bits() | Self::WriteCombining.bits() | From ce05821373669c4ac35ef78cfaec07a608ae6045 Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Tue, 24 Feb 2026 11:07:40 -0800 Subject: [PATCH 2/9] Update page table query to correctly inherit parent attributes The original page table query logic assumed that all parent-level entries were marked with the most permissive attributes, allowing the leaf entry to directly reflect the effective attributes during a query. However, since patina-paging also supports initialization from an existing page table, this assumption does not always hold. The query logic must instead accurately model attribute inheritance across the page table hierarchy. This change fixes the issue by introducing an additional input parameter to the internal helper, allowing the query to correctly propagate and reflect parent attributes. Signed-off-by: Kun Qin --- src/paging.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/paging.rs b/src/paging.rs index cd830d8..da2adbe 100644 --- a/src/paging.rs +++ b/src/paging.rs @@ -408,6 +408,7 @@ impl PageTableInternal { base: PhysicalAddress, prev_attributes: &mut RangeMappingState, state: PageTableState, + inherited_attrs: MemoryAttributes, ) -> Result { let mut va = start_va; @@ -444,7 +445,10 @@ impl PageTableInternal { } if entry.points_to_pa(level) { - let current_attributes = entry.get_attributes(); + // Compose the leaf entry's attributes with any restrictive attributes inherited from + // parent page table entries. On x86_64, U/S, R/W, and NX are "most restrictive wins" + // across all levels, which maps to OR of the restrictive flag bits. + let current_attributes = entry.get_attributes() | inherited_attrs; match prev_attributes { RangeMappingState::Uninitialized => { *prev_attributes = RangeMappingState::Mapped(current_attributes) @@ -481,6 +485,7 @@ impl PageTableInternal { next_base, prev_attributes, state, + inherited_attrs | entry.get_attributes(), ) { Ok(_) | Err(PtError::NoMapping) => {} Err(e) => return Err(e), @@ -782,6 +787,7 @@ impl PageTableInternal { self.base, &mut prev_attributes, self.get_state(), + MemoryAttributes::empty(), ) } From aa89e95eb27158308aef5ff4627ad8cd2f001d41 Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Fri, 13 Feb 2026 11:48:18 -0800 Subject: [PATCH 3/9] Add mm_supv feature flag This change adds a mm_supv flag to prepare for the usage of supervisor bit in the page table entries. Note that supervisor will map the MEMORY_SP bit to be supervisor pages and otherwise it will be user pages. Signed-off-by: Kun Qin --- Cargo.toml | 1 + src/lib.rs | 6 ++++++ src/x64.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/x64/structs.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e209442..a5145e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ cfg-if = "1.0.0" [features] doc = [] +mm_supv = [] [profile.test] opt-level = 0 diff --git a/src/lib.rs b/src/lib.rs index 4d8e7f7..a13d9de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,6 +160,12 @@ bitflags! { Self::UncachedExport.bits() | Self::WriteProtect.bits(); + #[cfg(feature = "mm_supv")] + const AccessAttributesMask = Self::ReadProtect.bits() | + Self::ExecuteProtect.bits() | + Self::ReadOnly.bits() | + Self::SpecialPurpose.bits(); + #[cfg(not(feature = "mm_supv"))] const AccessAttributesMask = Self::ReadProtect.bits() | Self::ExecuteProtect.bits() | Self::ReadOnly.bits(); diff --git a/src/x64.rs b/src/x64.rs index 13557f7..022ca48 100644 --- a/src/x64.rs +++ b/src/x64.rs @@ -106,6 +106,52 @@ pub(crate) fn invalidate_tlb(va: VirtualAddress) { }; } +pub fn disable_write_protection() -> u64 { + // SAFETY: This function is unsafe because it modifies control registers to disable write protection. The caller must ensure that this is done in a safe context, such as when the current code is running at a high enough privilege level and that interrupts are disabled to prevent + // other code from running while write protection is disabled. + unsafe { + let mut _cr0 = 0u64; + // Read CR0 if this is on a real system + #[cfg(all(not(test), target_arch = "x86_64"))] + { + asm!("mov {}, cr0", out(reg) _cr0); + } + + // Clear the Write Protect bit (bit 16) + let _new_cr0 = _cr0 & !(1 << 16); + // Write back to CR0 + #[cfg(all(not(test), target_arch = "x86_64"))] + { + asm!("mov cr0, {}", in(reg) _new_cr0); + } + + _cr0 + } +} + +pub fn enable_write_protection(cr0: u64) { + // SAFETY: This function is unsafe because it modifies control registers to + // enable write protection. It is only supposed to be used when writing to + // page tables in mm mode when the tables are marked as read only. + unsafe { + let mut _current_cr0 = 0u64; + // Read CR0 + #[cfg(all(not(test), target_arch = "x86_64"))] + { + asm!("mov {}, cr0", out(reg) _current_cr0); + } + + // Set the Write Protect bit (bit 16) + let _new_cr0 = _current_cr0 | (cr0 & (1 << 16)); + + // Write back to CR0 + #[cfg(all(not(test), target_arch = "x86_64"))] + { + asm!("mov cr0, {}", in(reg) _new_cr0); + } + } +} + pub(crate) struct PageTableArchX64; impl PageTableHal for PageTableArchX64 { diff --git a/src/x64/structs.rs b/src/x64/structs.rs index bf762db..693860c 100644 --- a/src/x64/structs.rs +++ b/src/x64/structs.rs @@ -9,8 +9,9 @@ use crate::{ MemoryAttributes, PtError, structs::{PageLevel, PhysicalAddress, VirtualAddress}, - x64::{PD, PDP, PML4, PML5, PT, invalidate_tlb}, + x64::{PD, PDP, PML4, PML5, PT, invalidate_tlb, disable_write_protection, enable_write_protection}, }; +use core::arch::asm; use bitfield_struct::bitfield; use core::ptr::write_volatile; @@ -81,7 +82,14 @@ impl PageTableEntryX64 { self.set_read_write(true); } - self.set_user_supervisor(true); + + #[cfg(feature = "mm_supv")] + if attributes.contains(MemoryAttributes::SpecialPurpose) { + self.set_user_supervisor(false); + } else { + self.set_user_supervisor(true); + } + self.set_write_through(false); self.set_cache_disabled(false); self.set_page_size(false); @@ -148,7 +156,15 @@ impl crate::arch::PageTableEntry for PageTableEntryX64 { } let prev_valid = self.present(); + + #[cfg(feature = "mm_supv")] + let cr0 = disable_write_protection(); + self.swap(©); + + #[cfg(feature = "mm_supv")] + enable_write_protection(cr0); + if prev_valid { invalidate_tlb(va); } @@ -164,7 +180,15 @@ impl crate::arch::PageTableEntry for PageTableEntryX64 { let mut copy = *self; copy.set_present(value); let prev_valid = self.present(); + + #[cfg(feature = "mm_supv")] + let cr0 = disable_write_protection(); + self.swap(©); + + #[cfg(feature = "mm_supv")] + enable_write_protection(cr0); + if prev_valid { invalidate_tlb(va); } @@ -190,6 +214,11 @@ impl crate::arch::PageTableEntry for PageTableEntryX64 { attributes |= MemoryAttributes::ExecuteProtect; } + #[cfg(feature = "mm_supv")] + if !self.user_supervisor() { + attributes |= MemoryAttributes::SpecialPurpose; + } + attributes } @@ -282,7 +311,15 @@ impl crate::arch::PageTableEntry for PageTableEntryX64 { let mut copy = *self; copy.0 = 0; let prev_valid = self.present(); + + #[cfg(feature = "mm_supv")] + let cr0 = disable_write_protection(); + self.swap(©); + + #[cfg(feature = "mm_supv")] + enable_write_protection(cr0); + if prev_valid { invalidate_tlb(va); } From f74e10c6c360f975887291c44c361fa4f6ac01ea Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Tue, 24 Feb 2026 15:59:08 -0800 Subject: [PATCH 4/9] remove build warning --- src/x64/structs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/x64/structs.rs b/src/x64/structs.rs index 693860c..ad4ea68 100644 --- a/src/x64/structs.rs +++ b/src/x64/structs.rs @@ -11,7 +11,6 @@ use crate::{ structs::{PageLevel, PhysicalAddress, VirtualAddress}, x64::{PD, PDP, PML4, PML5, PT, invalidate_tlb, disable_write_protection, enable_write_protection}, }; -use core::arch::asm; use bitfield_struct::bitfield; use core::ptr::write_volatile; From 9ad93cd929a92d71d3bf30357a6e84334d2019c7 Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Thu, 5 Mar 2026 10:59:35 -0800 Subject: [PATCH 5/9] match up the naming --- src/lib.rs | 2 +- src/x64/structs.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5bbc79d..27e49b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,7 +164,7 @@ bitflags! { const AccessAttributesMask = Self::ReadProtect.bits() | Self::ExecuteProtect.bits() | Self::ReadOnly.bits() | - Self::SpecialPurpose.bits(); + Self::Supervisor.bits(); #[cfg(not(feature = "mm_supv"))] const AccessAttributesMask = Self::ReadProtect.bits() | Self::ExecuteProtect.bits() | diff --git a/src/x64/structs.rs b/src/x64/structs.rs index ad4ea68..dfe0851 100644 --- a/src/x64/structs.rs +++ b/src/x64/structs.rs @@ -83,7 +83,7 @@ impl PageTableEntryX64 { #[cfg(feature = "mm_supv")] - if attributes.contains(MemoryAttributes::SpecialPurpose) { + if attributes.contains(MemoryAttributes::Supervisor) { self.set_user_supervisor(false); } else { self.set_user_supervisor(true); @@ -215,7 +215,7 @@ impl crate::arch::PageTableEntry for PageTableEntryX64 { #[cfg(feature = "mm_supv")] if !self.user_supervisor() { - attributes |= MemoryAttributes::SpecialPurpose; + attributes |= MemoryAttributes::Supervisor; } attributes From f79f1c9daa935b257b4a021ab786d6ade5f9059a Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Thu, 5 Mar 2026 11:08:57 -0800 Subject: [PATCH 6/9] add unsafe elsewhere --- src/x64.rs | 59 ++++++++++++++++++++-------------------------- src/x64/structs.rs | 4 +++- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/x64.rs b/src/x64.rs index 022ca48..cb9a3a4 100644 --- a/src/x64.rs +++ b/src/x64.rs @@ -107,48 +107,41 @@ pub(crate) fn invalidate_tlb(va: VirtualAddress) { } pub fn disable_write_protection() -> u64 { - // SAFETY: This function is unsafe because it modifies control registers to disable write protection. The caller must ensure that this is done in a safe context, such as when the current code is running at a high enough privilege level and that interrupts are disabled to prevent - // other code from running while write protection is disabled. + let mut _cr0 = 0u64; + // SAFETY: Reading CR0 requires sufficient privilege level. The caller must ensure that this is done in a safe + // context and that interrupts are disabled to prevent other code from running while write protection is disabled. + #[cfg(all(not(test), target_arch = "x86_64"))] unsafe { - let mut _cr0 = 0u64; - // Read CR0 if this is on a real system - #[cfg(all(not(test), target_arch = "x86_64"))] - { - asm!("mov {}, cr0", out(reg) _cr0); - } - - // Clear the Write Protect bit (bit 16) - let _new_cr0 = _cr0 & !(1 << 16); - // Write back to CR0 - #[cfg(all(not(test), target_arch = "x86_64"))] - { - asm!("mov cr0, {}", in(reg) _new_cr0); - } + asm!("mov {}, cr0", out(reg) _cr0); + } - _cr0 + // Clear the Write Protect bit (bit 16) + let _new_cr0 = _cr0 & !(1 << 16); + // SAFETY: Writing CR0 to disable write protection. See above safety comment. + #[cfg(all(not(test), target_arch = "x86_64"))] + unsafe { + asm!("mov cr0, {}", in(reg) _new_cr0); } + + _cr0 } pub fn enable_write_protection(cr0: u64) { - // SAFETY: This function is unsafe because it modifies control registers to - // enable write protection. It is only supposed to be used when writing to - // page tables in mm mode when the tables are marked as read only. + let mut _current_cr0 = 0u64; + // SAFETY: Reading CR0 requires sufficient privilege level. This is only used when writing to page tables in + // mm mode when the tables are marked as read only. + #[cfg(all(not(test), target_arch = "x86_64"))] unsafe { - let mut _current_cr0 = 0u64; - // Read CR0 - #[cfg(all(not(test), target_arch = "x86_64"))] - { - asm!("mov {}, cr0", out(reg) _current_cr0); - } + asm!("mov {}, cr0", out(reg) _current_cr0); + } - // Set the Write Protect bit (bit 16) - let _new_cr0 = _current_cr0 | (cr0 & (1 << 16)); + // Set the Write Protect bit (bit 16) + let _new_cr0 = _current_cr0 | (cr0 & (1 << 16)); - // Write back to CR0 - #[cfg(all(not(test), target_arch = "x86_64"))] - { - asm!("mov cr0, {}", in(reg) _new_cr0); - } + // SAFETY: Writing CR0 to restore write protection. See above safety comment. + #[cfg(all(not(test), target_arch = "x86_64"))] + unsafe { + asm!("mov cr0, {}", in(reg) _new_cr0); } } diff --git a/src/x64/structs.rs b/src/x64/structs.rs index dfe0851..33781b5 100644 --- a/src/x64/structs.rs +++ b/src/x64/structs.rs @@ -9,8 +9,10 @@ use crate::{ MemoryAttributes, PtError, structs::{PageLevel, PhysicalAddress, VirtualAddress}, - x64::{PD, PDP, PML4, PML5, PT, invalidate_tlb, disable_write_protection, enable_write_protection}, + x64::{PD, PDP, PML4, PML5, PT, invalidate_tlb}, }; +#[cfg(feature = "mm_supv")] +use crate::x64::{disable_write_protection, enable_write_protection}; use bitfield_struct::bitfield; use core::ptr::write_volatile; From 401b8f01961438958c23aef83f7263b5415a7f63 Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Wed, 11 Mar 2026 03:17:51 -0700 Subject: [PATCH 7/9] fmp --- src/x64/structs.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/x64/structs.rs b/src/x64/structs.rs index 33781b5..a6004d0 100644 --- a/src/x64/structs.rs +++ b/src/x64/structs.rs @@ -6,13 +6,13 @@ //! //! SPDX-License-Identifier: Apache-2.0 //! +#[cfg(feature = "mm_supv")] +use crate::x64::{disable_write_protection, enable_write_protection}; use crate::{ MemoryAttributes, PtError, structs::{PageLevel, PhysicalAddress, VirtualAddress}, x64::{PD, PDP, PML4, PML5, PT, invalidate_tlb}, }; -#[cfg(feature = "mm_supv")] -use crate::x64::{disable_write_protection, enable_write_protection}; use bitfield_struct::bitfield; use core::ptr::write_volatile; @@ -83,7 +83,6 @@ impl PageTableEntryX64 { self.set_read_write(true); } - #[cfg(feature = "mm_supv")] if attributes.contains(MemoryAttributes::Supervisor) { self.set_user_supervisor(false); From 683934f4095704e75dc57732de7d603ce2222d2a Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Wed, 11 Mar 2026 03:21:26 -0700 Subject: [PATCH 8/9] tests --- src/x64/structs.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/x64/structs.rs b/src/x64/structs.rs index a6004d0..81c171a 100644 --- a/src/x64/structs.rs +++ b/src/x64/structs.rs @@ -415,4 +415,58 @@ mod tests { // Should not panic or error let _ = entry.dump_entry(va, level); } + + #[test] + fn test_disable_write_protection_returns_zero_in_test_mode() { + // In test mode the inline asm is compiled out, so CR0 is always 0. + // This exercises the bit-manipulation path: clearing bit 16 of 0 is still 0. + use crate::x64::disable_write_protection; + let cr0 = disable_write_protection(); + assert_eq!(cr0, 0, "In test mode CR0 should be zero (asm compiled out)"); + } + + #[test] + fn test_enable_write_protection_does_not_panic() { + // In test mode the inline asm is compiled out, so enable_write_protection + // just runs the bit-manipulation logic with _current_cr0 = 0. + // Verify it completes without panicking for various input values. + use crate::x64::enable_write_protection; + enable_write_protection(0); + enable_write_protection(1 << 16); // WP bit set + enable_write_protection(u64::MAX); // all bits set + } + + #[test] + fn test_disable_then_enable_round_trip() { + // Verify the round-trip: disable returns the original CR0, and + // enable accepts it back without error. + use crate::x64::{disable_write_protection, enable_write_protection}; + let saved_cr0 = disable_write_protection(); + enable_write_protection(saved_cr0); + } + + #[test] + fn test_write_protection_bit_manipulation_logic() { + // The WP bit is bit 16 of CR0 (0x0001_0000). + // Test the bit-manipulation logic directly, mirroring what + // disable/enable do internally. + const WP_BIT: u64 = 1 << 16; + + // disable_write_protection clears bit 16: + let cr0_with_wp = 0xDEAD_BEEF_0001_0000u64; // WP set + let cleared = cr0_with_wp & !WP_BIT; + assert_eq!(cleared & WP_BIT, 0, "WP bit should be cleared"); + assert_eq!(cleared, 0xDEAD_BEEF_0000_0000u64); + + // enable_write_protection restores bit 16 from the saved value: + let current_cr0 = 0u64; // after disable, WP is cleared + let restored = current_cr0 | (cr0_with_wp & WP_BIT); + assert_ne!(restored & WP_BIT, 0, "WP bit should be restored"); + assert_eq!(restored, WP_BIT); + + // If the original CR0 had WP cleared, enable should not set it: + let cr0_without_wp = 0xDEAD_BEEF_0000_0000u64; + let should_stay_cleared = current_cr0 | (cr0_without_wp & WP_BIT); + assert_eq!(should_stay_cleared & WP_BIT, 0, "WP bit should remain cleared"); + } } From c281658cb542aa69ad2d57492934b95e149c5f93 Mon Sep 17 00:00:00 2001 From: Kun Qin Date: Fri, 10 Apr 2026 16:15:34 -0700 Subject: [PATCH 9/9] expose the structure definitions --- src/lib.rs | 3 +++ src/x64.rs | 2 +- src/x64/structs.rs | 12 ++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fb30d35..854250e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,9 @@ pub(crate) mod arch; pub mod page_allocator; pub(crate) mod paging; pub(crate) mod structs; + +// Re-export commonly used page size constants. +pub use structs::{SIZE_1GB, SIZE_2MB, SIZE_4KB}; #[cfg(test)] #[coverage(off)] mod tests; diff --git a/src/x64.rs b/src/x64.rs index c86315c..bd4a38b 100644 --- a/src/x64.rs +++ b/src/x64.rs @@ -10,7 +10,7 @@ use core::arch::asm; use core::ptr; -mod structs; +pub mod structs; #[cfg(test)] #[coverage(off)] mod tests; diff --git a/src/x64/structs.rs b/src/x64/structs.rs index 81c171a..2dd1298 100644 --- a/src/x64/structs.rs +++ b/src/x64/structs.rs @@ -42,10 +42,14 @@ pub(crate) const FOUR_LEVEL_PDP_SELF_MAP_BASE: u64 = 0xFFFF_FFFF_FFE0_0000; pub(crate) const FOUR_LEVEL_PD_SELF_MAP_BASE: u64 = 0xFFFF_FFFF_C000_0000; pub(crate) const FOUR_LEVEL_PT_SELF_MAP_BASE: u64 = 0xFFFF_FF80_0000_0000; -pub(crate) const CR3_PAGE_BASE_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; // 40 bit - lower 12 bits for alignment - -pub(crate) const PAGE_TABLE_ENTRY_4KB_PAGE_TABLE_BASE_ADDRESS_SHIFT: u64 = 12u64; // lower 12 bits for alignment -pub(crate) const PAGE_TABLE_ENTRY_4KB_PAGE_TABLE_BASE_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; // 40 bit - lower 12 bits for alignment +pub const CR3_PAGE_BASE_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; // 40 bit - lower 12 bits for alignment + +pub const PAGE_TABLE_ENTRY_4KB_PAGE_TABLE_BASE_ADDRESS_SHIFT: u64 = 12u64; // lower 12 bits for alignment +pub const PAGE_TABLE_ENTRY_4KB_PAGE_TABLE_BASE_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; // 40 bit - lower 12 bits for alignment +/// Address mask for 2MB large page addresses (bits [51:21]). +pub const PAGE_TABLE_ENTRY_2MB_PAGE_BASE_ADDRESS_MASK: u64 = 0x000F_FFFF_FFE0_0000; +/// Address mask for 1GB huge page addresses (bits [51:30]). +pub const PAGE_TABLE_ENTRY_1GB_PAGE_BASE_ADDRESS_MASK: u64 = 0x000F_FFFF_C000_0000; #[rustfmt::skip] #[bitfield(u64)]