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}