A Rust library for hardware-enforced memory protection using Intel PKU (Memory Protection Keys). This library provides a type-safe, RAII-based interface to manage memory access permissions at the thread level with extremely low overhead.
Traditional memory protection using mprotect requires system calls and TLB flushes, which are expensive. Intel PKU allows userspace applications to change access permissions of memory pages within a few CPU cycles by modifying the PKRU register.
pkeyguard-rs leverages Rust's ownership model and borrow checker to ensure that:
Memory is only accessible when the appropriate guard is in scope.
Read/Write permissions are enforced both at compile-time (via Rust references) and runtime (via Hardware CPU exceptions).
Zero-cost Abstractions: Direct mapping of PKU instructions to Rust's type system.
RAII Guards: Access rights are automatically revoked when the guard goes out of scope.
Defense in Depth: Even if an attacker bypasses the borrow checker with unsafe code, the hardware (Intel PKU) will trigger a Segmentation Fault if the permissions are not set correctly.
Thread-Local Isolation: Permissions are managed per-thread, as the PKRU register is part of the thread context.
Example: Secure Memory Access
use pkeyguard::{RegionGuard, PkeyGuard, AccessPermissions, PkeyPermissions, allocator};
fn main() -> Result<(), RuntimeError> {
// 1. Allocate a memory region via mmap/mprotect
// Default is ReadWrite, but we will control access via PKU later.
let mut region = RegionGuard::<allocator::Mmap, u32>::new(0, AccessPermissions::ReadWrite)
.map_err(RuntimeError::MprotectError)?;
// 2. Create a new Protection Key (Pkey)
// Initially set to NoAccess for maximum security.
let pkey = PkeyGuard::new(PkeyPermissions::NoAccess)
.map_err(RuntimeError::MprotectError)?;
// 3. Associate the region with the Protection Key
// From here, the hardware enforces access based on the pkey's state.
let mut associated_region = pkey
.associate::<PkeyPermissions::NoAccess>(&mut region)
.map_err(RuntimeError::MprotectError)?;
// 4. Write access block
{
// Temporarily elevate permissions to ReadWrite
let write_guard = associated_region
.set_access_rights::<PkeyPermissions::ReadWrite>()
.map_err(RuntimeError::MprotectError)?;
let mut mut_ref_guard = write_guard.mut_ref_guard()
.map_err(|e| RuntimeError::PkeyGuardError(e))?;
*mut_ref_guard = 123; // Safe write
println!("Value written: {}", *mut_ref_guard);
} // write_guard drops here, pkey reverts to NoAccess automatically
// 5. Read-only access block
{
let read_guard = associated_region
.set_access_rights::<PkeyPermissions::ReadOnly>()
.map_err(RuntimeError::MprotectError)?;
let ref_guard = read_guard.ref_guard()
.map_err(|e| RuntimeError::PkeyGuardError(e))?;
println!("Value read: {}", *ref_guard);
// Hardware Enforcement Example:
// Even if we use unsafe code to bypass the borrow checker,
// the CPU will trigger a Segmentation Fault because the PKRU register
// is set to ReadOnly.
/*
unsafe {
let mut_ref = ref_guard.ptr() as *mut u32;
*mut_ref = 789; // CRASH: Segmentation Fault (Hardware-enforced)
}
*/
}
Ok(())
}RegionGuard: Wraps a memory region allocated via mmap.PkeyGuard: Manages the lifecycle of an Intel PKU key (0-15).Association: Uses pkey_mprotect to tag specific memory pages with a protection key.set_access_rights: Executes the WRPKRU instruction to modify the current thread's access rights. This is much faster than a system call.
Mmap is the recommended allocator for protected regions because mmap returns page-aligned memory and accepts page-level protection flags directly.
Jmalloc is also available for jemalloc-backed regions. It allocates a page-aligned block, rounds the protected length up to the system page size, and applies mprotect to that page-sized region. This page alignment is required because mprotect and pkey_mprotect operate on page boundaries.
The library uses the Typestate Pattern. You cannot obtain a mutable reference to the protected data unless you have explicitly transitioned the associated_region into a ReadWrite state. This prevents logical errors where memory might be left in an unprotected state.
Run the integration tests with:
cargo test --testsThe integration tests always exercise the mprotect, allocator, and RegionGuard paths, including a regression test that verifies Jmalloc regions are page-aligned, page-sized, and accepted by mprotect. MPK-specific tests first check whether the current environment supports MPK by verifying x86 PKU/OSPKE CPU support and trying pkey_alloc; they return early when MPK is unavailable.
Run the sample program without MPK workloads:
cargo runRun the sample program with MPK workloads enabled when the environment supports them:
cargo run -- --mpkIn the QEMU environment started by ~/qemu/boot.sh, use cargo run -- --mpk to run the MPK sample workloads. If MPK is unavailable, the program prints a skip message instead of executing MPK instructions.
- CPU: Intel Skylake Scalable or newer (supporting the PKU feature).
- OS: Linux kernel 4.9 or later with CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS enabled.
This project is licensed under the MIT License.