Skip to main content

strat9_kernel/memory/
userslice.rs

1//! Userspace pointer validation for Strat9-OS.
2//!
3//! The `UserSlice` pattern (inspired by RedoxOS `usercopy.rs`) ensures the
4//! kernel never dereferences a raw userspace pointer without first checking:
5//!
6//! 1. **Range**: The entire region lies in the user half (< `USER_SPACE_END`)
7//! 2. **Overflow**: `base + len` doesn't wrap around
8//! 3. **Mapping**: Every page in the region is present in the *active* page
9//!    tables with the requested permissions (read or write)
10//!
11//! After validation, `UserSlice` provides safe copy operations that transfer
12//! data between userspace and kernel buffers.
13//!
14//! # Example
15//!
16//! ```ignore
17//! // In a syscall handler:
18//! let user_buf = UserSliceRead::new(buf_ptr, buf_len)?;
19//! let mut kernel_buf = [0u8; 256];
20//! let n = user_buf.copy_to(&mut kernel_buf)?;
21//! ```
22
23use crate::{
24    arch::xshim::{PageTableFlags, Translate, VirtAddr},
25    syscall::error::SyscallError,
26};
27use alloc::vec::Vec;
28
29// ===========================================================================
30// UserPod : marker trait for types safe to read/write via UserSlice
31// ===========================================================================
32
33/// Marker trait for types whose every bit pattern is valid (POD / plain old data).
34///
35/// This is an **unsafe** trait: implementors must guarantee that every possible
36/// bit pattern of the type is a valid value.  This prevents reading into bools,
37/// enums with invalid discriminants, references, pointers, or other non-POD types.
38///
39/// # Implementors
40///
41/// Only implement this for types where *all* bit patterns are valid:
42/// primitive integer types, `#[repr(C)]` structs of `UserPod` fields, etc.
43/// Never implement for `bool`, `char`, enums, references, or pointers.
44pub unsafe trait UserPod: Copy + 'static {}
45
46unsafe impl UserPod for u8 {}
47unsafe impl UserPod for u16 {}
48unsafe impl UserPod for u32 {}
49unsafe impl UserPod for u64 {}
50unsafe impl UserPod for i8 {}
51unsafe impl UserPod for i16 {}
52unsafe impl UserPod for i32 {}
53unsafe impl UserPod for i64 {}
54unsafe impl UserPod for usize {}
55unsafe impl UserPod for isize {}
56
57// ===========================================================================
58// UserAccessGuard : RAII guard for SMAP/AC stac/clac
59// ===========================================================================
60
61/// RAII guard that disables Supervisor Mode Access Prevention (SMAP) on
62/// creation and re-enables it on drop.  Ensures AC is restored on all
63/// code paths including panics and early returns.
64///
65/// # Usage
66///
67/// ```ignore
68/// let _guard = UserAccessGuard::new();
69/// // ... access user memory safely ...
70/// // AC is re-enabled when _guard is dropped.
71/// ```
72pub struct UserAccessGuard {
73    _private: (),
74}
75
76impl UserAccessGuard {
77    /// Disable SMAP (set AC flag) and return a guard that will re-enable it on drop.
78    #[inline]
79    pub fn new() -> Self {
80        crate::arch::stac();
81        UserAccessGuard { _private: () }
82    }
83}
84
85impl Drop for UserAccessGuard {
86    #[inline]
87    fn drop(&mut self) {
88        crate::arch::clac();
89    }
90}
91
92// UserAccessGuard is per-CPU state and must not be sent across threads.
93impl !Send for UserAccessGuard {}
94impl !Sync for UserAccessGuard {}
95
96/// End of user-accessible virtual address space.
97///
98/// On x86_64 with 4-level paging, canonical user addresses are
99/// `0x0000_0000_0000_0000 ..= 0x0000_7FFF_FFFF_FFFF`.
100/// Anything at or above this boundary is kernel space.
101/// Upper bound (exclusive semantics at use sites) of the user virtual range.
102///
103/// Strat9 userspace is statically linked in the higher-half window
104/// (0xFFFFFFFF80000000, same convention as the kernel image). Each process
105/// gets a PRIVATE copy of the PML4[511] PDP with the kernel-image slot
106/// removed (see address_space::new_user), so user mappings can span the full
107/// canonical range; kernel isolation comes from U/S page bits and the private
108/// window, not from an address split.
109pub const USER_SPACE_END: u64 = 0xFFFF_FFFF_FFFF_FFFF;
110
111/// Maximum length allowed for a single UserSlice (16 MiB).
112///
113/// Prevents a malicious userspace from causing the kernel to walk
114/// millions of page table entries or allocate huge kernel buffers.
115const MAX_USER_SLICE_LEN: usize = 16 * 1024 * 1024;
116
117/// Errors that can occur when constructing or using a `UserSlice`.
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum UserSliceError {
120    /// The pointer is null.
121    NullPointer,
122    /// The region extends into or past kernel address space.
123    KernelAddress,
124    /// `base + len` overflows (wraps around the address space).
125    Overflow,
126    /// The region exceeds the maximum allowed length.
127    TooLong,
128    /// One or more pages in the region are not mapped.
129    NotMapped,
130    /// The mapping lacks the required permission (e.g. not writable).
131    PermissionDenied,
132    /// The slice is too small for the requested operation.
133    InvalidSize,
134}
135
136impl From<UserSliceError> for SyscallError {
137    /// Performs the from operation.
138    fn from(e: UserSliceError) -> Self {
139        match e {
140            UserSliceError::NullPointer => SyscallError::Fault,
141            UserSliceError::KernelAddress => SyscallError::Fault,
142            UserSliceError::Overflow => SyscallError::Fault,
143            UserSliceError::TooLong => SyscallError::InvalidArgument,
144            UserSliceError::NotMapped => SyscallError::Fault,
145            UserSliceError::PermissionDenied => SyscallError::Fault,
146            UserSliceError::InvalidSize => SyscallError::InvalidArgument,
147        }
148    }
149}
150
151/// Permission requirements for a user memory region.
152#[derive(Debug, Clone, Copy)]
153enum Access {
154    /// Read-only access (the kernel reads from userspace).
155    Read,
156    /// Write access (the kernel writes to userspace).
157    Write,
158}
159
160/// Validate that a user memory region `[base, base+len)` is:
161/// - entirely within the user address space
162/// - mapped with the required permissions in the active page tables
163///
164/// Returns `Ok(())` on success, or a `UserSliceError` describing the problem.
165fn validate_user_region(base: u64, len: usize, access: Access) -> Result<(), UserSliceError> {
166    if len == 0 {
167        return Ok(());
168    }
169
170    if base == 0 {
171        return Err(UserSliceError::NullPointer);
172    }
173
174    if len > MAX_USER_SLICE_LEN {
175        return Err(UserSliceError::TooLong);
176    }
177
178    let end = base
179        .checked_add(len as u64)
180        .ok_or(UserSliceError::Overflow)?;
181
182    if base >= USER_SPACE_END || end > USER_SPACE_END {
183        return Err(UserSliceError::KernelAddress);
184    }
185
186    // Walk every page in the region and check the page tables.
187    let required_flags = match access {
188        Access::Read => PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE,
189        Access::Write => {
190            PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE | PageTableFlags::WRITABLE
191        }
192    };
193
194    check_pages_mapped(base, len, required_flags)
195}
196
197/// Walk the active page tables to verify that every 4 KiB page covering
198/// `[base, base+len)` is mapped with at least `required_flags`.
199fn check_pages_mapped(
200    base: u64,
201    len: usize,
202    required_flags: PageTableFlags,
203) -> Result<(), UserSliceError> {
204    use crate::x86_crate_shim::{
205        registers::control::Cr3,
206        structures::paging::{OffsetPageTable, PageTable},
207    };
208
209    let hhdm = crate::memory::hhdm_offset();
210    let phys_offset = VirtAddr::new(hhdm);
211
212    // Read the active CR3 to get the current process's page table.
213    let (l4_frame, _) = Cr3::read();
214    let l4_phys = l4_frame.start_address().as_u64();
215    let l4_virt = VirtAddr::new(l4_phys + hhdm);
216
217    // SAFETY: The HHDM mapping is always valid for physical RAM.
218    // We only read the page tables; no mutation.
219    let mapper =
220        unsafe { OffsetPageTable::new(&mut *l4_virt.as_mut_ptr::<PageTable>(), phys_offset) };
221
222    let page_size: u64 = 4096;
223    let start_page = base & !0xFFF; // Round down to page boundary
224    let end_addr = base + len as u64;
225
226    let mut addr = start_page;
227    while addr < end_addr {
228        let vaddr = VirtAddr::new(addr);
229
230        // Use the x86_64 crate's full translate to get the mapped frame + flags.
231        use crate::arch::xshim::TranslateResult;
232        match mapper.translate(vaddr) {
233            TranslateResult::Mapped { flags, .. } => {
234                // Check that the mapping has all required flags
235                if !flags.contains(required_flags) {
236                    log::trace!(
237                        "UserSlice: page {:#x} missing flags: have {:?}, need {:?}",
238                        addr,
239                        flags,
240                        required_flags
241                    );
242                    return Err(UserSliceError::PermissionDenied);
243                }
244            }
245            TranslateResult::NotMapped | TranslateResult::InvalidFrameAddress(_) => {
246                log::trace!("UserSlice: page {:#x} not mapped", addr);
247                return Err(UserSliceError::NotMapped);
248            }
249        }
250
251        addr += page_size;
252    }
253
254    Ok(())
255}
256
257// ============================================================================
258// UserSliceRead : validated read-only access to user memory
259// ============================================================================
260
261/// A validated read-only reference to a user-space memory region.
262///
263/// Construction validates that `[ptr, ptr+len)` is mapped and readable
264/// by the current process. After construction, the kernel can safely
265/// read from this region.
266///
267/// **Note**: The mapping could theoretically be changed by another thread
268/// between validation and use. On our single-core kernel this can't happen
269/// because we don't preempt during a syscall handler (interrupts are
270/// re-enabled but the scheduler won't remove our mappings). For SMP this
271/// would need additional protection (e.g. pinning pages).
272pub struct UserSliceRead {
273    ptr: u64,
274    len: usize,
275}
276
277impl UserSliceRead {
278    /// Create a new validated read-only user slice.
279    ///
280    /// Fails if:
281    /// - `ptr` is null
282    /// - `ptr + len` overflows or crosses into kernel space
283    /// - Any page in the range is not mapped or not user-accessible
284    pub fn new(ptr: u64, len: usize) -> Result<Self, UserSliceError> {
285        validate_user_region(ptr, len, Access::Read)?;
286        Ok(UserSliceRead { ptr, len })
287    }
288
289    /// The length of the validated region in bytes.
290    pub fn len(&self) -> usize {
291        self.len
292    }
293
294    /// Whether the region is empty (zero length).
295    pub fn is_empty(&self) -> bool {
296        self.len == 0
297    }
298
299    /// Copy validated user data into a kernel-owned `Vec<u8>`.
300    ///
301    /// Returns a vector containing a copy of the user memory.
302    pub fn read_to_vec(&self) -> Vec<u8> {
303        if self.len == 0 {
304            return Vec::new();
305        }
306
307        let mut buf = alloc::vec![0u8; self.len];
308        let _guard = UserAccessGuard::new();
309        // SAFETY: We validated that [ptr, ptr+len) is mapped and user-readable.
310        // UserAccessGuard ensures SMAP is disabled for the duration.
311        unsafe {
312            core::ptr::copy_nonoverlapping(self.ptr as *const u8, buf.as_mut_ptr(), self.len);
313        }
314        buf
315    }
316
317    /// Copy validated user data into a kernel buffer.
318    ///
319    /// Copies `min(self.len, dest.len())` bytes and returns how many were copied.
320    pub fn copy_to(&self, dest: &mut [u8]) -> usize {
321        let n = core::cmp::min(self.len, dest.len());
322        if n == 0 {
323            return 0;
324        }
325
326        let _guard = UserAccessGuard::new();
327        // SAFETY: We validated that [ptr, ptr+n) is mapped and user-readable.
328        // n <= self.len, so we stay within the validated region.
329        unsafe {
330            core::ptr::copy_nonoverlapping(self.ptr as *const u8, dest.as_mut_ptr(), n);
331        }
332        n
333    }
334
335    /// Read a single byte from the user slice at the given offset.
336    pub fn read_u8(&self, offset: usize) -> Result<u8, UserSliceError> {
337        if offset >= self.len {
338            return Err(UserSliceError::InvalidSize);
339        }
340        let _guard = UserAccessGuard::new();
341        // SAFETY: We validated that [ptr, ptr+len) is mapped and user-readable.
342        let val = unsafe { core::ptr::read_unaligned((self.ptr + offset as u64) as *const u8) };
343        Ok(val)
344    }
345
346    /// Read a u64 from the user slice at the given offset.
347    pub fn read_u64(&self, offset: usize) -> Result<u64, UserSliceError> {
348        if offset + 8 > self.len {
349            return Err(UserSliceError::InvalidSize);
350        }
351        let _guard = UserAccessGuard::new();
352        // SAFETY: We validated that [ptr, ptr+len) is mapped and user-readable.
353        let val = unsafe { core::ptr::read_unaligned((self.ptr + offset as u64) as *const u64) };
354        Ok(val)
355    }
356
357    /// Read a value of type T from the user slice.
358    ///
359    /// T must implement `UserPod` (sealed trait for types where every bit
360    /// pattern is valid). This prevents reading into bools, enums with
361    /// invalid discriminants, references, or other non-POD types.
362    pub fn read_val<T: UserPod>(&self) -> Result<T, UserSliceError> {
363        if self.len < core::mem::size_of::<T>() {
364            return Err(UserSliceError::InvalidSize);
365        }
366        let _guard = UserAccessGuard::new();
367        // SAFETY: We validated that [ptr, ptr+len) is mapped and user-readable.
368        // T: UserPod guarantees all bit patterns are valid.
369        let val = unsafe { core::ptr::read_unaligned(self.ptr as *const T) };
370        Ok(val)
371    }
372
373    /// Get the raw pointer (for logging/debugging only).
374    pub fn as_ptr(&self) -> u64 {
375        self.ptr
376    }
377}
378
379// ============================================================================
380// UserSliceWrite : validated write access to user memory
381// ============================================================================
382
383/// A validated writable reference to a user-space memory region.
384///
385/// Construction validates that `[ptr, ptr+len)` is mapped, user-accessible,
386/// and writable. After construction, the kernel can safely write to this region.
387pub struct UserSliceWrite {
388    ptr: u64,
389    len: usize,
390}
391
392impl UserSliceWrite {
393    /// Create a new validated writable user slice.
394    ///
395    /// Fails if:
396    /// - `ptr` is null
397    /// - `ptr + len` overflows or crosses into kernel space
398    /// - Any page in the range is not mapped, not user-accessible, or not writable
399    pub fn new(ptr: u64, len: usize) -> Result<Self, UserSliceError> {
400        validate_user_region(ptr, len, Access::Write)?;
401        Ok(UserSliceWrite { ptr, len })
402    }
403
404    /// The length of the validated region in bytes.
405    pub fn len(&self) -> usize {
406        self.len
407    }
408
409    /// Whether the region is empty (zero length).
410    pub fn is_empty(&self) -> bool {
411        self.len == 0
412    }
413
414    /// Copy kernel data into validated user memory.
415    ///
416    /// Copies `min(src.len(), self.len)` bytes and returns how many were copied.
417    pub fn copy_from(&self, src: &[u8]) -> usize {
418        let n = core::cmp::min(src.len(), self.len);
419        if n == 0 {
420            return 0;
421        }
422
423        let _guard = UserAccessGuard::new();
424        // SAFETY: We validated that [ptr, ptr+n) is mapped and user-writable.
425        // n <= self.len, so we stay within the validated region.
426        unsafe {
427            core::ptr::copy_nonoverlapping(src.as_ptr(), self.ptr as *mut u8, n);
428        }
429        n
430    }
431
432    /// Zero-fill the validated user memory region.
433    pub fn zero(&self) {
434        if self.len == 0 {
435            return;
436        }
437
438        let _guard = UserAccessGuard::new();
439        // SAFETY: We validated that [ptr, ptr+len) is mapped and user-writable.
440        unsafe {
441            core::ptr::write_bytes(self.ptr as *mut u8, 0, self.len);
442        }
443    }
444
445    /// Get the raw pointer (for logging/debugging only).
446    pub fn as_ptr(&self) -> u64 {
447        self.ptr
448    }
449}
450
451// ============================================================================
452// UserSliceReadWrite : validated read+write access to user memory
453// ============================================================================
454
455/// A validated read-write reference to a user-space memory region.
456///
457/// Construction validates that `[ptr, ptr+len)` is mapped, user-accessible,
458/// and writable (writable implies readable on x86_64).
459pub struct UserSliceReadWrite {
460    ptr: u64,
461    len: usize,
462}
463
464impl UserSliceReadWrite {
465    /// Create a new validated read-write user slice.
466    pub fn new(ptr: u64, len: usize) -> Result<Self, UserSliceError> {
467        validate_user_region(ptr, len, Access::Write)?;
468        Ok(UserSliceReadWrite { ptr, len })
469    }
470
471    /// The length of the validated region in bytes.
472    pub fn len(&self) -> usize {
473        self.len
474    }
475
476    /// Whether the region is empty (zero length).
477    pub fn is_empty(&self) -> bool {
478        self.len == 0
479    }
480
481    /// Copy validated user data into a kernel buffer.
482    pub fn copy_to(&self, dest: &mut [u8]) -> usize {
483        let n = core::cmp::min(self.len, dest.len());
484        if n == 0 {
485            return 0;
486        }
487        let _guard = UserAccessGuard::new();
488        // SAFETY: Validated as writable (which implies readable on x86_64).
489        unsafe {
490            core::ptr::copy_nonoverlapping(self.ptr as *const u8, dest.as_mut_ptr(), n);
491        }
492        n
493    }
494
495    /// Copy kernel data into validated user memory.
496    pub fn copy_from(&self, src: &[u8]) -> usize {
497        let n = core::cmp::min(src.len(), self.len);
498        if n == 0 {
499            return 0;
500        }
501        let _guard = UserAccessGuard::new();
502        // SAFETY: Validated as writable.
503        unsafe {
504            core::ptr::copy_nonoverlapping(src.as_ptr(), self.ptr as *mut u8, n);
505        }
506        n
507    }
508
509    /// Write a value of type T to the user slice.
510    ///
511    /// T must implement `UserPod` (sealed trait for types where every bit
512    /// pattern is valid). This prevents writing non-POD types to user memory.
513    pub fn write_val<T: UserPod>(&self, val: &T) -> Result<(), UserSliceError> {
514        if self.len < core::mem::size_of::<T>() {
515            return Err(UserSliceError::InvalidSize);
516        }
517        let _guard = UserAccessGuard::new();
518        // SAFETY: We validated that [ptr, ptr+len) is mapped and user-writable.
519        // T: UserPod guarantees all bit patterns are valid.
520        unsafe {
521            core::ptr::write_unaligned(self.ptr as *mut T, *val);
522        }
523        Ok(())
524    }
525
526    /// Get the raw pointer (for logging/debugging only).
527    pub fn as_ptr(&self) -> u64 {
528        self.ptr
529    }
530}