Memory Safety Architecture
Relevant source files
This document explains the core architecture and design principles of the memory safety system in the axptr library. It focuses on how the system provides a safe interface for kernel code to access user-space memory while preventing potential security vulnerabilities or system crashes. For details about specific pointer types, see User Space Pointers, and for information about safety mechanisms, see Safety Mechanisms.
Overview of Memory Safety Architecture
The axptr library implements a robust architecture to ensure memory operations across privilege boundaries (kernel accessing user memory) remain safe. The architecture is built around three key principles:
- Type-safe access - Using strongly-typed pointer wrappers
- Memory region validation - Ensuring pointers reference valid user memory regions
- Context-aware fault handling - Managing page faults during user memory access
Sources: src/lib.rs(L129 - L183) src/lib.rs(L219 - L254)
Core Components
The memory safety architecture consists of these fundamental components:
Component | Description | Role |
---|---|---|
UserPtr | Typed wrapper for mutable user pointers | Provides safe access to user memory with read/write permissions |
UserConstPtr | Typed wrapper for immutable user pointers | Provides safe access to user memory with read-only permissions |
AddrSpaceProvider | Trait for address space operations | Abstracts address space lookup and access control |
Memory checking functions | Safety validation utilities | Verifies memory region alignment, permissions, and availability |
Context tracking | Page fault handling mechanism | Manages page faults during user memory access |
classDiagram class UserPtr~T~ { +*mut T pointer +const ACCESS_FLAGS: MappingFlags +get() +get_as_slice() +get_as_null_terminated() } class UserConstPtr~T~ { +*const T pointer +const ACCESS_FLAGS: MappingFlags +get() +get_as_slice() +get_as_null_terminated() } class AddrSpaceProvider { <<trait>> +with_addr_space() } UserPtr --> AddrSpaceProvider : uses UserConstPtr --> AddrSpaceProvider : uses
Sources: src/lib.rs(L119 - L126) src/lib.rs(L129 - L134) src/lib.rs(L219 - L225)
Memory Access Workflow
The core workflow for safely accessing user memory follows these steps:
sequenceDiagram participant KernelCode as "Kernel Code" participant UserPtrUserConstPtr as "UserPtr/UserConstPtr" participant check_region as "check_region()" participant ACCESSING_USER_MEMflag as "ACCESSING_USER_MEM flag" participant UserMemory as "User Memory" KernelCode ->> UserPtrUserConstPtr: Request user memory access UserPtrUserConstPtr ->> check_region: Validate memory region check_region ->> check_region: Check alignment check_region ->> check_region: Verify access permissions check_region ->> check_region: Populate page tables alt Memory region valid check_region ->> UserPtrUserConstPtr: Access permitted UserPtrUserConstPtr ->> ACCESSING_USER_MEMflag: Set to true UserPtrUserConstPtr ->> UserMemory: Access memory UserPtrUserConstPtr ->> ACCESSING_USER_MEMflag: Set to false UserPtrUserConstPtr ->> KernelCode: Return memory reference else Memory region invalid check_region ->> UserPtrUserConstPtr: Return EFAULT UserPtrUserConstPtr ->> KernelCode: Propagate error end
Sources: src/lib.rs(L31 - L54) src/lib.rs(L11 - L29) src/lib.rs(L175 - L183)
Memory Region Validation
Before any user memory access, a series of validation steps ensure memory safety:
- Alignment Checking: Ensures the pointer is properly aligned for the requested type
- Access Permission Verification: Checks that the memory region has appropriate read/write permissions
- Page Table Population: Ensures that all required pages are mapped in the address space
flowchart TD start["Memory Access Request"] align["Check Alignment"] error["Return EFAULT"] perms["Check Access Permissions"] populate["Populate Page Tables"] access["Set ACCESSING_USER_MEM flag"] read["Access Memory"] clear["Clear ACCESSING_USER_MEM flag"] finish["Return Result"] access --> read align --> error align --> perms clear --> finish perms --> error perms --> populate populate --> access populate --> error read --> clear start --> align
Sources: src/lib.rs(L31 - L54) src/lib.rs(L110 - L117)
Context-Aware Page Fault Handling
A key aspect of the memory safety architecture is handling page faults during user memory access. This is accomplished through the ACCESSING_USER_MEM
flag, which indicates when the kernel is accessing user memory.
stateDiagram-v2 state AccessingUser { [*] --> Reading Reading --> PageFault : Page not present PageFault --> Reading : Handle fault safely } [*] --> Normal Normal --> AccessingUser : set ACCESSING_USER_MEM = true AccessingUser --> Normal : set ACCESSING_USER_MEM = false
The architecture uses a per-CPU variable to track this state:
#[percpu::def_percpu]
static mut ACCESSING_USER_MEM: bool = false;
When set to true, the OS knows that any page faults occurring should be handled differently than regular kernel page faults, preventing kernel crashes from invalid user memory accesses.
Sources: src/lib.rs(L11 - L29) src/lib.rs(L22 - L29)
Null-Terminated Data Handling
The architecture includes specialized handling for null-terminated data like C strings, which is particularly important for OS interfaces:
flowchart TD request["Request null-terminated data"] check["check_null_terminated()"] page["Process page by page"] scan["Scan for null terminator"] return["Return validated slice"] check --> page page --> scan request --> check scan --> return
This process efficiently handles null-terminated structures while maintaining safety guarantees by:
- Validating pages incrementally as needed
- Handling page faults appropriately during traversal
- Returning the correctly sized slice or string when the null terminator is found
Sources: src/lib.rs(L56 - L107) src/lib.rs(L202 - L217) src/lib.rs(L280 - L303)
Security Implications
The memory safety architecture provides critical security guarantees:
- Protection against invalid memory access: Prevents kernel crashes from accessing invalid user memory
- Defense against privilege escalation: Ensures kernel code can only access user memory with proper permissions
- Safety from malicious user input: Validates user-provided pointers before use
By combining strong typing, rigorous validation, and context-aware fault handling, the architecture creates a comprehensive barrier against memory-related security vulnerabilities when crossing privilege boundaries.
Sources: src/lib.rs(L31 - L54) src/lib.rs(L11 - L29)
Integration with Operating System
The architecture is designed to integrate with operating systems through:
flowchart TD subgraph subGraph1["Process Management"] errnoSys["axerrno"] percpu["percpu"] end subgraph subGraph0["Memory Subsystem"] mmSys["axmm"] memAddr["memory_addr"] pageTable["page_table_multiarch"] end axptr["axptr"] memorySubsystem["Memory Subsystem"] os["Operating System Kernel"] processManagement["Process Management"] axptr --> errnoSys axptr --> memAddr axptr --> mmSys axptr --> pageTable axptr --> percpu memorySubsystem --> os processManagement --> os
The architecture's dependencies enable it to work with the underlying memory management system while providing a consistent error handling mechanism through Linux-compatible error codes.
Sources: Cargo.toml(L7 - L12)