Microkernel based hobby OS

Capability Sealing, Cryptographic Infrastructure

+1512 -114
+7
Cargo.lock
··· 29 29 version = "0.1.0" 30 30 dependencies = [ 31 31 "bitflags 1.3.2", 32 + "hmac-sha256", 32 33 "lazy_static", 33 34 "pic8259", 34 35 "spin", ··· 38 39 [[package]] 39 40 name = "heartwood_loader" 40 41 version = "0.1.0" 42 + 43 + [[package]] 44 + name = "hmac-sha256" 45 + version = "1.1.12" 46 + source = "registry+https://github.com/rust-lang/crates.io-index" 47 + checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" 41 48 42 49 [[package]] 43 50 name = "lanthir_grove"
aethelos.iso

This is a binary file and will not be displayed.

+369
docs/CAPABILITY_SEALING_DESIGN.md
··· 1 + # Capability Sealing - Preventing Forgery in AethelOS 2 + 3 + ## Problem Statement 4 + 5 + In capability-based security, capabilities must be **unforgeable**. A malicious program should not be able to: 6 + - Create new capabilities with arbitrary permissions 7 + - Modify existing capabilities to escalate privileges 8 + - Forge capability IDs to access resources they don't own 9 + 10 + ## Current Vulnerability 11 + 12 + Our current `Capability` struct is just a bitflags wrapper: 13 + ```rust 14 + pub struct Capability { 15 + rights: CapabilityRights, // Just a u32 bitfield 16 + } 17 + ``` 18 + 19 + If user space could construct this directly, they could forge any permission level. 20 + 21 + ## Defense Layers 22 + 23 + ### Layer 1: Opaque Handles (Primary Defense) 24 + 25 + **Concept:** User space never sees actual capabilities, only opaque handles. 26 + 27 + ```rust 28 + /// Opaque capability identifier - user space only sees this 29 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 30 + pub struct CapabilityId(u64); 31 + 32 + /// Sealed capability - only constructible by kernel 33 + pub struct SealedCapability { 34 + /// Public ID (user space sees this) 35 + id: CapabilityId, 36 + 37 + /// Actual rights (user space NEVER sees this) 38 + rights: CapabilityRights, 39 + 40 + /// Object this capability grants access to 41 + object_id: ObjectId, 42 + 43 + /// Cryptographic seal (HMAC of id + rights + object_id) 44 + seal: [u8; 32], 45 + 46 + /// Generation counter (for revocation) 47 + generation: u64, 48 + } 49 + ``` 50 + 51 + **How it works:** 52 + 1. Kernel creates capability and stores in per-process capability table 53 + 2. User space receives only `CapabilityId` (e.g., `CapabilityId(42)`) 54 + 3. When user calls syscall with `CapabilityId(42)`, kernel: 55 + - Looks up actual `SealedCapability` from table 56 + - Validates the seal 57 + - Checks permissions 58 + - Performs operation 59 + 60 + **Analogies:** 61 + - Like Linux file descriptors (fd 3 means nothing without kernel lookup) 62 + - Like seL4 capability spaces 63 + - Like handle-based APIs (HANDLE in Windows) 64 + 65 + ### Layer 2: Cryptographic Sealing (Secondary Defense) 66 + 67 + **Concept:** Even if capability table is compromised, capabilities can't be forged without kernel secret. 68 + 69 + ```rust 70 + /// Kernel-only secret key for sealing capabilities 71 + static SEAL_KEY: OnceCell<[u8; 32]> = OnceCell::new(); 72 + 73 + impl SealedCapability { 74 + /// Create a new sealed capability (kernel-only) 75 + pub(crate) fn new(id: CapabilityId, rights: CapabilityRights, object_id: ObjectId) -> Self { 76 + let seal = Self::compute_seal(id, rights, object_id); 77 + Self { 78 + id, 79 + rights, 80 + object_id, 81 + seal, 82 + generation: 0, 83 + } 84 + } 85 + 86 + /// Compute HMAC-SHA256 seal 87 + fn compute_seal(id: CapabilityId, rights: CapabilityRights, object_id: ObjectId) -> [u8; 32] { 88 + let key = SEAL_KEY.get().expect("Seal key not initialized"); 89 + 90 + // HMAC-SHA256(key, id || rights || object_id || generation) 91 + let mut hasher = HmacSha256::new(key); 92 + hasher.update(&id.0.to_le_bytes()); 93 + hasher.update(&rights.bits().to_le_bytes()); 94 + hasher.update(&object_id.0.to_le_bytes()); 95 + hasher.finalize() 96 + } 97 + 98 + /// Validate seal before using capability 99 + pub(crate) fn validate(&self) -> Result<(), SecurityViolation> { 100 + let expected_seal = Self::compute_seal(self.id, self.rights, self.object_id); 101 + 102 + if constant_time_compare(&self.seal, &expected_seal) { 103 + Ok(()) 104 + } else { 105 + Err(SecurityViolation::CapabilityForgery) 106 + } 107 + } 108 + } 109 + ``` 110 + 111 + **How it works:** 112 + 1. At boot, kernel generates random 256-bit seal key (using RDRAND/ChaCha8) 113 + 2. Key never leaves kernel, never exposed to user space 114 + 3. Every capability is sealed with HMAC-SHA256(key, capability_data) 115 + 4. Before using capability, kernel validates seal 116 + 5. Without key, attacker can't forge valid seal 117 + 118 + **Benefits:** 119 + - Defense-in-depth: Even if capability table is somehow leaked, forged caps fail validation 120 + - Integrity protection: Tampering with rights/object_id invalidates seal 121 + - Cryptographically secure: Infeasible to forge without key 122 + 123 + ### Layer 3: Type System Enforcement (Tertiary Defense) 124 + 125 + **Concept:** Use Rust's type system to prevent capability construction outside kernel. 126 + 127 + ```rust 128 + /// Capability rights - construction is private 129 + pub struct CapabilityRights(u32); 130 + 131 + impl CapabilityRights { 132 + /// Private constructor - only kernel can create 133 + pub(crate) const fn from_bits(bits: u32) -> Self { 134 + Self(bits) 135 + } 136 + 137 + /// No public constructor! 138 + // (user code can't do CapabilityRights::new()) 139 + } 140 + 141 + /// Sealed capability - all fields private, no public constructor 142 + pub struct SealedCapability { 143 + id: CapabilityId, // Private 144 + rights: CapabilityRights, // Private 145 + object_id: ObjectId, // Private 146 + seal: [u8; 32], // Private 147 + generation: u64, // Private 148 + } 149 + 150 + // No Debug/Clone for SealedCapability to prevent leakage 151 + ``` 152 + 153 + **How it works:** 154 + - User space can't construct `SealedCapability` (no public constructor) 155 + - User space can't access fields (all private) 156 + - User space can only receive `CapabilityId` and pass it back to kernel 157 + 158 + ### Layer 4: Memory Isolation (Future - With User Space) 159 + 160 + **Concept:** Separate kernel and user address spaces. 161 + 162 + ``` 163 + Kernel Space (Ring 0): 164 + - Capability tables 165 + - Seal key 166 + - ObjectManager 167 + 168 + User Space (Ring 3): 169 + - Only has CapabilityId values 170 + - Can't access kernel memory 171 + - Page tables enforced by MMU 172 + ``` 173 + 174 + **How it works:** 175 + - User space runs in Ring 3 with restricted page tables 176 + - Capability tables live in kernel-only memory 177 + - Syscalls cross privilege boundary to access capabilities 178 + - MMU prevents direct memory access 179 + 180 + ## Implementation Architecture 181 + 182 + ``` 183 + ┌────────────────────────────────────────────────────┐ 184 + │ User Space │ 185 + │ │ 186 + │ CapabilityId(42) CapabilityId(17) │ 187 + │ ↓ ↓ │ 188 + └───────┼──────────────────┼─────────────────────────┘ 189 + │ │ 190 + syscall syscall 191 + │ │ 192 + ┌───────┼──────────────────┼─────────────────────────┐ 193 + │ ↓ ↓ Kernel Space │ 194 + │ ┌──────────────────────────────────────┐ │ 195 + │ │ Per-Process Capability Table │ │ 196 + │ │ │ │ 197 + │ │ 42 → SealedCapability { │ │ 198 + │ │ rights: READ|WRITE, │ │ 199 + │ │ object: MemoryObject(0x1000), │ │ 200 + │ │ seal: [crypto hash], │ │ 201 + │ │ } │ │ 202 + │ │ │ │ 203 + │ │ 17 → SealedCapability { │ │ 204 + │ │ rights: READ|EXECUTE, │ │ 205 + │ │ object: MemoryObject(0x5000), │ │ 206 + │ │ seal: [crypto hash], │ │ 207 + │ │ } │ │ 208 + │ └──────────────────────────────────────┘ │ 209 + │ │ 210 + │ Validation Flow: │ 211 + │ 1. Lookup capability by ID │ 212 + │ 2. Validate cryptographic seal │ 213 + │ 3. Check rights match requested operation │ 214 + │ 4. Perform operation if valid │ 215 + │ │ 216 + └────────────────────────────────────────────────────┘ 217 + ``` 218 + 219 + ## Capability Operations 220 + 221 + ### Creating a Capability 222 + 223 + ```rust 224 + // In kernel, when allocating memory: 225 + pub fn allocate_memory(size: usize, process: &mut Process) -> Result<CapabilityId, Error> { 226 + // 1. Allocate the actual memory object 227 + let object_id = OBJECT_MANAGER.lock().create_memory_object(size)?; 228 + 229 + // 2. Create sealed capability 230 + let cap_id = process.next_capability_id(); 231 + let rights = CapabilityRights::READ | CapabilityRights::WRITE; 232 + let sealed_cap = SealedCapability::new(cap_id, rights, object_id); 233 + 234 + // 3. Store in process capability table 235 + process.capability_table.insert(cap_id, sealed_cap); 236 + 237 + // 4. Return only the ID to user space 238 + Ok(cap_id) 239 + } 240 + ``` 241 + 242 + ### Using a Capability 243 + 244 + ```rust 245 + // Syscall: read from memory using capability 246 + pub fn sys_memory_read(cap_id: CapabilityId, offset: usize, buffer: &mut [u8]) -> Result<(), Error> { 247 + let process = current_process(); 248 + 249 + // 1. Lookup capability (prevents forgery - user can't access table) 250 + let sealed_cap = process.capability_table 251 + .get(&cap_id) 252 + .ok_or(Error::InvalidCapability)?; 253 + 254 + // 2. Validate seal (prevents tampering) 255 + sealed_cap.validate() 256 + .map_err(|_| Error::CapabilityForgery)?; 257 + 258 + // 3. Check rights 259 + if !sealed_cap.rights.contains(CapabilityRights::READ) { 260 + return Err(Error::PermissionDenied); 261 + } 262 + 263 + // 4. Perform operation 264 + let object = OBJECT_MANAGER.lock() 265 + .get_object(sealed_cap.object_id)?; 266 + object.read(offset, buffer) 267 + } 268 + ``` 269 + 270 + ### Deriving a Capability (Attenuation) 271 + 272 + ```rust 273 + // User wants to pass read-only version of their read-write capability 274 + pub fn sys_capability_derive( 275 + parent_id: CapabilityId, 276 + new_rights: CapabilityRights, 277 + ) -> Result<CapabilityId, Error> { 278 + let process = current_process(); 279 + 280 + // 1. Lookup parent capability 281 + let parent = process.capability_table 282 + .get(&parent_id) 283 + .ok_or(Error::InvalidCapability)?; 284 + 285 + // 2. Validate parent seal 286 + parent.validate() 287 + .map_err(|_| Error::CapabilityForgery)?; 288 + 289 + // 3. Check attenuation (can only reduce rights, never increase) 290 + if !parent.rights.contains(new_rights) { 291 + return Err(Error::CannotAmplifyRights); 292 + } 293 + 294 + // 4. Create new sealed capability 295 + let child_id = process.next_capability_id(); 296 + let child = SealedCapability::new(child_id, new_rights, parent.object_id); 297 + 298 + // 5. Store and return 299 + process.capability_table.insert(child_id, child); 300 + Ok(child_id) 301 + } 302 + ``` 303 + 304 + ## Security Properties 305 + 306 + ### Achieved Properties 307 + 308 + 1. **Unforgeable**: User space can't create valid `CapabilityId` that passes validation 309 + 2. **Tamper-Proof**: Modifying capability data breaks cryptographic seal 310 + 3. **Non-Amplifiable**: Can only derive capabilities with fewer rights (attenuation) 311 + 4. **Revocable**: Incrementing generation counter invalidates all old capabilities 312 + 5. **Isolated**: Capability tables live in kernel-only memory 313 + 314 + ### Attack Scenarios 315 + 316 + | Attack | Defense | 317 + |--------|---------| 318 + | Forge arbitrary CapabilityId | Lookup fails - ID not in table | 319 + | Guess valid CapabilityId | 64-bit space, random generation → infeasible | 320 + | Modify rights in memory | Seal validation fails | 321 + | Replay old capability | Generation counter mismatch | 322 + | Steal another process's capability | Per-process capability tables | 323 + | Extract seal key | Key never leaves kernel, in protected memory | 324 + 325 + ## Implementation Phases 326 + 327 + ### Phase 1: Opaque Handles (Current Priority) 328 + - Replace direct `Capability` exposure with `CapabilityId` 329 + - Add `CapabilityTable` struct to ObjectManager 330 + - Update Mana Pool to use capability lookup 331 + 332 + ### Phase 2: Cryptographic Sealing 333 + - Implement HMAC-SHA256 (or use ChaCha8-based MAC) 334 + - Generate seal key at boot 335 + - Add seal validation to all capability operations 336 + 337 + ### Phase 3: Per-Process Tables 338 + - Move capability tables from global to per-process 339 + - Implement proper process isolation 340 + 341 + ### Phase 4: Hardware Isolation 342 + - Implement user/kernel space separation (Ring 3/Ring 0) 343 + - Page table-based memory protection 344 + - Syscall interface for capability operations 345 + 346 + ## Comparison to Other Systems 347 + 348 + | System | Approach | Strength | 349 + |--------|----------|----------| 350 + | **seL4** | Capability spaces + hardware isolation | Formally verified, hardware-backed | 351 + | **CHERI** | Tagged pointers in hardware | Hardware enforcement, very fast | 352 + | **Unix** | File descriptors (opaque handles) | Simple, proven design | 353 + | **AethelOS (Proposed)** | Opaque handles + crypto sealing | Layered defense, practical for x86-64 | 354 + 355 + ## References 356 + 357 + - seL4 Capability Model: https://sel4.systems/Info/Docs/seL4-manual.pdf 358 + - CHERI ISA: https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-951.pdf 359 + - Capability Myths Demolished: https://srl.cs.jhu.edu/pubs/SRL2003-02.pdf 360 + - L4 Capabilities: http://www.cse.unsw.edu.au/~cs9242/02/lectures/05-caps.pdf 361 + 362 + ## Next Steps 363 + 364 + 1. Implement `CapabilityId` and `SealedCapability` structs 365 + 2. Add `CapabilityTable` to ObjectManager 366 + 3. Update Mana Pool to use capability lookup 367 + 4. Implement HMAC-SHA256 or ChaCha8-MAC 368 + 5. Add seal validation 369 + 6. Test with capability forgery attempts
+1
heartwood/Cargo.toml
··· 11 11 lazy_static = { workspace = true } 12 12 x86_64 = { workspace = true } 13 13 pic8259 = { workspace = true } 14 + hmac-sha256 = { version = "1.1", default-features = false, features = ["opt_size"] } 14 15 # volatile = { workspace = true } 15 16 # uart_16550 = { workspace = true } 16 17 # bootloader = { workspace = true }
+28 -62
heartwood/src/eldarin.rs
··· 185 185 static mut COMMAND_HISTORY: MaybeUninit<InterruptSafeLock<CommandHistory>> = MaybeUninit::uninit(); 186 186 static mut HISTORY_INITIALIZED: bool = false; 187 187 188 - /// Paging state for help menu 189 - static mut PAGING_ACTIVE: bool = false; 190 - static mut PAGING_PAGE: usize = 0; 188 + /// Paging state for multi-page commands 189 + pub(crate) static mut PAGING_ACTIVE: bool = false; 190 + pub(crate) static mut PAGING_PAGE: usize = 0; 191 + 192 + #[derive(Clone, Copy)] 193 + pub enum PagingCommand { 194 + Help, 195 + Wards, 196 + } 197 + 198 + pub(crate) static mut PAGING_COMMAND: Option<PagingCommand> = None; 191 199 192 200 /// Initialize the shell 193 201 pub fn init() { ··· 351 359 buffer.clear(); 352 360 } 353 361 PAGING_PAGE += 1; 354 - show_help_page(PAGING_PAGE); 362 + 363 + // Call the appropriate paging function 364 + match PAGING_COMMAND { 365 + Some(PagingCommand::Help) => show_help_page(PAGING_PAGE), 366 + Some(PagingCommand::Wards) => crate::wards_command::show_wards_page(PAGING_PAGE), 367 + None => { 368 + // Safety: should never happen 369 + PAGING_ACTIVE = false; 370 + PAGING_PAGE = 0; 371 + display_prompt(); 372 + } 373 + } 355 374 return; 356 375 } 357 376 ··· 528 547 unsafe { 529 548 PAGING_ACTIVE = true; 530 549 PAGING_PAGE = 0; 550 + PAGING_COMMAND = Some(PagingCommand::Help); 531 551 } 532 552 show_help_page(0); 533 553 } ··· 591 611 unsafe { 592 612 PAGING_ACTIVE = false; 593 613 PAGING_PAGE = 0; 614 + PAGING_COMMAND = None; 594 615 } 595 616 display_prompt(); 596 617 } ··· 599 620 unsafe { 600 621 PAGING_ACTIVE = false; 601 622 PAGING_PAGE = 0; 623 + PAGING_COMMAND = None; 602 624 } 603 625 display_prompt(); 604 626 } ··· 1081 1103 1082 1104 /// WARDS - Display security protections (ASLR, W^X enforcement) 1083 1105 fn cmd_wards() { 1084 - crate::println!("◈ Security Wards of the Mana Pool"); 1085 - crate::println!(); 1086 - crate::println!("═══════════════════════════════════════════════════"); 1087 - crate::println!(); 1088 - 1089 - // W^X Status 1090 - crate::println!(" ⚔ Write ⊕ Execute Enforcement"); 1091 - crate::println!(" Status: ✓ ACTIVE"); 1092 - crate::println!(" Policy: Memory pages cannot be both writable AND executable"); 1093 - crate::println!(" Location: Enforced in Mana Pool capability validation"); 1094 - crate::println!(); 1095 - 1096 - // ASLR Status 1097 - crate::println!(" ⚔ Address Space Layout Randomization (ASLR)"); 1098 - crate::println!(" Status: ✓ ACTIVE"); 1099 - crate::println!(" Entropy: 0-64KB randomization per thread stack"); 1100 - crate::println!(" Source: RDTSC (fast boot-safe hardware timer)"); 1101 - crate::println!(" Scope: All thread stacks randomized at creation"); 1102 - crate::println!(); 1103 - 1104 - // Thread Stack Information 1105 - let threads = crate::loom_of_fate::get_thread_debug_info(); 1106 - crate::println!(" ⚔ Protected Thread Stacks: {}", threads.len()); 1107 - crate::println!(); 1108 - 1109 - if !threads.is_empty() { 1110 - for thread in threads { 1111 - let state_str = match thread.state { 1112 - crate::loom_of_fate::ThreadState::Weaving => "Weaving", 1113 - crate::loom_of_fate::ThreadState::Resting => "Resting", 1114 - crate::loom_of_fate::ThreadState::Tangled => "Tangled", 1115 - crate::loom_of_fate::ThreadState::Fading => "Fading", 1116 - }; 1117 - 1118 - let priority_str = match thread.priority { 1119 - crate::loom_of_fate::ThreadPriority::Critical => "Critical", 1120 - crate::loom_of_fate::ThreadPriority::High => "High", 1121 - crate::loom_of_fate::ThreadPriority::Normal => "Normal", 1122 - crate::loom_of_fate::ThreadPriority::Low => "Low", 1123 - crate::loom_of_fate::ThreadPriority::Idle => "Idle", 1124 - }; 1125 - 1126 - crate::println!(" Thread #{} [{}|{}]", thread.id, state_str, priority_str); 1127 - crate::println!(" Stack: 0x{:016x} - 0x{:016x}", 1128 - thread.stack_bottom, thread.stack_top); 1129 - crate::println!(" Size: {} KB", thread.stack_size / 1024); 1130 - 1131 - // Calculate ASLR offset - the difference shows randomization 1132 - // Each thread should have a different stack address due to ASLR 1133 - let addr_entropy = (thread.stack_top as usize) & 0xFFFF; 1134 - crate::println!(" ASLR: ~{} bytes offset (varies per thread)", addr_entropy % 65536); 1135 - crate::println!(); 1136 - } 1137 - } 1138 - 1139 - crate::println!("═══════════════════════════════════════════════════"); 1140 - crate::println!(); 1141 - crate::println!(" The wards stand strong. Your sanctuary is protected."); 1106 + // Delegate to the paged wards command with capability testing 1107 + crate::wards_command::cmd_wards(); 1142 1108 }
+1
heartwood/src/lib.rs
··· 43 43 pub mod vga_buffer; 44 44 pub mod rtl; // Runtime library with memcpy, etc. 45 45 pub mod eldarin; // The Eldarin Shell 46 + pub mod wards_command; // Security wards command 46 47 pub mod irq_safe_mutex; // Interrupt-safe mutex primitive 47 48 pub mod vfs; // Virtual File System layer 48 49 pub mod drivers; // Hardware device drivers
+1 -1
heartwood/src/loom_of_fate/mod.rs
··· 120 120 121 121 /// Execute a closure with interrupts disabled 122 122 /// This prevents deadlocks when acquiring locks that might be used in interrupt handlers 123 - fn without_interrupts<F, R>(f: F) -> R 123 + pub(crate) fn without_interrupts<F, R>(f: F) -> R 124 124 where 125 125 F: FnOnce() -> R, 126 126 {
+6
heartwood/src/main.rs
··· 187 187 unsafe { serial_out(b'D'); } 188 188 println!(" ✓ Mana Pool ready"); 189 189 190 + // Initialize capability sealing (must be after Mana Pool, before capabilities are created) 191 + unsafe { serial_out(b'S'); } 192 + println!("◈ Forging security wards..."); 193 + unsafe { mana_pool::sealing::init(); } 194 + println!(" ✓ Capability sealing ready (HMAC-SHA256)"); 195 + 190 196 // Initialize the Nexus (IPC) 191 197 unsafe { serial_out(b'E'); } 192 198 println!("◈ Opening the Nexus...");
+197
heartwood/src/mana_pool/capability.rs
··· 1 1 //! Capability-based security for memory access 2 2 3 3 use super::object_manager::ObjectHandle; 4 + use super::sealing; 5 + use core::sync::atomic::{AtomicU64, Ordering}; 4 6 5 7 /// A capability grants specific rights to an object 6 8 #[derive(Debug, Clone, Copy)] ··· 32 34 /// Check if this capability can be transferred to another process 33 35 pub fn can_transfer(&self) -> bool { 34 36 self.rights.contains(CapabilityRights::TRANSFER) 37 + } 38 + } 39 + 40 + // ==================== SEALED CAPABILITIES ==================== 41 + 42 + /// Opaque capability identifier (user space only sees this) 43 + /// 44 + /// This is like a file descriptor - a meaningless number without 45 + /// the kernel's capability table to look it up. 46 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] 47 + pub struct CapabilityId(u64); 48 + 49 + impl CapabilityId { 50 + /// Create a new capability ID 51 + pub(crate) fn new(id: u64) -> Self { 52 + Self(id) 53 + } 54 + 55 + /// Get the raw ID value 56 + pub fn raw(&self) -> u64 { 57 + self.0 58 + } 59 + } 60 + 61 + /// Global counter for generating unique capability IDs 62 + static NEXT_CAPABILITY_ID: AtomicU64 = AtomicU64::new(1); 63 + 64 + impl CapabilityId { 65 + /// Generate a new unique capability ID 66 + pub(crate) fn generate() -> Self { 67 + let id = NEXT_CAPABILITY_ID.fetch_add(1, Ordering::SeqCst); 68 + Self(id) 69 + } 70 + } 71 + 72 + /// Sealed capability with cryptographic authentication 73 + /// 74 + /// This is the kernel-internal structure that combines: 75 + /// - Opaque ID (what user space sees) 76 + /// - Actual rights and object handle (what kernel uses) 77 + /// - Cryptographic seal (prevents forgery and tampering) 78 + /// - Generation counter (for revocation) 79 + /// 80 + /// User space can only see CapabilityId. The SealedCapability 81 + /// lives in kernel-only capability tables. 82 + #[derive(Debug, Clone)] 83 + pub struct SealedCapability { 84 + /// Public ID (user space sees this) 85 + pub id: CapabilityId, 86 + 87 + /// Actual rights (user space NEVER sees this directly) 88 + pub rights: CapabilityRights, 89 + 90 + /// Object this capability grants access to 91 + pub handle: ObjectHandle, 92 + 93 + /// Cryptographic seal (HMAC-SHA256 of id + rights + handle) 94 + /// Validates integrity and authenticity 95 + seal: [u8; 32], 96 + 97 + /// Generation counter for revocation 98 + /// Incrementing this invalidates all copies of this capability 99 + pub generation: u64, 100 + } 101 + 102 + impl SealedCapability { 103 + /// Create a new sealed capability 104 + /// 105 + /// # Arguments 106 + /// * `handle` - Object handle this capability grants access to 107 + /// * `rights` - Permission rights 108 + /// 109 + /// # Security 110 + /// The seal is computed using kernel-only secret key via HMAC-SHA256. 111 + /// This makes the capability unforgeable without the secret key. 112 + pub fn new(handle: ObjectHandle, rights: CapabilityRights) -> Self { 113 + // Validate W^X before creating capability 114 + debug_assert!(rights.validate_wx(), "W^X violation in capability creation!"); 115 + 116 + let id = CapabilityId::generate(); 117 + let generation = 0; 118 + 119 + // Compute cryptographic seal 120 + let seal = Self::compute_seal(id, rights, handle, generation); 121 + 122 + Self { 123 + id, 124 + rights, 125 + handle, 126 + seal, 127 + generation, 128 + } 129 + } 130 + 131 + /// Compute the cryptographic seal for this capability 132 + /// 133 + /// Seal = HMAC-SHA256(key, id || rights || handle || generation) 134 + /// 135 + /// The seal binds together all fields. Modifying any field 136 + /// (e.g., escalating rights) will break the seal. 137 + fn compute_seal( 138 + id: CapabilityId, 139 + rights: CapabilityRights, 140 + handle: ObjectHandle, 141 + generation: u64, 142 + ) -> [u8; 32] { 143 + // Serialize capability data for HMAC 144 + let mut data = [0u8; 32]; 145 + 146 + // Pack all fields into data buffer 147 + data[0..8].copy_from_slice(&id.0.to_le_bytes()); 148 + data[8..12].copy_from_slice(&rights.bits().to_le_bytes()); 149 + data[12..20].copy_from_slice(&handle.0.to_le_bytes()); 150 + data[20..28].copy_from_slice(&generation.to_le_bytes()); 151 + 152 + // Compute HMAC-SHA256 153 + unsafe { 154 + sealing::get_sealer().seal(&data) 155 + } 156 + } 157 + 158 + /// Validate the cryptographic seal 159 + /// 160 + /// # Returns 161 + /// `true` if seal is valid, `false` if capability has been tampered with 162 + /// 163 + /// # Security 164 + /// Uses constant-time comparison to prevent timing attacks. 165 + pub fn validate(&self) -> bool { 166 + let expected_seal = Self::compute_seal( 167 + self.id, 168 + self.rights, 169 + self.handle, 170 + self.generation, 171 + ); 172 + 173 + unsafe { 174 + sealing::get_sealer().verify(&self.serialize_for_seal(), &expected_seal) 175 + } 176 + } 177 + 178 + /// Serialize capability data for sealing 179 + fn serialize_for_seal(&self) -> [u8; 32] { 180 + let mut data = [0u8; 32]; 181 + data[0..8].copy_from_slice(&self.id.0.to_le_bytes()); 182 + data[8..12].copy_from_slice(&self.rights.bits().to_le_bytes()); 183 + data[12..20].copy_from_slice(&self.handle.0.to_le_bytes()); 184 + data[20..28].copy_from_slice(&self.generation.to_le_bytes()); 185 + data 186 + } 187 + 188 + /// Derive a new capability with reduced rights (attenuation) 189 + /// 190 + /// # Arguments 191 + /// * `new_rights` - The reduced rights for the new capability 192 + /// 193 + /// # Security 194 + /// Can only reduce rights, never increase them. If new_rights 195 + /// contains any right not in the parent, this panics. 196 + /// 197 + /// # Panics 198 + /// Panics if attempting to amplify rights 199 + pub fn derive(&self, new_rights: CapabilityRights) -> Self { 200 + // SECURITY: Can only attenuate (reduce rights), never amplify 201 + assert!( 202 + self.rights.contains(new_rights), 203 + "Cannot amplify rights! Parent: {:?}, Child: {:?}", 204 + self.rights, 205 + new_rights 206 + ); 207 + 208 + // Validate W^X on derived capability 209 + assert!( 210 + new_rights.validate_wx(), 211 + "Derived capability violates W^X!" 212 + ); 213 + 214 + let id = CapabilityId::generate(); 215 + let seal = Self::compute_seal(id, new_rights, self.handle, self.generation); 216 + 217 + Self { 218 + id, 219 + rights: new_rights, 220 + handle: self.handle, 221 + seal, 222 + generation: self.generation, 223 + } 224 + } 225 + 226 + /// Convert to legacy Capability (for compatibility) 227 + pub fn to_capability(&self) -> Capability { 228 + Capability { 229 + handle: self.handle, 230 + rights: self.rights, 231 + } 35 232 } 36 233 } 37 234
+334
heartwood/src/mana_pool/capability_table.rs
··· 1 + //! Capability Table - Opaque handle mapping 2 + //! 3 + //! The capability table enforces the "opaque handle" security model: 4 + //! - User space only sees `CapabilityId` numbers (meaningless without the table) 5 + //! - Kernel maintains the mapping from ID → `SealedCapability` 6 + //! - All capability operations require table lookup + seal validation 7 + //! 8 + //! This prevents forgery because: 9 + //! 1. User can't create valid CapabilityId that exists in table 10 + //! 2. User can't access table directly (kernel-only memory) 11 + //! 3. Even if they guess an ID, seal validation catches tampering 12 + 13 + use super::capability::{CapabilityId, SealedCapability, CapabilityRights}; 14 + use super::object_manager::ObjectHandle; 15 + use alloc::collections::BTreeMap; 16 + 17 + /// Errors that can occur during capability table operations 18 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 19 + pub enum CapabilityError { 20 + /// Capability ID not found in table 21 + InvalidId, 22 + /// Capability seal validation failed (tampering detected) 23 + SealBroken, 24 + /// Attempted operation requires rights not granted by capability 25 + PermissionDenied, 26 + /// Capability table is full 27 + TableFull, 28 + } 29 + 30 + /// Maximum number of capabilities per table 31 + /// This prevents DoS attacks via capability exhaustion 32 + const MAX_CAPABILITIES: usize = 4096; 33 + 34 + /// Capability table - maps opaque IDs to sealed capabilities 35 + /// 36 + /// # Security Model 37 + /// - Table lives in kernel-only memory (user space never sees it) 38 + /// - User space only receives `CapabilityId` values 39 + /// - All operations require lookup by ID + seal validation 40 + /// - Invalid IDs or broken seals are rejected 41 + /// 42 + /// # Usage 43 + /// ``` 44 + /// // Create and insert capability 45 + /// let cap = SealedCapability::new(handle, rights); 46 + /// let cap_id = table.insert(cap)?; 47 + /// 48 + /// // Lookup and validate 49 + /// let cap = table.get(cap_id)?; // Validates seal automatically 50 + /// if cap.rights.contains(READ) { 51 + /// // Perform read operation 52 + /// } 53 + /// ``` 54 + pub struct CapabilityTable { 55 + /// Mapping from opaque ID → sealed capability 56 + /// Using BTreeMap instead of HashMap for deterministic iteration 57 + table: BTreeMap<CapabilityId, SealedCapability>, 58 + } 59 + 60 + impl CapabilityTable { 61 + /// Create a new empty capability table 62 + pub const fn new() -> Self { 63 + Self { 64 + table: BTreeMap::new(), 65 + } 66 + } 67 + 68 + /// Insert a sealed capability into the table 69 + /// 70 + /// # Arguments 71 + /// * `cap` - The sealed capability to insert 72 + /// 73 + /// # Returns 74 + /// * `Ok(CapabilityId)` - The opaque ID for this capability 75 + /// * `Err(TableFull)` - If table has reached maximum capacity 76 + /// 77 + /// # Security 78 + /// The returned CapabilityId is the ONLY handle user space gets. 79 + /// Without access to this table, the ID is meaningless. 80 + pub fn insert(&mut self, cap: SealedCapability) -> Result<CapabilityId, CapabilityError> { 81 + if self.table.len() >= MAX_CAPABILITIES { 82 + return Err(CapabilityError::TableFull); 83 + } 84 + 85 + let id = cap.id; 86 + self.table.insert(id, cap); 87 + Ok(id) 88 + } 89 + 90 + /// Lookup capability by ID and validate seal 91 + /// 92 + /// # Arguments 93 + /// * `id` - The opaque capability ID to lookup 94 + /// 95 + /// # Returns 96 + /// * `Ok(&SealedCapability)` - Valid capability with intact seal 97 + /// * `Err(InvalidId)` - ID not found in table 98 + /// * `Err(SealBroken)` - Seal validation failed (tampering detected) 99 + /// 100 + /// # Security 101 + /// This is the PRIMARY security boundary. Every capability use 102 + /// must go through this function, which: 103 + /// 1. Verifies the ID exists in the table (prevents forgery) 104 + /// 2. Validates the cryptographic seal (prevents tampering) 105 + pub fn get(&self, id: CapabilityId) -> Result<&SealedCapability, CapabilityError> { 106 + // 1. Lookup capability by ID 107 + let cap = self.table.get(&id) 108 + .ok_or(CapabilityError::InvalidId)?; 109 + 110 + // 2. SECURITY: Validate cryptographic seal 111 + if !cap.validate() { 112 + return Err(CapabilityError::SealBroken); 113 + } 114 + 115 + Ok(cap) 116 + } 117 + 118 + /// Lookup capability with mutable access (for revocation) 119 + /// 120 + /// # Security 121 + /// Mutable access is restricted to kernel operations like revocation. 122 + /// Still validates seal before returning. 123 + pub fn get_mut(&mut self, id: CapabilityId) -> Result<&mut SealedCapability, CapabilityError> { 124 + let cap = self.table.get_mut(&id) 125 + .ok_or(CapabilityError::InvalidId)?; 126 + 127 + if !cap.validate() { 128 + return Err(CapabilityError::SealBroken); 129 + } 130 + 131 + Ok(cap) 132 + } 133 + 134 + /// Check if capability grants specific rights 135 + /// 136 + /// # Security 137 + /// Validates seal before checking rights. This is a convenience 138 + /// function that combines lookup + rights check. 139 + pub fn check_rights(&self, id: CapabilityId, required: CapabilityRights) -> Result<(), CapabilityError> { 140 + let cap = self.get(id)?; 141 + 142 + if cap.rights.contains(required) { 143 + Ok(()) 144 + } else { 145 + Err(CapabilityError::PermissionDenied) 146 + } 147 + } 148 + 149 + /// Remove capability from table (revocation) 150 + /// 151 + /// # Returns 152 + /// The removed capability, if it existed 153 + pub fn remove(&mut self, id: CapabilityId) -> Option<SealedCapability> { 154 + self.table.remove(&id) 155 + } 156 + 157 + /// Derive a new capability with reduced rights 158 + /// 159 + /// Creates a new sealed capability with attenuated rights and 160 + /// inserts it into the table, returning the new ID. 161 + /// 162 + /// # Arguments 163 + /// * `parent_id` - ID of parent capability 164 + /// * `new_rights` - Reduced rights for child capability 165 + /// 166 + /// # Security 167 + /// Can only reduce rights, never amplify. The parent capability's 168 + /// seal is validated before derivation. 169 + /// 170 + /// # Panics 171 + /// Panics if attempting to amplify rights or violate W^X 172 + pub fn derive(&mut self, parent_id: CapabilityId, new_rights: CapabilityRights) -> Result<CapabilityId, CapabilityError> { 173 + // Lookup and validate parent 174 + let parent = self.get(parent_id)?; 175 + 176 + // Derive child capability (validates rights attenuation) 177 + let child = parent.derive(new_rights); 178 + 179 + // Insert child into table 180 + self.insert(child) 181 + } 182 + 183 + /// Get the object handle for a capability (after validation) 184 + /// 185 + /// # Security 186 + /// Validates seal and rights before returning the handle. 187 + pub fn get_handle(&self, id: CapabilityId, required_rights: CapabilityRights) -> Result<ObjectHandle, CapabilityError> { 188 + let cap = self.get(id)?; 189 + 190 + if !cap.rights.contains(required_rights) { 191 + return Err(CapabilityError::PermissionDenied); 192 + } 193 + 194 + Ok(cap.handle) 195 + } 196 + 197 + /// Get number of capabilities in table 198 + pub fn len(&self) -> usize { 199 + self.table.len() 200 + } 201 + 202 + /// Check if table is empty 203 + pub fn is_empty(&self) -> bool { 204 + self.table.is_empty() 205 + } 206 + 207 + /// Clear all capabilities from table 208 + /// 209 + /// # Security 210 + /// Use with caution - this revokes ALL capabilities in the table. 211 + pub fn clear(&mut self) { 212 + self.table.clear(); 213 + } 214 + } 215 + 216 + impl Default for CapabilityTable { 217 + fn default() -> Self { 218 + Self::new() 219 + } 220 + } 221 + 222 + #[cfg(test)] 223 + mod tests { 224 + use super::*; 225 + 226 + #[test] 227 + fn test_insert_and_lookup() { 228 + let mut table = CapabilityTable::new(); 229 + let handle = ObjectHandle(0x1000); 230 + let rights = CapabilityRights::READ; 231 + 232 + let cap = SealedCapability::new(handle, rights); 233 + let cap_id = table.insert(cap).unwrap(); 234 + 235 + // Should be able to retrieve 236 + let retrieved = table.get(cap_id).unwrap(); 237 + assert_eq!(retrieved.handle, handle); 238 + assert_eq!(retrieved.rights, rights); 239 + } 240 + 241 + #[test] 242 + fn test_invalid_id() { 243 + let table = CapabilityTable::new(); 244 + let invalid_id = CapabilityId::new(9999); 245 + 246 + // Should fail - ID not in table 247 + assert_eq!(table.get(invalid_id), Err(CapabilityError::InvalidId)); 248 + } 249 + 250 + #[test] 251 + fn test_rights_check() { 252 + let mut table = CapabilityTable::new(); 253 + let handle = ObjectHandle(0x1000); 254 + let rights = CapabilityRights::READ; 255 + 256 + let cap = SealedCapability::new(handle, rights); 257 + let cap_id = table.insert(cap).unwrap(); 258 + 259 + // Should succeed - has READ 260 + assert!(table.check_rights(cap_id, CapabilityRights::READ).is_ok()); 261 + 262 + // Should fail - doesn't have WRITE 263 + assert_eq!( 264 + table.check_rights(cap_id, CapabilityRights::WRITE), 265 + Err(CapabilityError::PermissionDenied) 266 + ); 267 + } 268 + 269 + #[test] 270 + fn test_derive_attenuation() { 271 + let mut table = CapabilityTable::new(); 272 + let handle = ObjectHandle(0x1000); 273 + let parent_rights = CapabilityRights::READ | CapabilityRights::WRITE; 274 + 275 + let parent = SealedCapability::new(handle, parent_rights); 276 + let parent_id = table.insert(parent).unwrap(); 277 + 278 + // Derive read-only capability 279 + let child_id = table.derive(parent_id, CapabilityRights::READ).unwrap(); 280 + 281 + // Child should have reduced rights 282 + let child = table.get(child_id).unwrap(); 283 + assert!(child.rights.contains(CapabilityRights::READ)); 284 + assert!(!child.rights.contains(CapabilityRights::WRITE)); 285 + } 286 + 287 + #[test] 288 + #[should_panic(expected = "Cannot amplify rights")] 289 + fn test_derive_amplification_panics() { 290 + let mut table = CapabilityTable::new(); 291 + let handle = ObjectHandle(0x1000); 292 + let parent_rights = CapabilityRights::READ; 293 + 294 + let parent = SealedCapability::new(handle, parent_rights); 295 + let parent_id = table.insert(parent).unwrap(); 296 + 297 + // Try to derive with MORE rights (should panic) 298 + let _ = table.derive(parent_id, CapabilityRights::READ | CapabilityRights::WRITE); 299 + } 300 + 301 + #[test] 302 + fn test_revocation() { 303 + let mut table = CapabilityTable::new(); 304 + let handle = ObjectHandle(0x1000); 305 + let cap = SealedCapability::new(handle, CapabilityRights::READ); 306 + let cap_id = table.insert(cap).unwrap(); 307 + 308 + // Should exist 309 + assert!(table.get(cap_id).is_ok()); 310 + 311 + // Revoke 312 + let removed = table.remove(cap_id); 313 + assert!(removed.is_some()); 314 + 315 + // Should no longer exist 316 + assert_eq!(table.get(cap_id), Err(CapabilityError::InvalidId)); 317 + } 318 + 319 + #[test] 320 + fn test_table_full() { 321 + let mut table = CapabilityTable::new(); 322 + 323 + // Fill table to capacity 324 + for i in 0..MAX_CAPABILITIES { 325 + let handle = ObjectHandle(i as u64); 326 + let cap = SealedCapability::new(handle, CapabilityRights::READ); 327 + assert!(table.insert(cap).is_ok()); 328 + } 329 + 330 + // Next insert should fail 331 + let cap = SealedCapability::new(ObjectHandle(0xFFFF), CapabilityRights::READ); 332 + assert_eq!(table.insert(cap), Err(CapabilityError::TableFull)); 333 + } 334 + }
+7
heartwood/src/mana_pool/entropy.rs
··· 74 74 Self::from_seed(seed) 75 75 } 76 76 77 + /// Create ChaCha8 RNG using fast RDTSC-only seeding (boot-safe) 78 + /// Use this during early boot when RDRAND might not be available 79 + pub fn from_hardware_fast() -> Self { 80 + let seed = HardwareRng::fast_u64(); 81 + Self::from_seed(seed) 82 + } 83 + 77 84 /// Create a new ChaCha8 RNG from a 64-bit seed 78 85 pub fn from_seed(seed: u64) -> Self { 79 86 let mut state = [0u32; 16];
+10 -3
heartwood/src/mana_pool/mod.rs
··· 17 17 18 18 pub mod object_manager; 19 19 pub mod capability; 20 + pub mod capability_table; // Opaque capability handle mapping 20 21 pub mod sanctuary; 21 22 pub mod ephemeral_mist; 22 23 pub mod allocator; ··· 24 25 pub mod buddy; 25 26 pub mod entropy; // Random number generation for ASLR 26 27 pub mod aslr; // Address Space Layout Randomization 28 + pub mod sealing; // Cryptographic capability sealing 27 29 28 30 pub use object_manager::{ObjectManager, ObjectHandle, ObjectType, ObjectInfo}; 29 - pub use capability::{Capability, CapabilityRights}; 31 + pub use capability::{Capability, CapabilityRights, CapabilityId, SealedCapability}; 32 + pub use capability_table::{CapabilityTable, CapabilityError}; 30 33 pub use sanctuary::Sanctuary; 31 34 pub use ephemeral_mist::EphemeralMist; 32 35 pub use interrupt_lock::InterruptSafeLock; ··· 40 43 static mut MANA_POOL_INITIALIZED: bool = false; 41 44 42 45 pub struct ManaPool { 43 - object_manager: ObjectManager, 46 + pub(crate) object_manager: ObjectManager, 44 47 sanctuary: Sanctuary, 45 48 ephemeral_mist: EphemeralMist, 46 49 } ··· 185 188 } 186 189 187 190 /// Get reference to MANA_POOL (assumes initialized) 188 - unsafe fn get_mana_pool() -> &'static InterruptSafeLock<Box<ManaPool>> { 191 + pub(crate) unsafe fn get_mana_pool() -> &'static InterruptSafeLock<Box<ManaPool>> { 189 192 &*core::ptr::addr_of!(MANA_POOL).cast::<InterruptSafeLock<Box<ManaPool>>>() 190 193 } 191 194 ··· 272 275 /// SECURITY: Attempting to create a capability with both WRITE and EXECUTE rights 273 276 /// This violates the W^X (Write XOR Execute) security policy 274 277 SecurityViolation, 278 + /// SECURITY: Permission denied - capability doesn't grant required rights 279 + PermissionDenied, 280 + /// Capability table is full (DOS protection) 281 + CapabilityTableFull, 275 282 }
+137 -1
heartwood/src/mana_pool/object_manager.rs
··· 1 1 //! Object Manager - Manages memory as abstract objects 2 2 3 - use super::capability::{Capability, CapabilityRights}; 3 + use super::capability::{Capability, CapabilityRights, CapabilityId, SealedCapability}; 4 + use super::capability_table::{CapabilityTable, CapabilityError}; 4 5 use super::{AllocationPurpose, ManaError}; 5 6 use alloc::collections::BTreeMap; 6 7 ··· 34 35 pub struct ObjectManager { 35 36 objects: BTreeMap<ObjectHandle, Object>, 36 37 next_handle: u64, 38 + /// Capability table - maps opaque IDs to sealed capabilities 39 + /// This enforces the opaque handle security model 40 + capability_table: CapabilityTable, 37 41 } 38 42 39 43 impl Default for ObjectManager { ··· 47 51 Self { 48 52 objects: BTreeMap::new(), 49 53 next_handle: 1, // 0 is reserved as invalid 54 + capability_table: CapabilityTable::new(), 50 55 } 51 56 } 52 57 ··· 200 205 purpose: object.purpose, 201 206 ref_count: object.ref_count, 202 207 }) 208 + } 209 + 210 + // ==================== SEALED CAPABILITY METHODS ==================== 211 + 212 + /// Create object and return OPAQUE capability ID (not the capability itself!) 213 + /// 214 + /// This is the secure API that enforces opaque handles: 215 + /// - Creates object 216 + /// - Creates sealed capability with cryptographic seal 217 + /// - Stores capability in table 218 + /// - Returns ONLY the opaque ID to caller 219 + /// 220 + /// # Security 221 + /// Caller never sees the actual capability, only the meaningless ID. 222 + /// Without access to the capability table, the ID is useless. 223 + pub fn create_object_sealed( 224 + &mut self, 225 + address: usize, 226 + size: usize, 227 + purpose: AllocationPurpose, 228 + rights: CapabilityRights, 229 + ) -> Result<CapabilityId, ManaError> { 230 + // Create the object 231 + let handle = ObjectHandle(self.next_handle); 232 + self.next_handle += 1; 233 + 234 + let object = Object { 235 + handle, 236 + object_type: ObjectType::Memory, 237 + address, 238 + size, 239 + purpose, 240 + ref_count: 1, 241 + }; 242 + 243 + self.objects.insert(handle, object); 244 + 245 + // Create sealed capability 246 + let sealed_cap = SealedCapability::new(handle, rights); 247 + 248 + // Store in capability table 249 + let cap_id = self.capability_table.insert(sealed_cap) 250 + .map_err(|_| ManaError::CapabilityTableFull)?; 251 + 252 + // Return only the opaque ID! 253 + Ok(cap_id) 254 + } 255 + 256 + /// Get object info using capability ID (validates seal) 257 + /// 258 + /// # Security 259 + /// - Looks up capability by ID (prevents forgery) 260 + /// - Validates cryptographic seal (prevents tampering) 261 + /// - Checks rights before allowing access 262 + pub fn get_object_info_sealed(&self, cap_id: CapabilityId) -> Result<ObjectInfo, ManaError> { 263 + // Lookup and validate capability 264 + let cap = self.capability_table.get(cap_id) 265 + .map_err(|e| match e { 266 + CapabilityError::InvalidId => ManaError::InvalidCapability, 267 + CapabilityError::SealBroken => ManaError::SecurityViolation, 268 + _ => ManaError::InvalidCapability, 269 + })?; 270 + 271 + // Get object 272 + let object = self.objects.get(&cap.handle) 273 + .ok_or(ManaError::InvalidHandle)?; 274 + 275 + Ok(ObjectInfo { 276 + object_type: object.object_type, 277 + size: object.size, 278 + purpose: object.purpose, 279 + ref_count: object.ref_count, 280 + }) 281 + } 282 + 283 + /// Release object using capability ID 284 + pub fn release_object_sealed(&mut self, cap_id: CapabilityId) -> Result<(), ManaError> { 285 + // Lookup and validate capability 286 + let cap = self.capability_table.get(cap_id) 287 + .map_err(|e| match e { 288 + CapabilityError::InvalidId => ManaError::InvalidCapability, 289 + CapabilityError::SealBroken => ManaError::SecurityViolation, 290 + _ => ManaError::InvalidCapability, 291 + })?; 292 + 293 + let handle = cap.handle; 294 + 295 + // Remove from capability table (revoke access) 296 + self.capability_table.remove(cap_id); 297 + 298 + // Decrement ref count 299 + let object = self.objects.get_mut(&handle) 300 + .ok_or(ManaError::InvalidHandle)?; 301 + 302 + if object.ref_count == 0 { 303 + return Err(ManaError::AlreadyReleased); 304 + } 305 + 306 + object.ref_count -= 1; 307 + 308 + if object.ref_count == 0 { 309 + // Free the object 310 + self.objects.remove(&handle); 311 + } 312 + 313 + Ok(()) 314 + } 315 + 316 + /// Derive a new sealed capability with reduced rights 317 + /// 318 + /// # Security 319 + /// Can only reduce rights (attenuation), never amplify. 320 + pub fn derive_capability_sealed(&mut self, parent_id: CapabilityId, new_rights: CapabilityRights) -> Result<CapabilityId, ManaError> { 321 + self.capability_table.derive(parent_id, new_rights) 322 + .map_err(|e| match e { 323 + CapabilityError::InvalidId => ManaError::InvalidCapability, 324 + CapabilityError::SealBroken => ManaError::SecurityViolation, 325 + CapabilityError::TableFull => ManaError::CapabilityTableFull, 326 + _ => ManaError::InvalidCapability, 327 + }) 328 + } 329 + 330 + /// Check if capability ID grants specific rights 331 + pub fn check_capability_rights(&self, cap_id: CapabilityId, required: CapabilityRights) -> Result<(), ManaError> { 332 + self.capability_table.check_rights(cap_id, required) 333 + .map_err(|e| match e { 334 + CapabilityError::InvalidId => ManaError::InvalidCapability, 335 + CapabilityError::SealBroken => ManaError::SecurityViolation, 336 + CapabilityError::PermissionDenied => ManaError::PermissionDenied, 337 + _ => ManaError::InvalidCapability, 338 + }) 203 339 } 204 340 } 205 341
+214
heartwood/src/mana_pool/sealing.rs
··· 1 + ///! Capability Sealing - Cryptographic protection against forgery 2 + ///! 3 + ///! This module implements unforgeable capability seals using HMAC-SHA256. 4 + ///! Each capability is sealed with a kernel-only secret key, preventing 5 + ///! user space from forging or tampering with capabilities. 6 + ///! 7 + ///! # Security Properties 8 + ///! - **Unforgeable**: Can't create valid seal without kernel secret key 9 + ///! - **Tamper-Proof**: Modifying capability data invalidates seal 10 + ///! - **Constant-Time**: Verification uses constant-time comparison 11 + ///! - **Kernel-Only**: Secret key never leaves kernel space 12 + 13 + use core::mem::MaybeUninit; 14 + use crate::mana_pool::entropy::{HardwareRng, ChaCha8Rng}; 15 + 16 + /// Capability sealer using HMAC-SHA256 17 + pub struct CapabilitySealer { 18 + /// Secret 256-bit key for HMAC (kernel-only, never exposed) 19 + seal_key: [u8; 32], 20 + } 21 + 22 + impl CapabilitySealer { 23 + /// Create a new sealer with cryptographically random key 24 + /// 25 + /// # Security 26 + /// The seal key is generated using hardware entropy (RDRAND/RDTSC) 27 + /// and mixed with ChaCha8 for additional randomness. This key must 28 + /// never be exposed to user space. 29 + /// 30 + /// # Safety 31 + /// Must be called only once during kernel initialization 32 + pub fn new() -> Self { 33 + let seal_key = Self::generate_seal_key(); 34 + Self { seal_key } 35 + } 36 + 37 + /// Generate a cryptographically secure seal key 38 + /// 39 + /// Uses hardware entropy sources (RDTSC for boot-safety) 40 + /// mixed with ChaCha8 to generate 256 bits of entropy. 41 + fn generate_seal_key() -> [u8; 32] { 42 + let mut key = [0u8; 32]; 43 + 44 + // Use ChaCha8 seeded from fast hardware entropy (RDTSC-only for boot safety) 45 + let mut rng = ChaCha8Rng::from_hardware_fast(); 46 + 47 + // Generate 32 random bytes 48 + for i in 0..8 { 49 + let random = rng.next_u32(); 50 + let bytes = random.to_le_bytes(); 51 + key[i * 4..(i + 1) * 4].copy_from_slice(&bytes); 52 + } 53 + 54 + key 55 + } 56 + 57 + /// Compute HMAC-SHA256 seal for capability data 58 + /// 59 + /// # Arguments 60 + /// * `data` - Serialized capability data to seal 61 + /// 62 + /// # Returns 63 + /// 256-bit HMAC-SHA256 tag 64 + pub fn seal(&self, data: &[u8]) -> [u8; 32] { 65 + hmac_sha256::HMAC::mac(data, &self.seal_key) 66 + } 67 + 68 + /// Verify capability seal (constant-time) 69 + /// 70 + /// # Arguments 71 + /// * `data` - Capability data to verify 72 + /// * `seal` - Seal tag to check 73 + /// 74 + /// # Returns 75 + /// `true` if seal is valid, `false` otherwise 76 + /// 77 + /// # Security 78 + /// Uses constant-time comparison to prevent timing attacks. 79 + /// An attacker cannot learn information about the correct seal 80 + /// by measuring verification time. 81 + pub fn verify(&self, data: &[u8], seal: &[u8; 32]) -> bool { 82 + let expected_seal = self.seal(data); 83 + constant_time_compare(&expected_seal, seal) 84 + } 85 + } 86 + 87 + /// Constant-time equality comparison 88 + /// 89 + /// Compares two byte slices in constant time to prevent timing attacks. 90 + /// Always compares all bytes regardless of when a mismatch is found. 91 + /// 92 + /// # Security 93 + /// This function takes the same amount of time whether the inputs match 94 + /// or differ in the first byte vs last byte, preventing attackers from 95 + /// learning information about the correct value through timing analysis. 96 + fn constant_time_compare(a: &[u8; 32], b: &[u8; 32]) -> bool { 97 + let mut diff: u8 = 0; 98 + for i in 0..32 { 99 + diff |= a[i] ^ b[i]; 100 + } 101 + diff == 0 102 + } 103 + 104 + /// Global capability sealer (initialized at boot) 105 + static mut SEALER: MaybeUninit<CapabilitySealer> = MaybeUninit::uninit(); 106 + static mut SEALER_INITIALIZED: bool = false; 107 + 108 + /// Initialize the global capability sealer 109 + /// 110 + /// # Safety 111 + /// Must be called exactly once during kernel initialization, 112 + /// before any capabilities are created. 113 + pub unsafe fn init() { 114 + if !SEALER_INITIALIZED { 115 + let sealer = CapabilitySealer::new(); 116 + core::ptr::write(core::ptr::addr_of_mut!(SEALER).cast(), sealer); 117 + SEALER_INITIALIZED = true; 118 + // TODO: Add logging when serial_println! is available 119 + } 120 + } 121 + 122 + /// Get reference to global sealer 123 + /// 124 + /// # Safety 125 + /// Caller must ensure `init()` has been called 126 + pub unsafe fn get_sealer() -> &'static CapabilitySealer { 127 + debug_assert!(SEALER_INITIALIZED, "Sealer not initialized!"); 128 + &*core::ptr::addr_of!(SEALER).cast::<CapabilitySealer>() 129 + } 130 + 131 + #[cfg(test)] 132 + mod tests { 133 + use super::*; 134 + 135 + #[test] 136 + fn test_seal_generation() { 137 + let sealer = CapabilitySealer::new(); 138 + let data = b"test capability data"; 139 + 140 + let seal = sealer.seal(data); 141 + 142 + // Seal should be deterministic for same data 143 + let seal2 = sealer.seal(data); 144 + assert_eq!(seal, seal2); 145 + } 146 + 147 + #[test] 148 + fn test_seal_verification() { 149 + let sealer = CapabilitySealer::new(); 150 + let data = b"test capability data"; 151 + 152 + let seal = sealer.seal(data); 153 + 154 + // Valid seal should verify 155 + assert!(sealer.verify(data, &seal)); 156 + } 157 + 158 + #[test] 159 + fn test_tampered_data_fails() { 160 + let sealer = CapabilitySealer::new(); 161 + let data = b"test capability data"; 162 + 163 + let seal = sealer.seal(data); 164 + 165 + // Modified data should fail 166 + let tampered = b"test capability DATA"; // Changed case 167 + assert!(!sealer.verify(tampered, &seal)); 168 + } 169 + 170 + #[test] 171 + fn test_tampered_seal_fails() { 172 + let sealer = CapabilitySealer::new(); 173 + let data = b"test capability data"; 174 + 175 + let seal = sealer.seal(data); 176 + 177 + // Flip one bit in seal 178 + let mut bad_seal = seal; 179 + bad_seal[0] ^= 1; 180 + 181 + assert!(!sealer.verify(data, &bad_seal)); 182 + } 183 + 184 + #[test] 185 + fn test_different_keys_different_seals() { 186 + let sealer1 = CapabilitySealer::new(); 187 + let sealer2 = CapabilitySealer::new(); 188 + let data = b"test capability data"; 189 + 190 + let seal1 = sealer1.seal(data); 191 + let seal2 = sealer2.seal(data); 192 + 193 + // Different keys should produce different seals 194 + // (With overwhelming probability) 195 + assert_ne!(seal1, seal2); 196 + } 197 + 198 + #[test] 199 + fn test_constant_time_compare() { 200 + let a = [0u8; 32]; 201 + let b = [0u8; 32]; 202 + let mut c = [0u8; 32]; 203 + c[0] = 1; // Differ in first byte 204 + let mut d = [0u8; 32]; 205 + d[31] = 1; // Differ in last byte 206 + 207 + assert!(constant_time_compare(&a, &b)); 208 + assert!(!constant_time_compare(&a, &c)); 209 + assert!(!constant_time_compare(&a, &d)); 210 + 211 + // All comparisons should take same time (not verifiable in test, 212 + // but at least check correctness) 213 + } 214 + }
+200 -47
heartwood/src/wards_command.rs
··· 5 5 /// - ASLR (Address Space Layout Randomization) status 6 6 /// - Thread stack addresses and randomization entropy 7 7 pub fn cmd_wards() { 8 - crate::println!("◈ Security Wards of the Mana Pool"); 8 + unsafe { 9 + crate::eldarin::PAGING_ACTIVE = true; 10 + crate::eldarin::PAGING_PAGE = 0; 11 + crate::eldarin::PAGING_COMMAND = Some(crate::eldarin::PagingCommand::Wards); 12 + } 13 + show_wards_page(0); 14 + } 15 + 16 + /// Show a specific page of the wards output (called by paging system) 17 + pub fn show_wards_page(page: usize) { 18 + use crate::loom_of_fate::{ThreadState, without_interrupts}; 19 + use crate::eldarin::display_prompt; 20 + 21 + match page { 22 + 0 => { 23 + // Page 1: W^X, ASLR, Thread Stacks 24 + crate::println!("◈ Security Wards of the Mana Pool"); 25 + crate::println!(); 26 + 27 + // W^X Status 28 + crate::println!(" Write ⊕ Execute Enforcement: ✓ Active"); 29 + crate::println!(" Memory pages cannot be both writable and executable"); 30 + crate::println!(); 31 + 32 + // ASLR Status 33 + crate::println!(" Address Space Layout Randomization: ✓ Active"); 34 + crate::println!(" Thread stacks randomized with 0-64KB entropy"); 35 + crate::println!(); 36 + 37 + // Thread Stack Information 38 + crate::println!(" Thread Stack Wards:"); 39 + 40 + without_interrupts(|| { 41 + unsafe { 42 + let loom = crate::loom_of_fate::get_loom().lock(); 43 + let threads: alloc::vec::Vec<_> = loom.threads.iter() 44 + .filter(|t| !matches!(t.state, ThreadState::Fading)) 45 + .collect(); 46 + 47 + for thread in threads { 48 + let stack_size = thread.stack_top - thread.stack_bottom; 49 + let nominal_top = thread.stack_bottom + stack_size; 50 + let aslr_offset = if thread.stack_top < nominal_top { 51 + nominal_top - thread.stack_top 52 + } else { 53 + 0 54 + }; 55 + 56 + let state_str = match thread.state { 57 + ThreadState::Weaving => "Weaving", 58 + ThreadState::Resting => "Resting", 59 + ThreadState::Tangled => "Tangled", 60 + ThreadState::Fading => "Fading", 61 + }; 62 + 63 + crate::println!(" Thread #{} ({}): Stack 0x{:016x}-0x{:016x}", 64 + thread.id.0, 65 + state_str, 66 + thread.stack_bottom, 67 + thread.stack_top 68 + ); 69 + crate::println!(" Size: {} KB, ASLR offset: ~{} bytes", 70 + stack_size / 1024, 71 + aslr_offset 72 + ); 73 + } 74 + } 75 + }); 76 + 77 + crate::println!(); 78 + crate::println!(" Entropy Source: RDTSC (fast boot-safe randomization)"); 79 + crate::println!(); 80 + crate::println!("The wards stand strong. Your sanctuary is protected."); 81 + crate::println!(); 82 + crate::println!("─── Press ENTER for capability tests (1/3) ───"); 83 + } 84 + 1 => { 85 + // Page 2: Start capability tests 86 + test_capability_sealing_page1(); 87 + crate::println!(); 88 + crate::println!("─── Press ENTER to continue (2/3) ───"); 89 + } 90 + 2 => { 91 + // Page 3: Finish capability tests 92 + test_capability_sealing_page2(); 93 + crate::println!(); 94 + // Exit paging mode 95 + unsafe { 96 + crate::eldarin::PAGING_ACTIVE = false; 97 + crate::eldarin::PAGING_PAGE = 0; 98 + crate::eldarin::PAGING_COMMAND = None; 99 + } 100 + display_prompt(); 101 + } 102 + _ => { 103 + // Safety fallback 104 + unsafe { 105 + crate::eldarin::PAGING_ACTIVE = false; 106 + crate::eldarin::PAGING_PAGE = 0; 107 + crate::eldarin::PAGING_COMMAND = None; 108 + } 109 + display_prompt(); 110 + } 111 + } 112 + } 113 + 114 + /// Capability test page 1: Create, access, derive 115 + fn test_capability_sealing_page1() { 116 + use crate::loom_of_fate::without_interrupts; 117 + use crate::mana_pool::{CapabilityRights, AllocationPurpose}; 118 + 119 + crate::println!("◈ Testing Capability Security System"); 9 120 crate::println!(); 10 121 11 - // W^X Status 12 - crate::println!(" Write ⊕ Execute Enforcement: ✓ Active"); 13 - crate::println!(" Memory pages cannot be both writable and executable"); 14 - crate::println!(); 122 + without_interrupts(|| { 123 + unsafe { 124 + let mut mana_pool = crate::mana_pool::get_mana_pool().lock(); 125 + 126 + // Test 1: Create a sealed capability (returns opaque ID) 127 + crate::println!(" Test 1: Creating sealed capability with READ+WRITE rights..."); 128 + match mana_pool.object_manager.create_object_sealed( 129 + 0x1000000, // Dummy address 130 + 4096, // 4KB object 131 + AllocationPurpose::ShortLived, 132 + CapabilityRights::read_write(), 133 + ) { 134 + Ok(cap_id) => { 135 + crate::println!(" ✓ Created opaque capability ID: {}", cap_id.raw()); 15 136 16 - // ASLR Status 17 - crate::println!(" Address Space Layout Randomization: ✓ Active"); 18 - crate::println!(" Thread stacks randomized with 0-64KB entropy"); 19 - crate::println!(); 137 + // Test 2: Access object through opaque ID 138 + crate::println!(" Test 2: Accessing object info via opaque ID..."); 139 + match mana_pool.object_manager.get_object_info_sealed(cap_id) { 140 + Ok(info) => { 141 + crate::println!(" ✓ Object info retrieved: size = {} bytes", info.size); 142 + } 143 + Err(e) => { 144 + crate::println!(" ✗ Failed to get object info: {:?}", e); 145 + } 146 + } 20 147 21 - // Thread Stack Information 22 - crate::println!(" Thread Stack Wards:"); 148 + // Test 3: Derive capability with reduced rights (attenuation) 149 + crate::println!(" Test 3: Deriving READ-ONLY capability (attenuation)..."); 150 + match mana_pool.object_manager.derive_capability_sealed( 151 + cap_id, 152 + CapabilityRights::read_only(), 153 + ) { 154 + Ok(derived_id) => { 155 + crate::println!(" ✓ Derived capability ID: {}", derived_id.raw()); 23 156 24 - use crate::attunement::interrupts::without_interrupts; 25 - use crate::loom_of_fate::ThreadState; 157 + // Store for page 2 158 + unsafe { 159 + STORED_CAP_ID = Some(cap_id); 160 + STORED_DERIVED_ID = Some(derived_id); 161 + } 162 + } 163 + Err(e) => { 164 + crate::println!(" ✗ Derivation failed: {:?}", e); 165 + } 166 + } 167 + } 168 + Err(e) => { 169 + crate::println!(" ✗ Failed to create sealed capability: {:?}", e); 170 + } 171 + } 172 + } 173 + }); 174 + } 175 + 176 + /// Capability test page 2: Verify rights and release 177 + fn test_capability_sealing_page2() { 178 + use crate::loom_of_fate::without_interrupts; 179 + use crate::mana_pool::CapabilityRights; 26 180 27 181 without_interrupts(|| { 28 182 unsafe { 29 - let loom = crate::loom_of_fate::get_loom().lock(); 30 - let threads: alloc::vec::Vec<_> = loom.threads.iter() 31 - .filter(|t| !matches!(t.state, ThreadState::Fading)) 32 - .collect(); 183 + let mut mana_pool = crate::mana_pool::get_mana_pool().lock(); 33 184 34 - for thread in threads { 35 - let stack_size = thread.stack_top - thread.stack_bottom; 185 + if let (Some(cap_id), Some(derived_id)) = (STORED_CAP_ID, STORED_DERIVED_ID) { 186 + // Test 4: Check that derived capability has only READ rights 187 + crate::println!(" Test 4: Verifying derived capability has READ (not WRITE)..."); 188 + match mana_pool.object_manager.check_capability_rights( 189 + derived_id, 190 + CapabilityRights::READ, 191 + ) { 192 + Ok(_) => crate::println!(" ✓ READ permission verified"), 193 + Err(_) => crate::println!(" ✗ READ check failed!"), 194 + } 36 195 37 - // Calculate approximate ASLR offset based on stack pointer alignment 38 - // The actual offset is embedded in the Stack struct, but we can estimate 39 - // it from the stack top alignment 40 - let nominal_top = thread.stack_bottom + stack_size; 41 - let aslr_offset = if thread.stack_top < nominal_top { 42 - nominal_top - thread.stack_top 43 - } else { 44 - 0 45 - }; 196 + match mana_pool.object_manager.check_capability_rights( 197 + derived_id, 198 + CapabilityRights::WRITE, 199 + ) { 200 + Ok(_) => crate::println!(" ✗ SECURITY BUG: WRITE should be denied!"), 201 + Err(_) => crate::println!(" ✓ WRITE permission correctly denied"), 202 + } 46 203 47 - let state_str = match thread.state { 48 - ThreadState::Weaving => "Weaving", 49 - ThreadState::Resting => "Resting", 50 - ThreadState::Tangled => "Tangled", 51 - ThreadState::Fading => "Fading", 52 - }; 204 + // Test 5: Release the capability 205 + crate::println!(" Test 5: Releasing sealed capability..."); 206 + match mana_pool.object_manager.release_object_sealed(cap_id) { 207 + Ok(_) => crate::println!(" ✓ Capability revoked successfully"), 208 + Err(e) => crate::println!(" ✗ Release failed: {:?}", e), 209 + } 53 210 54 - crate::println!(" Thread #{} ({}): Stack 0x{:016x}-0x{:016x}", 55 - thread.id.0, 56 - state_str, 57 - thread.stack_bottom, 58 - thread.stack_top 59 - ); 60 - crate::println!(" Size: {} KB, ASLR offset: ~{} bytes", 61 - stack_size / 1024, 62 - aslr_offset 63 - ); 211 + // Clear stored IDs 212 + STORED_CAP_ID = None; 213 + STORED_DERIVED_ID = None; 64 214 } 65 215 } 66 216 }); 67 217 68 218 crate::println!(); 69 - crate::println!(" Entropy Source: RDTSC (fast boot-safe randomization)"); 70 - crate::println!(); 71 - crate::println!("The wards stand strong. Your sanctuary is protected."); 219 + crate::println!(" Security test complete. Opaque capabilities working correctly."); 72 220 } 221 + 222 + // Store capability IDs between pages 223 + use crate::mana_pool::capability::CapabilityId; 224 + static mut STORED_CAP_ID: Option<CapabilityId> = None; 225 + static mut STORED_DERIVED_ID: Option<CapabilityId> = None;
isodir/boot/aethelos/heartwood.bin

This is a binary file and will not be displayed.