···43pub mod vga_buffer;
44pub mod rtl; // Runtime library with memcpy, etc.
45pub mod eldarin; // The Eldarin Shell
046pub mod irq_safe_mutex; // Interrupt-safe mutex primitive
47pub mod vfs; // Virtual File System layer
48pub mod drivers; // Hardware device drivers
···43pub mod vga_buffer;
44pub mod rtl; // Runtime library with memcpy, etc.
45pub mod eldarin; // The Eldarin Shell
46+pub mod wards_command; // Security wards command
47pub mod irq_safe_mutex; // Interrupt-safe mutex primitive
48pub mod vfs; // Virtual File System layer
49pub mod drivers; // Hardware device drivers
+1-1
heartwood/src/loom_of_fate/mod.rs
···120121/// Execute a closure with interrupts disabled
122/// This prevents deadlocks when acquiring locks that might be used in interrupt handlers
123-fn without_interrupts<F, R>(f: F) -> R
124where
125 F: FnOnce() -> R,
126{
···120121/// Execute a closure with interrupts disabled
122/// This prevents deadlocks when acquiring locks that might be used in interrupt handlers
123+pub(crate) fn without_interrupts<F, R>(f: F) -> R
124where
125 F: FnOnce() -> R,
126{
+6
heartwood/src/main.rs
···187 unsafe { serial_out(b'D'); }
188 println!(" ✓ Mana Pool ready");
189000000190 // Initialize the Nexus (IPC)
191 unsafe { serial_out(b'E'); }
192 println!("◈ Opening the Nexus...");
···187 unsafe { serial_out(b'D'); }
188 println!(" ✓ Mana Pool ready");
189190+ // 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+196 // Initialize the Nexus (IPC)
197 unsafe { serial_out(b'E'); }
198 println!("◈ Opening the Nexus...");
+197
heartwood/src/mana_pool/capability.rs
···1//! Capability-based security for memory access
23use super::object_manager::ObjectHandle;
0045/// A capability grants specific rights to an object
6#[derive(Debug, Clone, Copy)]
···32 /// Check if this capability can be transferred to another process
33 pub fn can_transfer(&self) -> bool {
34 self.rights.contains(CapabilityRights::TRANSFER)
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035 }
36}
37
···1//! Capability-based security for memory access
23use super::object_manager::ObjectHandle;
4+use super::sealing;
5+use core::sync::atomic::{AtomicU64, Ordering};
67/// A capability grants specific rights to an object
8#[derive(Debug, Clone, Copy)]
···34 /// Check if this capability can be transferred to another process
35 pub fn can_transfer(&self) -> bool {
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+ }
232 }
233}
234
···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 Self::from_seed(seed)
75 }
76000000077 /// Create a new ChaCha8 RNG from a 64-bit seed
78 pub fn from_seed(seed: u64) -> Self {
79 let mut state = [0u32; 16];
···74 Self::from_seed(seed)
75 }
7677+ /// 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+84 /// Create a new ChaCha8 RNG from a 64-bit seed
85 pub fn from_seed(seed: u64) -> Self {
86 let mut state = [0u32; 16];
+10-3
heartwood/src/mana_pool/mod.rs
···1718pub mod object_manager;
19pub mod capability;
020pub mod sanctuary;
21pub mod ephemeral_mist;
22pub mod allocator;
···24pub mod buddy;
25pub mod entropy; // Random number generation for ASLR
26pub mod aslr; // Address Space Layout Randomization
02728pub use object_manager::{ObjectManager, ObjectHandle, ObjectType, ObjectInfo};
29-pub use capability::{Capability, CapabilityRights};
030pub use sanctuary::Sanctuary;
31pub use ephemeral_mist::EphemeralMist;
32pub use interrupt_lock::InterruptSafeLock;
···40static mut MANA_POOL_INITIALIZED: bool = false;
4142pub struct ManaPool {
43- object_manager: ObjectManager,
44 sanctuary: Sanctuary,
45 ephemeral_mist: EphemeralMist,
46}
···185}
186187/// Get reference to MANA_POOL (assumes initialized)
188-unsafe fn get_mana_pool() -> &'static InterruptSafeLock<Box<ManaPool>> {
189 &*core::ptr::addr_of!(MANA_POOL).cast::<InterruptSafeLock<Box<ManaPool>>>()
190}
191···272 /// SECURITY: Attempting to create a capability with both WRITE and EXECUTE rights
273 /// This violates the W^X (Write XOR Execute) security policy
274 SecurityViolation,
0000275}
···1718pub mod object_manager;
19pub mod capability;
20+pub mod capability_table; // Opaque capability handle mapping
21pub mod sanctuary;
22pub mod ephemeral_mist;
23pub mod allocator;
···25pub mod buddy;
26pub mod entropy; // Random number generation for ASLR
27pub mod aslr; // Address Space Layout Randomization
28+pub mod sealing; // Cryptographic capability sealing
2930pub use object_manager::{ObjectManager, ObjectHandle, ObjectType, ObjectInfo};
31+pub use capability::{Capability, CapabilityRights, CapabilityId, SealedCapability};
32+pub use capability_table::{CapabilityTable, CapabilityError};
33pub use sanctuary::Sanctuary;
34pub use ephemeral_mist::EphemeralMist;
35pub use interrupt_lock::InterruptSafeLock;
···43static mut MANA_POOL_INITIALIZED: bool = false;
4445pub struct ManaPool {
46+ pub(crate) object_manager: ObjectManager,
47 sanctuary: Sanctuary,
48 ephemeral_mist: EphemeralMist,
49}
···188}
189190/// Get reference to MANA_POOL (assumes initialized)
191+pub(crate) unsafe fn get_mana_pool() -> &'static InterruptSafeLock<Box<ManaPool>> {
192 &*core::ptr::addr_of!(MANA_POOL).cast::<InterruptSafeLock<Box<ManaPool>>>()
193}
194···275 /// SECURITY: Attempting to create a capability with both WRITE and EXECUTE rights
276 /// This violates the W^X (Write XOR Execute) security policy
277 SecurityViolation,
278+ /// SECURITY: Permission denied - capability doesn't grant required rights
279+ PermissionDenied,
280+ /// Capability table is full (DOS protection)
281+ CapabilityTableFull,
282}
···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/// - ASLR (Address Space Layout Randomization) status
6/// - Thread stack addresses and randomization entropy
7pub fn cmd_wards() {
8- crate::println!("◈ Security Wards of the Mana Pool");
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009 crate::println!();
1011- // W^X Status
12- crate::println!(" Write ⊕ Execute Enforcement: ✓ Active");
13- crate::println!(" Memory pages cannot be both writable and executable");
14- crate::println!();
00000000001516- // ASLR Status
17- crate::println!(" Address Space Layout Randomization: ✓ Active");
18- crate::println!(" Thread stacks randomized with 0-64KB entropy");
19- crate::println!();
0000002021- // Thread Stack Information
22- crate::println!(" Thread Stack Wards:");
0000002324- use crate::attunement::interrupts::without_interrupts;
25- use crate::loom_of_fate::ThreadState;
0000000000000000000002627 without_interrupts(|| {
28 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();
3334- for thread in threads {
35- let stack_size = thread.stack_top - thread.stack_bottom;
000000003637- // 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- };
4647- let state_str = match thread.state {
48- ThreadState::Weaving => "Weaving",
49- ThreadState::Resting => "Resting",
50- ThreadState::Tangled => "Tangled",
51- ThreadState::Fading => "Fading",
52- };
5354- 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- );
64 }
65 }
66 });
6768 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.");
72}
00000
···5/// - ASLR (Address Space Layout Randomization) status
6/// - Thread stack addresses and randomization entropy
7pub fn cmd_wards() {
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");
120 crate::println!();
121122+ 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());
136137+ // 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+ }
147148+ // 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());
156157+ // 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;
180181 without_interrupts(|| {
182 unsafe {
183+ let mut mana_pool = crate::mana_pool::get_mana_pool().lock();
000184185+ 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+ }
195196+ 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+ }
00203204+ // 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+ }
210211+ // Clear stored IDs
212+ STORED_CAP_ID = None;
213+ STORED_DERIVED_ID = None;
0000000214 }
215 }
216 });
217218 crate::println!();
219+ crate::println!(" Security test complete. Opaque capabilities working correctly.");
00220}
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;