···185185static mut COMMAND_HISTORY: MaybeUninit<InterruptSafeLock<CommandHistory>> = MaybeUninit::uninit();
186186static mut HISTORY_INITIALIZED: bool = false;
187187188188-/// Paging state for help menu
189189-static mut PAGING_ACTIVE: bool = false;
190190-static mut PAGING_PAGE: usize = 0;
188188+/// Paging state for multi-page commands
189189+pub(crate) static mut PAGING_ACTIVE: bool = false;
190190+pub(crate) static mut PAGING_PAGE: usize = 0;
191191+192192+#[derive(Clone, Copy)]
193193+pub enum PagingCommand {
194194+ Help,
195195+ Wards,
196196+}
197197+198198+pub(crate) static mut PAGING_COMMAND: Option<PagingCommand> = None;
191199192200/// Initialize the shell
193201pub fn init() {
···351359 buffer.clear();
352360 }
353361 PAGING_PAGE += 1;
354354- show_help_page(PAGING_PAGE);
362362+363363+ // Call the appropriate paging function
364364+ match PAGING_COMMAND {
365365+ Some(PagingCommand::Help) => show_help_page(PAGING_PAGE),
366366+ Some(PagingCommand::Wards) => crate::wards_command::show_wards_page(PAGING_PAGE),
367367+ None => {
368368+ // Safety: should never happen
369369+ PAGING_ACTIVE = false;
370370+ PAGING_PAGE = 0;
371371+ display_prompt();
372372+ }
373373+ }
355374 return;
356375 }
357376···528547 unsafe {
529548 PAGING_ACTIVE = true;
530549 PAGING_PAGE = 0;
550550+ PAGING_COMMAND = Some(PagingCommand::Help);
531551 }
532552 show_help_page(0);
533553}
···591611 unsafe {
592612 PAGING_ACTIVE = false;
593613 PAGING_PAGE = 0;
614614+ PAGING_COMMAND = None;
594615 }
595616 display_prompt();
596617 }
···599620 unsafe {
600621 PAGING_ACTIVE = false;
601622 PAGING_PAGE = 0;
623623+ PAGING_COMMAND = None;
602624 }
603625 display_prompt();
604626 }
···1081110310821104/// WARDS - Display security protections (ASLR, W^X enforcement)
10831105fn cmd_wards() {
10841084- crate::println!("◈ Security Wards of the Mana Pool");
10851085- crate::println!();
10861086- crate::println!("═══════════════════════════════════════════════════");
10871087- crate::println!();
10881088-10891089- // W^X Status
10901090- crate::println!(" ⚔ Write ⊕ Execute Enforcement");
10911091- crate::println!(" Status: ✓ ACTIVE");
10921092- crate::println!(" Policy: Memory pages cannot be both writable AND executable");
10931093- crate::println!(" Location: Enforced in Mana Pool capability validation");
10941094- crate::println!();
10951095-10961096- // ASLR Status
10971097- crate::println!(" ⚔ Address Space Layout Randomization (ASLR)");
10981098- crate::println!(" Status: ✓ ACTIVE");
10991099- crate::println!(" Entropy: 0-64KB randomization per thread stack");
11001100- crate::println!(" Source: RDTSC (fast boot-safe hardware timer)");
11011101- crate::println!(" Scope: All thread stacks randomized at creation");
11021102- crate::println!();
11031103-11041104- // Thread Stack Information
11051105- let threads = crate::loom_of_fate::get_thread_debug_info();
11061106- crate::println!(" ⚔ Protected Thread Stacks: {}", threads.len());
11071107- crate::println!();
11081108-11091109- if !threads.is_empty() {
11101110- for thread in threads {
11111111- let state_str = match thread.state {
11121112- crate::loom_of_fate::ThreadState::Weaving => "Weaving",
11131113- crate::loom_of_fate::ThreadState::Resting => "Resting",
11141114- crate::loom_of_fate::ThreadState::Tangled => "Tangled",
11151115- crate::loom_of_fate::ThreadState::Fading => "Fading",
11161116- };
11171117-11181118- let priority_str = match thread.priority {
11191119- crate::loom_of_fate::ThreadPriority::Critical => "Critical",
11201120- crate::loom_of_fate::ThreadPriority::High => "High",
11211121- crate::loom_of_fate::ThreadPriority::Normal => "Normal",
11221122- crate::loom_of_fate::ThreadPriority::Low => "Low",
11231123- crate::loom_of_fate::ThreadPriority::Idle => "Idle",
11241124- };
11251125-11261126- crate::println!(" Thread #{} [{}|{}]", thread.id, state_str, priority_str);
11271127- crate::println!(" Stack: 0x{:016x} - 0x{:016x}",
11281128- thread.stack_bottom, thread.stack_top);
11291129- crate::println!(" Size: {} KB", thread.stack_size / 1024);
11301130-11311131- // Calculate ASLR offset - the difference shows randomization
11321132- // Each thread should have a different stack address due to ASLR
11331133- let addr_entropy = (thread.stack_top as usize) & 0xFFFF;
11341134- crate::println!(" ASLR: ~{} bytes offset (varies per thread)", addr_entropy % 65536);
11351135- crate::println!();
11361136- }
11371137- }
11381138-11391139- crate::println!("═══════════════════════════════════════════════════");
11401140- crate::println!();
11411141- crate::println!(" The wards stand strong. Your sanctuary is protected.");
11061106+ // Delegate to the paged wards command with capability testing
11071107+ crate::wards_command::cmd_wards();
11421108}
+1
heartwood/src/lib.rs
···4343pub mod vga_buffer;
4444pub mod rtl; // Runtime library with memcpy, etc.
4545pub mod eldarin; // The Eldarin Shell
4646+pub mod wards_command; // Security wards command
4647pub mod irq_safe_mutex; // Interrupt-safe mutex primitive
4748pub mod vfs; // Virtual File System layer
4849pub mod drivers; // Hardware device drivers
+1-1
heartwood/src/loom_of_fate/mod.rs
···120120121121/// Execute a closure with interrupts disabled
122122/// This prevents deadlocks when acquiring locks that might be used in interrupt handlers
123123-fn without_interrupts<F, R>(f: F) -> R
123123+pub(crate) fn without_interrupts<F, R>(f: F) -> R
124124where
125125 F: FnOnce() -> R,
126126{
+6
heartwood/src/main.rs
···187187 unsafe { serial_out(b'D'); }
188188 println!(" ✓ Mana Pool ready");
189189190190+ // Initialize capability sealing (must be after Mana Pool, before capabilities are created)
191191+ unsafe { serial_out(b'S'); }
192192+ println!("◈ Forging security wards...");
193193+ unsafe { mana_pool::sealing::init(); }
194194+ println!(" ✓ Capability sealing ready (HMAC-SHA256)");
195195+190196 // Initialize the Nexus (IPC)
191197 unsafe { serial_out(b'E'); }
192198 println!("◈ Opening the Nexus...");
+197
heartwood/src/mana_pool/capability.rs
···11//! Capability-based security for memory access
2233use super::object_manager::ObjectHandle;
44+use super::sealing;
55+use core::sync::atomic::{AtomicU64, Ordering};
4657/// A capability grants specific rights to an object
68#[derive(Debug, Clone, Copy)]
···3234 /// Check if this capability can be transferred to another process
3335 pub fn can_transfer(&self) -> bool {
3436 self.rights.contains(CapabilityRights::TRANSFER)
3737+ }
3838+}
3939+4040+// ==================== SEALED CAPABILITIES ====================
4141+4242+/// Opaque capability identifier (user space only sees this)
4343+///
4444+/// This is like a file descriptor - a meaningless number without
4545+/// the kernel's capability table to look it up.
4646+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
4747+pub struct CapabilityId(u64);
4848+4949+impl CapabilityId {
5050+ /// Create a new capability ID
5151+ pub(crate) fn new(id: u64) -> Self {
5252+ Self(id)
5353+ }
5454+5555+ /// Get the raw ID value
5656+ pub fn raw(&self) -> u64 {
5757+ self.0
5858+ }
5959+}
6060+6161+/// Global counter for generating unique capability IDs
6262+static NEXT_CAPABILITY_ID: AtomicU64 = AtomicU64::new(1);
6363+6464+impl CapabilityId {
6565+ /// Generate a new unique capability ID
6666+ pub(crate) fn generate() -> Self {
6767+ let id = NEXT_CAPABILITY_ID.fetch_add(1, Ordering::SeqCst);
6868+ Self(id)
6969+ }
7070+}
7171+7272+/// Sealed capability with cryptographic authentication
7373+///
7474+/// This is the kernel-internal structure that combines:
7575+/// - Opaque ID (what user space sees)
7676+/// - Actual rights and object handle (what kernel uses)
7777+/// - Cryptographic seal (prevents forgery and tampering)
7878+/// - Generation counter (for revocation)
7979+///
8080+/// User space can only see CapabilityId. The SealedCapability
8181+/// lives in kernel-only capability tables.
8282+#[derive(Debug, Clone)]
8383+pub struct SealedCapability {
8484+ /// Public ID (user space sees this)
8585+ pub id: CapabilityId,
8686+8787+ /// Actual rights (user space NEVER sees this directly)
8888+ pub rights: CapabilityRights,
8989+9090+ /// Object this capability grants access to
9191+ pub handle: ObjectHandle,
9292+9393+ /// Cryptographic seal (HMAC-SHA256 of id + rights + handle)
9494+ /// Validates integrity and authenticity
9595+ seal: [u8; 32],
9696+9797+ /// Generation counter for revocation
9898+ /// Incrementing this invalidates all copies of this capability
9999+ pub generation: u64,
100100+}
101101+102102+impl SealedCapability {
103103+ /// Create a new sealed capability
104104+ ///
105105+ /// # Arguments
106106+ /// * `handle` - Object handle this capability grants access to
107107+ /// * `rights` - Permission rights
108108+ ///
109109+ /// # Security
110110+ /// The seal is computed using kernel-only secret key via HMAC-SHA256.
111111+ /// This makes the capability unforgeable without the secret key.
112112+ pub fn new(handle: ObjectHandle, rights: CapabilityRights) -> Self {
113113+ // Validate W^X before creating capability
114114+ debug_assert!(rights.validate_wx(), "W^X violation in capability creation!");
115115+116116+ let id = CapabilityId::generate();
117117+ let generation = 0;
118118+119119+ // Compute cryptographic seal
120120+ let seal = Self::compute_seal(id, rights, handle, generation);
121121+122122+ Self {
123123+ id,
124124+ rights,
125125+ handle,
126126+ seal,
127127+ generation,
128128+ }
129129+ }
130130+131131+ /// Compute the cryptographic seal for this capability
132132+ ///
133133+ /// Seal = HMAC-SHA256(key, id || rights || handle || generation)
134134+ ///
135135+ /// The seal binds together all fields. Modifying any field
136136+ /// (e.g., escalating rights) will break the seal.
137137+ fn compute_seal(
138138+ id: CapabilityId,
139139+ rights: CapabilityRights,
140140+ handle: ObjectHandle,
141141+ generation: u64,
142142+ ) -> [u8; 32] {
143143+ // Serialize capability data for HMAC
144144+ let mut data = [0u8; 32];
145145+146146+ // Pack all fields into data buffer
147147+ data[0..8].copy_from_slice(&id.0.to_le_bytes());
148148+ data[8..12].copy_from_slice(&rights.bits().to_le_bytes());
149149+ data[12..20].copy_from_slice(&handle.0.to_le_bytes());
150150+ data[20..28].copy_from_slice(&generation.to_le_bytes());
151151+152152+ // Compute HMAC-SHA256
153153+ unsafe {
154154+ sealing::get_sealer().seal(&data)
155155+ }
156156+ }
157157+158158+ /// Validate the cryptographic seal
159159+ ///
160160+ /// # Returns
161161+ /// `true` if seal is valid, `false` if capability has been tampered with
162162+ ///
163163+ /// # Security
164164+ /// Uses constant-time comparison to prevent timing attacks.
165165+ pub fn validate(&self) -> bool {
166166+ let expected_seal = Self::compute_seal(
167167+ self.id,
168168+ self.rights,
169169+ self.handle,
170170+ self.generation,
171171+ );
172172+173173+ unsafe {
174174+ sealing::get_sealer().verify(&self.serialize_for_seal(), &expected_seal)
175175+ }
176176+ }
177177+178178+ /// Serialize capability data for sealing
179179+ fn serialize_for_seal(&self) -> [u8; 32] {
180180+ let mut data = [0u8; 32];
181181+ data[0..8].copy_from_slice(&self.id.0.to_le_bytes());
182182+ data[8..12].copy_from_slice(&self.rights.bits().to_le_bytes());
183183+ data[12..20].copy_from_slice(&self.handle.0.to_le_bytes());
184184+ data[20..28].copy_from_slice(&self.generation.to_le_bytes());
185185+ data
186186+ }
187187+188188+ /// Derive a new capability with reduced rights (attenuation)
189189+ ///
190190+ /// # Arguments
191191+ /// * `new_rights` - The reduced rights for the new capability
192192+ ///
193193+ /// # Security
194194+ /// Can only reduce rights, never increase them. If new_rights
195195+ /// contains any right not in the parent, this panics.
196196+ ///
197197+ /// # Panics
198198+ /// Panics if attempting to amplify rights
199199+ pub fn derive(&self, new_rights: CapabilityRights) -> Self {
200200+ // SECURITY: Can only attenuate (reduce rights), never amplify
201201+ assert!(
202202+ self.rights.contains(new_rights),
203203+ "Cannot amplify rights! Parent: {:?}, Child: {:?}",
204204+ self.rights,
205205+ new_rights
206206+ );
207207+208208+ // Validate W^X on derived capability
209209+ assert!(
210210+ new_rights.validate_wx(),
211211+ "Derived capability violates W^X!"
212212+ );
213213+214214+ let id = CapabilityId::generate();
215215+ let seal = Self::compute_seal(id, new_rights, self.handle, self.generation);
216216+217217+ Self {
218218+ id,
219219+ rights: new_rights,
220220+ handle: self.handle,
221221+ seal,
222222+ generation: self.generation,
223223+ }
224224+ }
225225+226226+ /// Convert to legacy Capability (for compatibility)
227227+ pub fn to_capability(&self) -> Capability {
228228+ Capability {
229229+ handle: self.handle,
230230+ rights: self.rights,
231231+ }
35232 }
36233}
37234
+334
heartwood/src/mana_pool/capability_table.rs
···11+//! Capability Table - Opaque handle mapping
22+//!
33+//! The capability table enforces the "opaque handle" security model:
44+//! - User space only sees `CapabilityId` numbers (meaningless without the table)
55+//! - Kernel maintains the mapping from ID → `SealedCapability`
66+//! - All capability operations require table lookup + seal validation
77+//!
88+//! This prevents forgery because:
99+//! 1. User can't create valid CapabilityId that exists in table
1010+//! 2. User can't access table directly (kernel-only memory)
1111+//! 3. Even if they guess an ID, seal validation catches tampering
1212+1313+use super::capability::{CapabilityId, SealedCapability, CapabilityRights};
1414+use super::object_manager::ObjectHandle;
1515+use alloc::collections::BTreeMap;
1616+1717+/// Errors that can occur during capability table operations
1818+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1919+pub enum CapabilityError {
2020+ /// Capability ID not found in table
2121+ InvalidId,
2222+ /// Capability seal validation failed (tampering detected)
2323+ SealBroken,
2424+ /// Attempted operation requires rights not granted by capability
2525+ PermissionDenied,
2626+ /// Capability table is full
2727+ TableFull,
2828+}
2929+3030+/// Maximum number of capabilities per table
3131+/// This prevents DoS attacks via capability exhaustion
3232+const MAX_CAPABILITIES: usize = 4096;
3333+3434+/// Capability table - maps opaque IDs to sealed capabilities
3535+///
3636+/// # Security Model
3737+/// - Table lives in kernel-only memory (user space never sees it)
3838+/// - User space only receives `CapabilityId` values
3939+/// - All operations require lookup by ID + seal validation
4040+/// - Invalid IDs or broken seals are rejected
4141+///
4242+/// # Usage
4343+/// ```
4444+/// // Create and insert capability
4545+/// let cap = SealedCapability::new(handle, rights);
4646+/// let cap_id = table.insert(cap)?;
4747+///
4848+/// // Lookup and validate
4949+/// let cap = table.get(cap_id)?; // Validates seal automatically
5050+/// if cap.rights.contains(READ) {
5151+/// // Perform read operation
5252+/// }
5353+/// ```
5454+pub struct CapabilityTable {
5555+ /// Mapping from opaque ID → sealed capability
5656+ /// Using BTreeMap instead of HashMap for deterministic iteration
5757+ table: BTreeMap<CapabilityId, SealedCapability>,
5858+}
5959+6060+impl CapabilityTable {
6161+ /// Create a new empty capability table
6262+ pub const fn new() -> Self {
6363+ Self {
6464+ table: BTreeMap::new(),
6565+ }
6666+ }
6767+6868+ /// Insert a sealed capability into the table
6969+ ///
7070+ /// # Arguments
7171+ /// * `cap` - The sealed capability to insert
7272+ ///
7373+ /// # Returns
7474+ /// * `Ok(CapabilityId)` - The opaque ID for this capability
7575+ /// * `Err(TableFull)` - If table has reached maximum capacity
7676+ ///
7777+ /// # Security
7878+ /// The returned CapabilityId is the ONLY handle user space gets.
7979+ /// Without access to this table, the ID is meaningless.
8080+ pub fn insert(&mut self, cap: SealedCapability) -> Result<CapabilityId, CapabilityError> {
8181+ if self.table.len() >= MAX_CAPABILITIES {
8282+ return Err(CapabilityError::TableFull);
8383+ }
8484+8585+ let id = cap.id;
8686+ self.table.insert(id, cap);
8787+ Ok(id)
8888+ }
8989+9090+ /// Lookup capability by ID and validate seal
9191+ ///
9292+ /// # Arguments
9393+ /// * `id` - The opaque capability ID to lookup
9494+ ///
9595+ /// # Returns
9696+ /// * `Ok(&SealedCapability)` - Valid capability with intact seal
9797+ /// * `Err(InvalidId)` - ID not found in table
9898+ /// * `Err(SealBroken)` - Seal validation failed (tampering detected)
9999+ ///
100100+ /// # Security
101101+ /// This is the PRIMARY security boundary. Every capability use
102102+ /// must go through this function, which:
103103+ /// 1. Verifies the ID exists in the table (prevents forgery)
104104+ /// 2. Validates the cryptographic seal (prevents tampering)
105105+ pub fn get(&self, id: CapabilityId) -> Result<&SealedCapability, CapabilityError> {
106106+ // 1. Lookup capability by ID
107107+ let cap = self.table.get(&id)
108108+ .ok_or(CapabilityError::InvalidId)?;
109109+110110+ // 2. SECURITY: Validate cryptographic seal
111111+ if !cap.validate() {
112112+ return Err(CapabilityError::SealBroken);
113113+ }
114114+115115+ Ok(cap)
116116+ }
117117+118118+ /// Lookup capability with mutable access (for revocation)
119119+ ///
120120+ /// # Security
121121+ /// Mutable access is restricted to kernel operations like revocation.
122122+ /// Still validates seal before returning.
123123+ pub fn get_mut(&mut self, id: CapabilityId) -> Result<&mut SealedCapability, CapabilityError> {
124124+ let cap = self.table.get_mut(&id)
125125+ .ok_or(CapabilityError::InvalidId)?;
126126+127127+ if !cap.validate() {
128128+ return Err(CapabilityError::SealBroken);
129129+ }
130130+131131+ Ok(cap)
132132+ }
133133+134134+ /// Check if capability grants specific rights
135135+ ///
136136+ /// # Security
137137+ /// Validates seal before checking rights. This is a convenience
138138+ /// function that combines lookup + rights check.
139139+ pub fn check_rights(&self, id: CapabilityId, required: CapabilityRights) -> Result<(), CapabilityError> {
140140+ let cap = self.get(id)?;
141141+142142+ if cap.rights.contains(required) {
143143+ Ok(())
144144+ } else {
145145+ Err(CapabilityError::PermissionDenied)
146146+ }
147147+ }
148148+149149+ /// Remove capability from table (revocation)
150150+ ///
151151+ /// # Returns
152152+ /// The removed capability, if it existed
153153+ pub fn remove(&mut self, id: CapabilityId) -> Option<SealedCapability> {
154154+ self.table.remove(&id)
155155+ }
156156+157157+ /// Derive a new capability with reduced rights
158158+ ///
159159+ /// Creates a new sealed capability with attenuated rights and
160160+ /// inserts it into the table, returning the new ID.
161161+ ///
162162+ /// # Arguments
163163+ /// * `parent_id` - ID of parent capability
164164+ /// * `new_rights` - Reduced rights for child capability
165165+ ///
166166+ /// # Security
167167+ /// Can only reduce rights, never amplify. The parent capability's
168168+ /// seal is validated before derivation.
169169+ ///
170170+ /// # Panics
171171+ /// Panics if attempting to amplify rights or violate W^X
172172+ pub fn derive(&mut self, parent_id: CapabilityId, new_rights: CapabilityRights) -> Result<CapabilityId, CapabilityError> {
173173+ // Lookup and validate parent
174174+ let parent = self.get(parent_id)?;
175175+176176+ // Derive child capability (validates rights attenuation)
177177+ let child = parent.derive(new_rights);
178178+179179+ // Insert child into table
180180+ self.insert(child)
181181+ }
182182+183183+ /// Get the object handle for a capability (after validation)
184184+ ///
185185+ /// # Security
186186+ /// Validates seal and rights before returning the handle.
187187+ pub fn get_handle(&self, id: CapabilityId, required_rights: CapabilityRights) -> Result<ObjectHandle, CapabilityError> {
188188+ let cap = self.get(id)?;
189189+190190+ if !cap.rights.contains(required_rights) {
191191+ return Err(CapabilityError::PermissionDenied);
192192+ }
193193+194194+ Ok(cap.handle)
195195+ }
196196+197197+ /// Get number of capabilities in table
198198+ pub fn len(&self) -> usize {
199199+ self.table.len()
200200+ }
201201+202202+ /// Check if table is empty
203203+ pub fn is_empty(&self) -> bool {
204204+ self.table.is_empty()
205205+ }
206206+207207+ /// Clear all capabilities from table
208208+ ///
209209+ /// # Security
210210+ /// Use with caution - this revokes ALL capabilities in the table.
211211+ pub fn clear(&mut self) {
212212+ self.table.clear();
213213+ }
214214+}
215215+216216+impl Default for CapabilityTable {
217217+ fn default() -> Self {
218218+ Self::new()
219219+ }
220220+}
221221+222222+#[cfg(test)]
223223+mod tests {
224224+ use super::*;
225225+226226+ #[test]
227227+ fn test_insert_and_lookup() {
228228+ let mut table = CapabilityTable::new();
229229+ let handle = ObjectHandle(0x1000);
230230+ let rights = CapabilityRights::READ;
231231+232232+ let cap = SealedCapability::new(handle, rights);
233233+ let cap_id = table.insert(cap).unwrap();
234234+235235+ // Should be able to retrieve
236236+ let retrieved = table.get(cap_id).unwrap();
237237+ assert_eq!(retrieved.handle, handle);
238238+ assert_eq!(retrieved.rights, rights);
239239+ }
240240+241241+ #[test]
242242+ fn test_invalid_id() {
243243+ let table = CapabilityTable::new();
244244+ let invalid_id = CapabilityId::new(9999);
245245+246246+ // Should fail - ID not in table
247247+ assert_eq!(table.get(invalid_id), Err(CapabilityError::InvalidId));
248248+ }
249249+250250+ #[test]
251251+ fn test_rights_check() {
252252+ let mut table = CapabilityTable::new();
253253+ let handle = ObjectHandle(0x1000);
254254+ let rights = CapabilityRights::READ;
255255+256256+ let cap = SealedCapability::new(handle, rights);
257257+ let cap_id = table.insert(cap).unwrap();
258258+259259+ // Should succeed - has READ
260260+ assert!(table.check_rights(cap_id, CapabilityRights::READ).is_ok());
261261+262262+ // Should fail - doesn't have WRITE
263263+ assert_eq!(
264264+ table.check_rights(cap_id, CapabilityRights::WRITE),
265265+ Err(CapabilityError::PermissionDenied)
266266+ );
267267+ }
268268+269269+ #[test]
270270+ fn test_derive_attenuation() {
271271+ let mut table = CapabilityTable::new();
272272+ let handle = ObjectHandle(0x1000);
273273+ let parent_rights = CapabilityRights::READ | CapabilityRights::WRITE;
274274+275275+ let parent = SealedCapability::new(handle, parent_rights);
276276+ let parent_id = table.insert(parent).unwrap();
277277+278278+ // Derive read-only capability
279279+ let child_id = table.derive(parent_id, CapabilityRights::READ).unwrap();
280280+281281+ // Child should have reduced rights
282282+ let child = table.get(child_id).unwrap();
283283+ assert!(child.rights.contains(CapabilityRights::READ));
284284+ assert!(!child.rights.contains(CapabilityRights::WRITE));
285285+ }
286286+287287+ #[test]
288288+ #[should_panic(expected = "Cannot amplify rights")]
289289+ fn test_derive_amplification_panics() {
290290+ let mut table = CapabilityTable::new();
291291+ let handle = ObjectHandle(0x1000);
292292+ let parent_rights = CapabilityRights::READ;
293293+294294+ let parent = SealedCapability::new(handle, parent_rights);
295295+ let parent_id = table.insert(parent).unwrap();
296296+297297+ // Try to derive with MORE rights (should panic)
298298+ let _ = table.derive(parent_id, CapabilityRights::READ | CapabilityRights::WRITE);
299299+ }
300300+301301+ #[test]
302302+ fn test_revocation() {
303303+ let mut table = CapabilityTable::new();
304304+ let handle = ObjectHandle(0x1000);
305305+ let cap = SealedCapability::new(handle, CapabilityRights::READ);
306306+ let cap_id = table.insert(cap).unwrap();
307307+308308+ // Should exist
309309+ assert!(table.get(cap_id).is_ok());
310310+311311+ // Revoke
312312+ let removed = table.remove(cap_id);
313313+ assert!(removed.is_some());
314314+315315+ // Should no longer exist
316316+ assert_eq!(table.get(cap_id), Err(CapabilityError::InvalidId));
317317+ }
318318+319319+ #[test]
320320+ fn test_table_full() {
321321+ let mut table = CapabilityTable::new();
322322+323323+ // Fill table to capacity
324324+ for i in 0..MAX_CAPABILITIES {
325325+ let handle = ObjectHandle(i as u64);
326326+ let cap = SealedCapability::new(handle, CapabilityRights::READ);
327327+ assert!(table.insert(cap).is_ok());
328328+ }
329329+330330+ // Next insert should fail
331331+ let cap = SealedCapability::new(ObjectHandle(0xFFFF), CapabilityRights::READ);
332332+ assert_eq!(table.insert(cap), Err(CapabilityError::TableFull));
333333+ }
334334+}
+7
heartwood/src/mana_pool/entropy.rs
···7474 Self::from_seed(seed)
7575 }
76767777+ /// Create ChaCha8 RNG using fast RDTSC-only seeding (boot-safe)
7878+ /// Use this during early boot when RDRAND might not be available
7979+ pub fn from_hardware_fast() -> Self {
8080+ let seed = HardwareRng::fast_u64();
8181+ Self::from_seed(seed)
8282+ }
8383+7784 /// Create a new ChaCha8 RNG from a 64-bit seed
7885 pub fn from_seed(seed: u64) -> Self {
7986 let mut state = [0u32; 16];
+10-3
heartwood/src/mana_pool/mod.rs
···17171818pub mod object_manager;
1919pub mod capability;
2020+pub mod capability_table; // Opaque capability handle mapping
2021pub mod sanctuary;
2122pub mod ephemeral_mist;
2223pub mod allocator;
···2425pub mod buddy;
2526pub mod entropy; // Random number generation for ASLR
2627pub mod aslr; // Address Space Layout Randomization
2828+pub mod sealing; // Cryptographic capability sealing
27292830pub use object_manager::{ObjectManager, ObjectHandle, ObjectType, ObjectInfo};
2929-pub use capability::{Capability, CapabilityRights};
3131+pub use capability::{Capability, CapabilityRights, CapabilityId, SealedCapability};
3232+pub use capability_table::{CapabilityTable, CapabilityError};
3033pub use sanctuary::Sanctuary;
3134pub use ephemeral_mist::EphemeralMist;
3235pub use interrupt_lock::InterruptSafeLock;
···4043static mut MANA_POOL_INITIALIZED: bool = false;
41444245pub struct ManaPool {
4343- object_manager: ObjectManager,
4646+ pub(crate) object_manager: ObjectManager,
4447 sanctuary: Sanctuary,
4548 ephemeral_mist: EphemeralMist,
4649}
···185188}
186189187190/// Get reference to MANA_POOL (assumes initialized)
188188-unsafe fn get_mana_pool() -> &'static InterruptSafeLock<Box<ManaPool>> {
191191+pub(crate) unsafe fn get_mana_pool() -> &'static InterruptSafeLock<Box<ManaPool>> {
189192 &*core::ptr::addr_of!(MANA_POOL).cast::<InterruptSafeLock<Box<ManaPool>>>()
190193}
191194···272275 /// SECURITY: Attempting to create a capability with both WRITE and EXECUTE rights
273276 /// This violates the W^X (Write XOR Execute) security policy
274277 SecurityViolation,
278278+ /// SECURITY: Permission denied - capability doesn't grant required rights
279279+ PermissionDenied,
280280+ /// Capability table is full (DOS protection)
281281+ CapabilityTableFull,
275282}
+137-1
heartwood/src/mana_pool/object_manager.rs
···11//! Object Manager - Manages memory as abstract objects
2233-use super::capability::{Capability, CapabilityRights};
33+use super::capability::{Capability, CapabilityRights, CapabilityId, SealedCapability};
44+use super::capability_table::{CapabilityTable, CapabilityError};
45use super::{AllocationPurpose, ManaError};
56use alloc::collections::BTreeMap;
67···3435pub struct ObjectManager {
3536 objects: BTreeMap<ObjectHandle, Object>,
3637 next_handle: u64,
3838+ /// Capability table - maps opaque IDs to sealed capabilities
3939+ /// This enforces the opaque handle security model
4040+ capability_table: CapabilityTable,
3741}
38423943impl Default for ObjectManager {
···4751 Self {
4852 objects: BTreeMap::new(),
4953 next_handle: 1, // 0 is reserved as invalid
5454+ capability_table: CapabilityTable::new(),
5055 }
5156 }
5257···200205 purpose: object.purpose,
201206 ref_count: object.ref_count,
202207 })
208208+ }
209209+210210+ // ==================== SEALED CAPABILITY METHODS ====================
211211+212212+ /// Create object and return OPAQUE capability ID (not the capability itself!)
213213+ ///
214214+ /// This is the secure API that enforces opaque handles:
215215+ /// - Creates object
216216+ /// - Creates sealed capability with cryptographic seal
217217+ /// - Stores capability in table
218218+ /// - Returns ONLY the opaque ID to caller
219219+ ///
220220+ /// # Security
221221+ /// Caller never sees the actual capability, only the meaningless ID.
222222+ /// Without access to the capability table, the ID is useless.
223223+ pub fn create_object_sealed(
224224+ &mut self,
225225+ address: usize,
226226+ size: usize,
227227+ purpose: AllocationPurpose,
228228+ rights: CapabilityRights,
229229+ ) -> Result<CapabilityId, ManaError> {
230230+ // Create the object
231231+ let handle = ObjectHandle(self.next_handle);
232232+ self.next_handle += 1;
233233+234234+ let object = Object {
235235+ handle,
236236+ object_type: ObjectType::Memory,
237237+ address,
238238+ size,
239239+ purpose,
240240+ ref_count: 1,
241241+ };
242242+243243+ self.objects.insert(handle, object);
244244+245245+ // Create sealed capability
246246+ let sealed_cap = SealedCapability::new(handle, rights);
247247+248248+ // Store in capability table
249249+ let cap_id = self.capability_table.insert(sealed_cap)
250250+ .map_err(|_| ManaError::CapabilityTableFull)?;
251251+252252+ // Return only the opaque ID!
253253+ Ok(cap_id)
254254+ }
255255+256256+ /// Get object info using capability ID (validates seal)
257257+ ///
258258+ /// # Security
259259+ /// - Looks up capability by ID (prevents forgery)
260260+ /// - Validates cryptographic seal (prevents tampering)
261261+ /// - Checks rights before allowing access
262262+ pub fn get_object_info_sealed(&self, cap_id: CapabilityId) -> Result<ObjectInfo, ManaError> {
263263+ // Lookup and validate capability
264264+ let cap = self.capability_table.get(cap_id)
265265+ .map_err(|e| match e {
266266+ CapabilityError::InvalidId => ManaError::InvalidCapability,
267267+ CapabilityError::SealBroken => ManaError::SecurityViolation,
268268+ _ => ManaError::InvalidCapability,
269269+ })?;
270270+271271+ // Get object
272272+ let object = self.objects.get(&cap.handle)
273273+ .ok_or(ManaError::InvalidHandle)?;
274274+275275+ Ok(ObjectInfo {
276276+ object_type: object.object_type,
277277+ size: object.size,
278278+ purpose: object.purpose,
279279+ ref_count: object.ref_count,
280280+ })
281281+ }
282282+283283+ /// Release object using capability ID
284284+ pub fn release_object_sealed(&mut self, cap_id: CapabilityId) -> Result<(), ManaError> {
285285+ // Lookup and validate capability
286286+ let cap = self.capability_table.get(cap_id)
287287+ .map_err(|e| match e {
288288+ CapabilityError::InvalidId => ManaError::InvalidCapability,
289289+ CapabilityError::SealBroken => ManaError::SecurityViolation,
290290+ _ => ManaError::InvalidCapability,
291291+ })?;
292292+293293+ let handle = cap.handle;
294294+295295+ // Remove from capability table (revoke access)
296296+ self.capability_table.remove(cap_id);
297297+298298+ // Decrement ref count
299299+ let object = self.objects.get_mut(&handle)
300300+ .ok_or(ManaError::InvalidHandle)?;
301301+302302+ if object.ref_count == 0 {
303303+ return Err(ManaError::AlreadyReleased);
304304+ }
305305+306306+ object.ref_count -= 1;
307307+308308+ if object.ref_count == 0 {
309309+ // Free the object
310310+ self.objects.remove(&handle);
311311+ }
312312+313313+ Ok(())
314314+ }
315315+316316+ /// Derive a new sealed capability with reduced rights
317317+ ///
318318+ /// # Security
319319+ /// Can only reduce rights (attenuation), never amplify.
320320+ pub fn derive_capability_sealed(&mut self, parent_id: CapabilityId, new_rights: CapabilityRights) -> Result<CapabilityId, ManaError> {
321321+ self.capability_table.derive(parent_id, new_rights)
322322+ .map_err(|e| match e {
323323+ CapabilityError::InvalidId => ManaError::InvalidCapability,
324324+ CapabilityError::SealBroken => ManaError::SecurityViolation,
325325+ CapabilityError::TableFull => ManaError::CapabilityTableFull,
326326+ _ => ManaError::InvalidCapability,
327327+ })
328328+ }
329329+330330+ /// Check if capability ID grants specific rights
331331+ pub fn check_capability_rights(&self, cap_id: CapabilityId, required: CapabilityRights) -> Result<(), ManaError> {
332332+ self.capability_table.check_rights(cap_id, required)
333333+ .map_err(|e| match e {
334334+ CapabilityError::InvalidId => ManaError::InvalidCapability,
335335+ CapabilityError::SealBroken => ManaError::SecurityViolation,
336336+ CapabilityError::PermissionDenied => ManaError::PermissionDenied,
337337+ _ => ManaError::InvalidCapability,
338338+ })
203339 }
204340}
205341
+214
heartwood/src/mana_pool/sealing.rs
···11+///! Capability Sealing - Cryptographic protection against forgery
22+///!
33+///! This module implements unforgeable capability seals using HMAC-SHA256.
44+///! Each capability is sealed with a kernel-only secret key, preventing
55+///! user space from forging or tampering with capabilities.
66+///!
77+///! # Security Properties
88+///! - **Unforgeable**: Can't create valid seal without kernel secret key
99+///! - **Tamper-Proof**: Modifying capability data invalidates seal
1010+///! - **Constant-Time**: Verification uses constant-time comparison
1111+///! - **Kernel-Only**: Secret key never leaves kernel space
1212+1313+use core::mem::MaybeUninit;
1414+use crate::mana_pool::entropy::{HardwareRng, ChaCha8Rng};
1515+1616+/// Capability sealer using HMAC-SHA256
1717+pub struct CapabilitySealer {
1818+ /// Secret 256-bit key for HMAC (kernel-only, never exposed)
1919+ seal_key: [u8; 32],
2020+}
2121+2222+impl CapabilitySealer {
2323+ /// Create a new sealer with cryptographically random key
2424+ ///
2525+ /// # Security
2626+ /// The seal key is generated using hardware entropy (RDRAND/RDTSC)
2727+ /// and mixed with ChaCha8 for additional randomness. This key must
2828+ /// never be exposed to user space.
2929+ ///
3030+ /// # Safety
3131+ /// Must be called only once during kernel initialization
3232+ pub fn new() -> Self {
3333+ let seal_key = Self::generate_seal_key();
3434+ Self { seal_key }
3535+ }
3636+3737+ /// Generate a cryptographically secure seal key
3838+ ///
3939+ /// Uses hardware entropy sources (RDTSC for boot-safety)
4040+ /// mixed with ChaCha8 to generate 256 bits of entropy.
4141+ fn generate_seal_key() -> [u8; 32] {
4242+ let mut key = [0u8; 32];
4343+4444+ // Use ChaCha8 seeded from fast hardware entropy (RDTSC-only for boot safety)
4545+ let mut rng = ChaCha8Rng::from_hardware_fast();
4646+4747+ // Generate 32 random bytes
4848+ for i in 0..8 {
4949+ let random = rng.next_u32();
5050+ let bytes = random.to_le_bytes();
5151+ key[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
5252+ }
5353+5454+ key
5555+ }
5656+5757+ /// Compute HMAC-SHA256 seal for capability data
5858+ ///
5959+ /// # Arguments
6060+ /// * `data` - Serialized capability data to seal
6161+ ///
6262+ /// # Returns
6363+ /// 256-bit HMAC-SHA256 tag
6464+ pub fn seal(&self, data: &[u8]) -> [u8; 32] {
6565+ hmac_sha256::HMAC::mac(data, &self.seal_key)
6666+ }
6767+6868+ /// Verify capability seal (constant-time)
6969+ ///
7070+ /// # Arguments
7171+ /// * `data` - Capability data to verify
7272+ /// * `seal` - Seal tag to check
7373+ ///
7474+ /// # Returns
7575+ /// `true` if seal is valid, `false` otherwise
7676+ ///
7777+ /// # Security
7878+ /// Uses constant-time comparison to prevent timing attacks.
7979+ /// An attacker cannot learn information about the correct seal
8080+ /// by measuring verification time.
8181+ pub fn verify(&self, data: &[u8], seal: &[u8; 32]) -> bool {
8282+ let expected_seal = self.seal(data);
8383+ constant_time_compare(&expected_seal, seal)
8484+ }
8585+}
8686+8787+/// Constant-time equality comparison
8888+///
8989+/// Compares two byte slices in constant time to prevent timing attacks.
9090+/// Always compares all bytes regardless of when a mismatch is found.
9191+///
9292+/// # Security
9393+/// This function takes the same amount of time whether the inputs match
9494+/// or differ in the first byte vs last byte, preventing attackers from
9595+/// learning information about the correct value through timing analysis.
9696+fn constant_time_compare(a: &[u8; 32], b: &[u8; 32]) -> bool {
9797+ let mut diff: u8 = 0;
9898+ for i in 0..32 {
9999+ diff |= a[i] ^ b[i];
100100+ }
101101+ diff == 0
102102+}
103103+104104+/// Global capability sealer (initialized at boot)
105105+static mut SEALER: MaybeUninit<CapabilitySealer> = MaybeUninit::uninit();
106106+static mut SEALER_INITIALIZED: bool = false;
107107+108108+/// Initialize the global capability sealer
109109+///
110110+/// # Safety
111111+/// Must be called exactly once during kernel initialization,
112112+/// before any capabilities are created.
113113+pub unsafe fn init() {
114114+ if !SEALER_INITIALIZED {
115115+ let sealer = CapabilitySealer::new();
116116+ core::ptr::write(core::ptr::addr_of_mut!(SEALER).cast(), sealer);
117117+ SEALER_INITIALIZED = true;
118118+ // TODO: Add logging when serial_println! is available
119119+ }
120120+}
121121+122122+/// Get reference to global sealer
123123+///
124124+/// # Safety
125125+/// Caller must ensure `init()` has been called
126126+pub unsafe fn get_sealer() -> &'static CapabilitySealer {
127127+ debug_assert!(SEALER_INITIALIZED, "Sealer not initialized!");
128128+ &*core::ptr::addr_of!(SEALER).cast::<CapabilitySealer>()
129129+}
130130+131131+#[cfg(test)]
132132+mod tests {
133133+ use super::*;
134134+135135+ #[test]
136136+ fn test_seal_generation() {
137137+ let sealer = CapabilitySealer::new();
138138+ let data = b"test capability data";
139139+140140+ let seal = sealer.seal(data);
141141+142142+ // Seal should be deterministic for same data
143143+ let seal2 = sealer.seal(data);
144144+ assert_eq!(seal, seal2);
145145+ }
146146+147147+ #[test]
148148+ fn test_seal_verification() {
149149+ let sealer = CapabilitySealer::new();
150150+ let data = b"test capability data";
151151+152152+ let seal = sealer.seal(data);
153153+154154+ // Valid seal should verify
155155+ assert!(sealer.verify(data, &seal));
156156+ }
157157+158158+ #[test]
159159+ fn test_tampered_data_fails() {
160160+ let sealer = CapabilitySealer::new();
161161+ let data = b"test capability data";
162162+163163+ let seal = sealer.seal(data);
164164+165165+ // Modified data should fail
166166+ let tampered = b"test capability DATA"; // Changed case
167167+ assert!(!sealer.verify(tampered, &seal));
168168+ }
169169+170170+ #[test]
171171+ fn test_tampered_seal_fails() {
172172+ let sealer = CapabilitySealer::new();
173173+ let data = b"test capability data";
174174+175175+ let seal = sealer.seal(data);
176176+177177+ // Flip one bit in seal
178178+ let mut bad_seal = seal;
179179+ bad_seal[0] ^= 1;
180180+181181+ assert!(!sealer.verify(data, &bad_seal));
182182+ }
183183+184184+ #[test]
185185+ fn test_different_keys_different_seals() {
186186+ let sealer1 = CapabilitySealer::new();
187187+ let sealer2 = CapabilitySealer::new();
188188+ let data = b"test capability data";
189189+190190+ let seal1 = sealer1.seal(data);
191191+ let seal2 = sealer2.seal(data);
192192+193193+ // Different keys should produce different seals
194194+ // (With overwhelming probability)
195195+ assert_ne!(seal1, seal2);
196196+ }
197197+198198+ #[test]
199199+ fn test_constant_time_compare() {
200200+ let a = [0u8; 32];
201201+ let b = [0u8; 32];
202202+ let mut c = [0u8; 32];
203203+ c[0] = 1; // Differ in first byte
204204+ let mut d = [0u8; 32];
205205+ d[31] = 1; // Differ in last byte
206206+207207+ assert!(constant_time_compare(&a, &b));
208208+ assert!(!constant_time_compare(&a, &c));
209209+ assert!(!constant_time_compare(&a, &d));
210210+211211+ // All comparisons should take same time (not verifiable in test,
212212+ // but at least check correctness)
213213+ }
214214+}
+200-47
heartwood/src/wards_command.rs
···55/// - ASLR (Address Space Layout Randomization) status
66/// - Thread stack addresses and randomization entropy
77pub fn cmd_wards() {
88- crate::println!("◈ Security Wards of the Mana Pool");
88+ unsafe {
99+ crate::eldarin::PAGING_ACTIVE = true;
1010+ crate::eldarin::PAGING_PAGE = 0;
1111+ crate::eldarin::PAGING_COMMAND = Some(crate::eldarin::PagingCommand::Wards);
1212+ }
1313+ show_wards_page(0);
1414+}
1515+1616+/// Show a specific page of the wards output (called by paging system)
1717+pub fn show_wards_page(page: usize) {
1818+ use crate::loom_of_fate::{ThreadState, without_interrupts};
1919+ use crate::eldarin::display_prompt;
2020+2121+ match page {
2222+ 0 => {
2323+ // Page 1: W^X, ASLR, Thread Stacks
2424+ crate::println!("◈ Security Wards of the Mana Pool");
2525+ crate::println!();
2626+2727+ // W^X Status
2828+ crate::println!(" Write ⊕ Execute Enforcement: ✓ Active");
2929+ crate::println!(" Memory pages cannot be both writable and executable");
3030+ crate::println!();
3131+3232+ // ASLR Status
3333+ crate::println!(" Address Space Layout Randomization: ✓ Active");
3434+ crate::println!(" Thread stacks randomized with 0-64KB entropy");
3535+ crate::println!();
3636+3737+ // Thread Stack Information
3838+ crate::println!(" Thread Stack Wards:");
3939+4040+ without_interrupts(|| {
4141+ unsafe {
4242+ let loom = crate::loom_of_fate::get_loom().lock();
4343+ let threads: alloc::vec::Vec<_> = loom.threads.iter()
4444+ .filter(|t| !matches!(t.state, ThreadState::Fading))
4545+ .collect();
4646+4747+ for thread in threads {
4848+ let stack_size = thread.stack_top - thread.stack_bottom;
4949+ let nominal_top = thread.stack_bottom + stack_size;
5050+ let aslr_offset = if thread.stack_top < nominal_top {
5151+ nominal_top - thread.stack_top
5252+ } else {
5353+ 0
5454+ };
5555+5656+ let state_str = match thread.state {
5757+ ThreadState::Weaving => "Weaving",
5858+ ThreadState::Resting => "Resting",
5959+ ThreadState::Tangled => "Tangled",
6060+ ThreadState::Fading => "Fading",
6161+ };
6262+6363+ crate::println!(" Thread #{} ({}): Stack 0x{:016x}-0x{:016x}",
6464+ thread.id.0,
6565+ state_str,
6666+ thread.stack_bottom,
6767+ thread.stack_top
6868+ );
6969+ crate::println!(" Size: {} KB, ASLR offset: ~{} bytes",
7070+ stack_size / 1024,
7171+ aslr_offset
7272+ );
7373+ }
7474+ }
7575+ });
7676+7777+ crate::println!();
7878+ crate::println!(" Entropy Source: RDTSC (fast boot-safe randomization)");
7979+ crate::println!();
8080+ crate::println!("The wards stand strong. Your sanctuary is protected.");
8181+ crate::println!();
8282+ crate::println!("─── Press ENTER for capability tests (1/3) ───");
8383+ }
8484+ 1 => {
8585+ // Page 2: Start capability tests
8686+ test_capability_sealing_page1();
8787+ crate::println!();
8888+ crate::println!("─── Press ENTER to continue (2/3) ───");
8989+ }
9090+ 2 => {
9191+ // Page 3: Finish capability tests
9292+ test_capability_sealing_page2();
9393+ crate::println!();
9494+ // Exit paging mode
9595+ unsafe {
9696+ crate::eldarin::PAGING_ACTIVE = false;
9797+ crate::eldarin::PAGING_PAGE = 0;
9898+ crate::eldarin::PAGING_COMMAND = None;
9999+ }
100100+ display_prompt();
101101+ }
102102+ _ => {
103103+ // Safety fallback
104104+ unsafe {
105105+ crate::eldarin::PAGING_ACTIVE = false;
106106+ crate::eldarin::PAGING_PAGE = 0;
107107+ crate::eldarin::PAGING_COMMAND = None;
108108+ }
109109+ display_prompt();
110110+ }
111111+ }
112112+}
113113+114114+/// Capability test page 1: Create, access, derive
115115+fn test_capability_sealing_page1() {
116116+ use crate::loom_of_fate::without_interrupts;
117117+ use crate::mana_pool::{CapabilityRights, AllocationPurpose};
118118+119119+ crate::println!("◈ Testing Capability Security System");
9120 crate::println!();
101211111- // W^X Status
1212- crate::println!(" Write ⊕ Execute Enforcement: ✓ Active");
1313- crate::println!(" Memory pages cannot be both writable and executable");
1414- crate::println!();
122122+ without_interrupts(|| {
123123+ unsafe {
124124+ let mut mana_pool = crate::mana_pool::get_mana_pool().lock();
125125+126126+ // Test 1: Create a sealed capability (returns opaque ID)
127127+ crate::println!(" Test 1: Creating sealed capability with READ+WRITE rights...");
128128+ match mana_pool.object_manager.create_object_sealed(
129129+ 0x1000000, // Dummy address
130130+ 4096, // 4KB object
131131+ AllocationPurpose::ShortLived,
132132+ CapabilityRights::read_write(),
133133+ ) {
134134+ Ok(cap_id) => {
135135+ crate::println!(" ✓ Created opaque capability ID: {}", cap_id.raw());
151361616- // ASLR Status
1717- crate::println!(" Address Space Layout Randomization: ✓ Active");
1818- crate::println!(" Thread stacks randomized with 0-64KB entropy");
1919- crate::println!();
137137+ // Test 2: Access object through opaque ID
138138+ crate::println!(" Test 2: Accessing object info via opaque ID...");
139139+ match mana_pool.object_manager.get_object_info_sealed(cap_id) {
140140+ Ok(info) => {
141141+ crate::println!(" ✓ Object info retrieved: size = {} bytes", info.size);
142142+ }
143143+ Err(e) => {
144144+ crate::println!(" ✗ Failed to get object info: {:?}", e);
145145+ }
146146+ }
201472121- // Thread Stack Information
2222- crate::println!(" Thread Stack Wards:");
148148+ // Test 3: Derive capability with reduced rights (attenuation)
149149+ crate::println!(" Test 3: Deriving READ-ONLY capability (attenuation)...");
150150+ match mana_pool.object_manager.derive_capability_sealed(
151151+ cap_id,
152152+ CapabilityRights::read_only(),
153153+ ) {
154154+ Ok(derived_id) => {
155155+ crate::println!(" ✓ Derived capability ID: {}", derived_id.raw());
231562424- use crate::attunement::interrupts::without_interrupts;
2525- use crate::loom_of_fate::ThreadState;
157157+ // Store for page 2
158158+ unsafe {
159159+ STORED_CAP_ID = Some(cap_id);
160160+ STORED_DERIVED_ID = Some(derived_id);
161161+ }
162162+ }
163163+ Err(e) => {
164164+ crate::println!(" ✗ Derivation failed: {:?}", e);
165165+ }
166166+ }
167167+ }
168168+ Err(e) => {
169169+ crate::println!(" ✗ Failed to create sealed capability: {:?}", e);
170170+ }
171171+ }
172172+ }
173173+ });
174174+}
175175+176176+/// Capability test page 2: Verify rights and release
177177+fn test_capability_sealing_page2() {
178178+ use crate::loom_of_fate::without_interrupts;
179179+ use crate::mana_pool::CapabilityRights;
2618027181 without_interrupts(|| {
28182 unsafe {
2929- let loom = crate::loom_of_fate::get_loom().lock();
3030- let threads: alloc::vec::Vec<_> = loom.threads.iter()
3131- .filter(|t| !matches!(t.state, ThreadState::Fading))
3232- .collect();
183183+ let mut mana_pool = crate::mana_pool::get_mana_pool().lock();
331843434- for thread in threads {
3535- let stack_size = thread.stack_top - thread.stack_bottom;
185185+ if let (Some(cap_id), Some(derived_id)) = (STORED_CAP_ID, STORED_DERIVED_ID) {
186186+ // Test 4: Check that derived capability has only READ rights
187187+ crate::println!(" Test 4: Verifying derived capability has READ (not WRITE)...");
188188+ match mana_pool.object_manager.check_capability_rights(
189189+ derived_id,
190190+ CapabilityRights::READ,
191191+ ) {
192192+ Ok(_) => crate::println!(" ✓ READ permission verified"),
193193+ Err(_) => crate::println!(" ✗ READ check failed!"),
194194+ }
361953737- // Calculate approximate ASLR offset based on stack pointer alignment
3838- // The actual offset is embedded in the Stack struct, but we can estimate
3939- // it from the stack top alignment
4040- let nominal_top = thread.stack_bottom + stack_size;
4141- let aslr_offset = if thread.stack_top < nominal_top {
4242- nominal_top - thread.stack_top
4343- } else {
4444- 0
4545- };
196196+ match mana_pool.object_manager.check_capability_rights(
197197+ derived_id,
198198+ CapabilityRights::WRITE,
199199+ ) {
200200+ Ok(_) => crate::println!(" ✗ SECURITY BUG: WRITE should be denied!"),
201201+ Err(_) => crate::println!(" ✓ WRITE permission correctly denied"),
202202+ }
462034747- let state_str = match thread.state {
4848- ThreadState::Weaving => "Weaving",
4949- ThreadState::Resting => "Resting",
5050- ThreadState::Tangled => "Tangled",
5151- ThreadState::Fading => "Fading",
5252- };
204204+ // Test 5: Release the capability
205205+ crate::println!(" Test 5: Releasing sealed capability...");
206206+ match mana_pool.object_manager.release_object_sealed(cap_id) {
207207+ Ok(_) => crate::println!(" ✓ Capability revoked successfully"),
208208+ Err(e) => crate::println!(" ✗ Release failed: {:?}", e),
209209+ }
532105454- crate::println!(" Thread #{} ({}): Stack 0x{:016x}-0x{:016x}",
5555- thread.id.0,
5656- state_str,
5757- thread.stack_bottom,
5858- thread.stack_top
5959- );
6060- crate::println!(" Size: {} KB, ASLR offset: ~{} bytes",
6161- stack_size / 1024,
6262- aslr_offset
6363- );
211211+ // Clear stored IDs
212212+ STORED_CAP_ID = None;
213213+ STORED_DERIVED_ID = None;
64214 }
65215 }
66216 });
6721768218 crate::println!();
6969- crate::println!(" Entropy Source: RDTSC (fast boot-safe randomization)");
7070- crate::println!();
7171- crate::println!("The wards stand strong. Your sanctuary is protected.");
219219+ crate::println!(" Security test complete. Opaque capabilities working correctly.");
72220}
221221+222222+// Store capability IDs between pages
223223+use crate::mana_pool::capability::CapabilityId;
224224+static mut STORED_CAP_ID: Option<CapabilityId> = None;
225225+static mut STORED_DERIVED_ID: Option<CapabilityId> = None;