Skip to main content

strat9_kernel/
kaslr.rs

1//! Kernel Address Space Layout Randomization (KASLR).
2//!
3//! Generates per-boot random offsets for:
4//! - Userspace mmap base address
5//! - Userspace stack base address
6//! - ELF PIE loading base address
7//!
8//! Offsets are generated once at boot from the entropy pool and remain
9//! constant for the lifetime of the boot. Each process receives its own
10//! randomized layout derived from these base offsets.
11//!
12//! # Entropy requirements
13//!
14//! `init()` calls `entropy::fill_random()` which blocks until the pool
15//! has accumulated at least 64 bytes of entropy. On a live system with
16//! keyboard/timer interrupts this takes < 1 ms. On QEMU without RDRAND
17//! the pool seeds from TSC and accumulates quickly via IRQ noise.
18
19use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
20
21/// Whether KASLR has been initialized.
22static INITIALIZED: AtomicBool = AtomicBool::new(false);
23
24/// Randomized mmap base offset (added to MMAP_BASE).
25static MMAP_BASE_OFFSET: AtomicU64 = AtomicU64::new(0);
26
27/// Randomized user stack base offset (added to USER_STACK_BASE).
28static STACK_BASE_OFFSET: AtomicU64 = AtomicU64::new(0);
29
30/// Randomized PIE base offset (added to PIE_BASE_ADDR).
31static PIE_BASE_OFFSET: AtomicU64 = AtomicU64::new(0);
32
33/// Initialize KASLR offsets from the entropy pool. Called once at boot.
34///
35/// Blocks on `fill_random()` until the entropy pool is seeded.
36pub fn init() {
37    if INITIALIZED.load(Ordering::Relaxed) {
38        return;
39    }
40
41    // Use RDTSC for KASLR seed (avoids entropy pool hang during early boot)
42    let (lo, hi): (u32, u32);
43    unsafe {
44        core::arch::asm!("rdtsc", out("eax") lo, out("edx") hi, options(nostack, nomem));
45    }
46    let seed = ((hi as u64) << 32) | lo as u64;
47    let r0 = seed.wrapping_mul(6364136223846793005);
48    let r1 = (seed >> 32) as u8;
49    let r2 = (seed >> 40) as u8;
50
51    // Mmap base offset: 0 .. 256 MiB, aligned to 4 KiB.
52    // Use bitmask instead of modulo to avoid modulo bias.
53    let mmap_off = (r0 & 0x0FFF_FFFF) & !0xFFF;
54    MMAP_BASE_OFFSET.store(mmap_off, Ordering::Relaxed);
55
56    // Stack base offset: 0 .. 255 pages = 0 ~ 1 MiB.
57    // r1 is a byte (0..255); multiply by page size for byte offset.
58    let stack_off = (r1 as u64) * 4096;
59    STACK_BASE_OFFSET.store(stack_off, Ordering::Relaxed);
60
61    // PIE base offset: 0 .. 15 * 2 MiB = 0 ~ 30 MiB, aligned to 2 MiB.
62    // Mask to 4 bits (0..15) then shift left by 21 bits (2 MiB).
63    let pie_off = ((r2 as u64) & 0x0F) << 21;
64    PIE_BASE_OFFSET.store(pie_off, Ordering::Relaxed);
65
66    INITIALIZED.store(true, Ordering::Release);
67
68    crate::serial_println!(
69        "[KASLR] mmap_base_off={:#x} stack_off={:#x} pie_off={:#x}",
70        mmap_off,
71        stack_off,
72        pie_off
73    );
74}
75
76/// Get the randomized mmap base address.
77///
78/// # Panics
79/// Panics if `init()` has not been called yet.
80#[inline]
81pub fn mmap_base() -> u64 {
82    debug_assert!(
83        INITIALIZED.load(Ordering::Relaxed),
84        "kaslr::init() not called"
85    );
86    const MMAP_BASE: u64 = 0x0000_0000_6000_0000;
87    MMAP_BASE + MMAP_BASE_OFFSET.load(Ordering::Relaxed)
88}
89
90/// Get the randomized user stack base address.
91///
92/// # Panics
93/// Panics if `init()` has not been called yet.
94#[inline]
95pub fn stack_base() -> u64 {
96    debug_assert!(
97        INITIALIZED.load(Ordering::Relaxed),
98        "kaslr::init() not called"
99    );
100    const STACK_BASE: u64 = 0x0000_7FFF_F000_0000;
101    STACK_BASE + STACK_BASE_OFFSET.load(Ordering::Relaxed)
102}
103
104/// Draw a fresh per-image stack jitter: a page-aligned offset in
105/// `0..=255` pages (0..~1 MiB).
106///
107/// The boot-time [`STACK_BASE_OFFSET`] randomizes the stack *region* once
108/// per boot; this adds per-process entropy on top so two processes do not
109/// share identical stack addresses (issue #62).
110pub fn draw_stack_jitter() -> u64 {
111    let mut buf = [0u8; 1];
112    crate::entropy::fill_random(&mut buf);
113    (buf[0] as u64) << 12
114}
115
116/// Stack base for a specific process, adding its own jitter on top of the
117/// boot-randomized base. Use with [`draw_stack_jitter`].
118#[inline]
119pub fn stack_base_with_jitter(jitter: u64) -> u64 {
120    // Saturate instead of overflowing: the base sits at 0x7FFF_F000_0000
121    // and USER_ADDR_MAX is 0x8000_0000_0000, so even a full 1 MiB jitter
122    // plus an 8 MiB stack stays far below the limit.
123    stack_base().saturating_add(jitter & !0xFFF)
124}
125
126/// Get the top of a stack of `pages` 4 KiB pages starting at `base`.
127#[inline]
128pub fn stack_top_for(base: u64, pages: usize) -> u64 {
129    base + (pages as u64) * 4096
130}
131
132/// Get the randomized user stack top address.
133#[inline]
134pub fn stack_top() -> u64 {
135    stack_top_for(stack_base(), crate::process::elf::USER_STACK_PAGES)
136}
137
138/// Get the guard page address below the user stack.
139#[inline]
140pub fn stack_guard() -> u64 {
141    stack_base() - 4096
142}
143
144/// Get the randomized PIE base address for ELF loading.
145///
146/// # Panics
147/// Panics if `init()` has not been called yet.
148#[inline]
149pub fn pie_base() -> u64 {
150    debug_assert!(
151        INITIALIZED.load(Ordering::Relaxed),
152        "kaslr::init() not called"
153    );
154    const PIE_BASE: u64 = 0x0000_0001_0000_0000;
155    PIE_BASE + PIE_BASE_OFFSET.load(Ordering::Relaxed)
156}