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}