aethelos.iso
aethelos.iso
This is a binary file and will not be displayed.
+754
docs/RUNE_OF_PERMANENCE_DESIGN.md
+754
docs/RUNE_OF_PERMANENCE_DESIGN.md
···
1
+
# The Rune of Permanence - Read-Only Kernel Data Protection
2
+
3
+
> *"The fundamental laws of the realm, once scribed at the Dawn of Awakening, are immutable. These crystalline structures, etched into the fabric of reality, cannot be altered—for to change them would be to rewrite the very physics of the world."*
4
+
5
+
## Overview
6
+
7
+
The Rune of Permanence is AethelOS's implementation of **kernel data protection through immutability**. After the kernel completes its initialization (the "Dawn of Awakening"), critical data structures are marked as read-only at the hardware level, preventing any runtime modification—even by the kernel itself.
8
+
9
+
### The Philosophy
10
+
11
+
In AethelOS, the kernel's foundational structures are not merely "protected"—they are **enchanted with permanence**. Once the realm awakens and its laws are established, those laws become as immutable as the laws of physics. The MMU (Memory Management Unit) acts as a guardian of reality itself, enforcing these constraints with hardware precision.
12
+
13
+
Any attempt to modify a permanent structure is not a "security violation"—it is an attempt to **break the fabric of reality**, and the MMU denies it instantly with a page fault.
14
+
15
+
---
16
+
17
+
## Security Model
18
+
19
+
### Threat: Data-Only Attacks
20
+
21
+
Even with W^X (no executable data), attackers can still compromise a system by **corrupting data structures** rather than injecting code:
22
+
23
+
#### Attack Vector 1: Function Pointer Overwrites
24
+
25
+
```rust
26
+
// Vulnerable dispatch table
27
+
static mut SYSCALL_TABLE: [fn(); 256] = [ /* ... */ ];
28
+
29
+
// Attacker exploits buffer overflow to overwrite entry:
30
+
SYSCALL_TABLE[42] = attacker_function; // ← Redirect syscall!
31
+
```
32
+
33
+
**Impact:** Attacker gains arbitrary code execution by hijacking control flow through legitimate function pointers.
34
+
35
+
#### Attack Vector 2: Security Policy Corruption
36
+
37
+
```rust
38
+
// Vulnerable security configuration
39
+
static mut CAPABILITIES_ENABLED: bool = true;
40
+
41
+
// Attacker overwrites this to disable security:
42
+
CAPABILITIES_ENABLED = false; // ← Bypass capability checks!
43
+
```
44
+
45
+
**Impact:** Attacker disables security features by corrupting policy flags.
46
+
47
+
#### Attack Vector 3: Critical Constant Corruption
48
+
49
+
```rust
50
+
// Vulnerable kernel configuration
51
+
static mut MAX_THREADS: usize = 256;
52
+
53
+
// Attacker sets this to 0:
54
+
MAX_THREADS = 0; // ← Denial of service!
55
+
```
56
+
57
+
**Impact:** System becomes unstable or unusable.
58
+
59
+
### Defense: The Rune of Permanence
60
+
61
+
**After-Boot Immutability:**
62
+
63
+
```
64
+
┌─────────────────────────────────────────────┐
65
+
│ Kernel Memory Layout │
66
+
├─────────────────────────────────────────────┤
67
+
│ .text (code) │ R-X │ ← Already W^X │
68
+
├─────────────────────────────────────────────┤
69
+
│ .rodata (constants) │ R-- │ ← Already RO │
70
+
├─────────────────────────────────────────────┤
71
+
│ .data (variables) │ RW- │ ← Mutable │
72
+
├─────────────────────────────────────────────┤
73
+
│ .rune (permanent) │ RW- │ ← Boot phase │
74
+
│ │ ↓ │ │
75
+
│ │ R-- │ ← After boot! │
76
+
└─────────────────────────────────────────────┘
77
+
```
78
+
79
+
**The `.rune` section** contains structures that:
80
+
1. **Are initialized at boot** (writable during init)
81
+
2. **Never change afterward** (read-only after init)
82
+
3. **Are security-critical** (function pointers, policies, etc.)
83
+
84
+
---
85
+
86
+
## Implementation Strategy
87
+
88
+
### Phase 1: Identify Permanent Structures
89
+
90
+
Categorize kernel data by mutability:
91
+
92
+
#### ✓ Permanent (Mark as `.rune`)
93
+
94
+
**Function Pointer Tables:**
95
+
- Syscall dispatch table
96
+
- Interrupt handler table (IDT entries)
97
+
- Virtual function tables (vtables)
98
+
- Driver callback tables
99
+
100
+
**Security Policies:**
101
+
- Capability enforcement flags
102
+
- W^X enforcement settings
103
+
- ASLR configuration
104
+
- Stack canary enforcement
105
+
106
+
**Kernel Configuration:**
107
+
- Thread limits (MAX_THREADS)
108
+
- Memory layout constants (KERNEL_BASE)
109
+
- Hardware resource limits
110
+
111
+
**Immutable References:**
112
+
- Static references to permanent structures
113
+
- Global singletons initialized at boot
114
+
115
+
#### ✗ Mutable (Keep as `.data`)
116
+
117
+
**Runtime State:**
118
+
- Current thread ID
119
+
- Scheduler state
120
+
- Memory allocator free lists
121
+
- I/O buffers
122
+
123
+
**Statistics:**
124
+
- Thread counters
125
+
- Memory usage metrics
126
+
- Performance timers
127
+
128
+
**Lock State:**
129
+
- Mutex ownership
130
+
- Semaphore counts
131
+
132
+
---
133
+
134
+
### Phase 2: Rust Language Support
135
+
136
+
Rust provides excellent compile-time immutability, but we need **runtime immutability** after boot.
137
+
138
+
#### Option A: `const` for Compile-Time Immutability
139
+
140
+
```rust
141
+
// Already immutable at compile time
142
+
const KERNEL_VERSION: &str = "0.1.0-alpha";
143
+
144
+
// Compiler places this in .rodata (read-only section)
145
+
```
146
+
147
+
**Limitation:** Can't initialize complex structures at runtime (e.g., generated entropy, hardware detection).
148
+
149
+
#### Option B: `static` + Custom Section for Runtime Immutability
150
+
151
+
```rust
152
+
// Place in custom .rune section for post-boot protection
153
+
#[link_section = ".rune"]
154
+
static mut SYSCALL_TABLE: SyscallTable = SyscallTable::empty();
155
+
156
+
// During boot: Initialize (writable)
157
+
unsafe {
158
+
SYSCALL_TABLE.register(0, sys_read);
159
+
SYSCALL_TABLE.register(1, sys_write);
160
+
// ...
161
+
}
162
+
163
+
// After boot: Mark page as read-only
164
+
unsafe {
165
+
seal_permanent_structures();
166
+
}
167
+
```
168
+
169
+
**Advantages:**
170
+
- Runtime initialization (can use hardware RNG, probe devices)
171
+
- Explicit control over when immutability takes effect
172
+
- Clear separation of permanent vs mutable data
173
+
174
+
---
175
+
176
+
### Phase 3: Memory Page Protection
177
+
178
+
Use x86_64 page tables to enforce read-only protection at the hardware level.
179
+
180
+
#### Page Table Entry Bits
181
+
182
+
```
183
+
┌───────────────────────────────────────────────┐
184
+
│ Page Table Entry (x86_64) │
185
+
├───────────────────────────────────────────────┤
186
+
│ Bit 0: Present (P) │ 1 = page valid │
187
+
│ Bit 1: Read/Write (RW) │ 1 = writable │ ← Clear this!
188
+
│ Bit 2: User/Supervisor (US)│ 0 = kernel only │
189
+
│ Bit 3: Write-Through (PWT) │ │
190
+
│ Bit 4: Cache Disable (PCD) │ │
191
+
│ Bit 5: Accessed (A) │ │
192
+
│ Bit 6: Dirty (D) │ │
193
+
│ Bit 7: Page Size (PS) │ │
194
+
│ Bit 63: Execute Disable (NX)│ 1 = no execute │
195
+
└───────────────────────────────────────────────┘
196
+
```
197
+
198
+
**To make a page read-only:**
199
+
1. Find the page table entry (PTE) for the `.rune` section
200
+
2. Clear bit 1 (Read/Write bit) → `RW = 0`
201
+
3. Flush TLB (translation lookaside buffer) to apply change
202
+
203
+
#### Implementation
204
+
205
+
```rust
206
+
// In mana_pool/page_tables.rs
207
+
208
+
/// Seal the `.rune` section as read-only after boot
209
+
///
210
+
/// # Safety
211
+
/// This MUST only be called once, after all permanent structures
212
+
/// have been initialized. After this call, ANY writes to the .rune
213
+
/// section will cause a page fault.
214
+
pub unsafe fn seal_rune_section() {
215
+
extern "C" {
216
+
static __rune_start: u8;
217
+
static __rune_end: u8;
218
+
}
219
+
220
+
let start = &__rune_start as *const u8 as u64;
221
+
let end = &__rune_end as *const u8 as u64;
222
+
223
+
println!("◈ Sealing The Rune of Permanence...");
224
+
println!(" Range: 0x{:016x} - 0x{:016x}", start, end);
225
+
226
+
// Iterate over pages in the .rune section
227
+
let mut addr = start & !0xFFF; // Align to 4KB page boundary
228
+
while addr < end {
229
+
// Get page table entry for this address
230
+
let pte = get_pte_for_address(addr);
231
+
232
+
// Clear the Write bit (bit 1)
233
+
let mut entry = pte.read();
234
+
entry &= !(1 << 1); // Clear RW bit → read-only
235
+
pte.write(entry);
236
+
237
+
// Move to next page
238
+
addr += 0x1000; // 4KB page size
239
+
}
240
+
241
+
// Flush TLB to ensure changes take effect immediately
242
+
flush_tlb();
243
+
244
+
println!(" ✓ The Rune is sealed. Permanence enforced by the MMU.");
245
+
}
246
+
247
+
/// Flush the Translation Lookaside Buffer (TLB)
248
+
///
249
+
/// This forces the CPU to reload page table entries from memory,
250
+
/// ensuring that our read-only protection takes effect immediately.
251
+
#[inline(always)]
252
+
fn flush_tlb() {
253
+
unsafe {
254
+
// Reloading CR3 flushes the entire TLB
255
+
core::arch::asm!(
256
+
"mov rax, cr3",
257
+
"mov cr3, rax",
258
+
out("rax") _,
259
+
options(nostack, preserves_flags)
260
+
);
261
+
}
262
+
}
263
+
```
264
+
265
+
---
266
+
267
+
### Phase 4: Linker Script Configuration
268
+
269
+
Define the `.rune` section in our linker script so permanent structures are grouped together.
270
+
271
+
#### Update `linker.ld`
272
+
273
+
```ld
274
+
SECTIONS
275
+
{
276
+
. = 0x100000; /* Kernel starts at 1MB */
277
+
278
+
.text : ALIGN(4K) {
279
+
*(.text .text.*)
280
+
}
281
+
282
+
.rodata : ALIGN(4K) {
283
+
*(.rodata .rodata.*)
284
+
}
285
+
286
+
/* NEW: The Rune of Permanence section */
287
+
.rune : ALIGN(4K) {
288
+
PROVIDE(__rune_start = .);
289
+
*(.rune .rune.*)
290
+
PROVIDE(__rune_end = .);
291
+
}
292
+
293
+
.data : ALIGN(4K) {
294
+
*(.data .data.*)
295
+
}
296
+
297
+
.bss : ALIGN(4K) {
298
+
*(.bss .bss.*)
299
+
}
300
+
}
301
+
```
302
+
303
+
**Key points:**
304
+
- `.rune` section is page-aligned (4KB) for MMU protection
305
+
- `__rune_start` and `__rune_end` symbols mark the boundaries
306
+
- Placed between `.rodata` and `.data` for clarity
307
+
308
+
---
309
+
310
+
### Phase 5: Boot Sequence Integration
311
+
312
+
Integrate sealing into the kernel boot process.
313
+
314
+
#### Boot Flow
315
+
316
+
```rust
317
+
// In main.rs
318
+
319
+
fn kernel_main() -> ! {
320
+
// Phase 1: Early initialization (mutable)
321
+
println!("◈ Dawn of Awakening - Kernel Initialization");
322
+
323
+
init_vga_buffer();
324
+
init_gdt();
325
+
init_idt();
326
+
init_pic();
327
+
init_timer();
328
+
init_mana_pool();
329
+
330
+
// Phase 2: Initialize permanent structures (still mutable)
331
+
println!("◈ Scribing the foundational runes...");
332
+
333
+
init_syscall_table(); // Populate function pointers
334
+
init_security_policy(); // Set capability flags
335
+
init_kernel_config(); // Set limits and constants
336
+
337
+
// Phase 3: Seal permanent structures (make read-only)
338
+
println!("◈ Invoking The Rune of Permanence...");
339
+
unsafe {
340
+
mana_pool::page_tables::seal_rune_section();
341
+
}
342
+
println!(" ✓ The foundational laws are now immutable.");
343
+
344
+
// Phase 4: Start scheduler (permanent structures are now protected)
345
+
println!("◈ Weaving the Loom of Fate...");
346
+
loom_of_fate::init();
347
+
348
+
// Phase 5: Enter userspace (if applicable)
349
+
// ...
350
+
351
+
loop {
352
+
// Main kernel loop
353
+
}
354
+
}
355
+
```
356
+
357
+
**Critical Timing:**
358
+
- **Before sealing:** Initialize all structures that go in `.rune`
359
+
- **After sealing:** NEVER write to `.rune` - will cause page fault!
360
+
361
+
---
362
+
363
+
## Practical Example: Protecting the IDT
364
+
365
+
The Interrupt Descriptor Table (IDT) is a prime target for attackers. Let's protect it with The Rune of Permanence.
366
+
367
+
### Current Implementation (Vulnerable)
368
+
369
+
```rust
370
+
// In attunement/idt.rs
371
+
372
+
// Mutable IDT in .data section (writable!)
373
+
static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
374
+
375
+
pub fn init() {
376
+
unsafe {
377
+
// Set up interrupt handlers
378
+
IDT.breakpoint.set_handler_fn(breakpoint_handler);
379
+
IDT.page_fault.set_handler_fn(page_fault_handler);
380
+
// ...
381
+
382
+
// Load IDT
383
+
IDT.load();
384
+
}
385
+
}
386
+
```
387
+
388
+
**Vulnerability:** Attacker with write-what-where primitive can overwrite IDT entries to hijack interrupts.
389
+
390
+
### Protected Implementation (Permanent)
391
+
392
+
```rust
393
+
// In attunement/idt.rs
394
+
395
+
// IDT in .rune section (read-only after boot!)
396
+
#[link_section = ".rune"]
397
+
static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
398
+
399
+
pub fn init() {
400
+
unsafe {
401
+
// During boot: Initialize IDT (still writable)
402
+
IDT.breakpoint.set_handler_fn(breakpoint_handler);
403
+
IDT.page_fault.set_handler_fn(page_fault_handler);
404
+
// ...
405
+
406
+
// Load IDT
407
+
IDT.load();
408
+
409
+
// NOTE: After seal_rune_section() is called,
410
+
// this IDT becomes read-only and cannot be modified!
411
+
}
412
+
}
413
+
```
414
+
415
+
**Protection:**
416
+
- Attacker can READ the IDT (see handler addresses) → Mitigated by ASLR
417
+
- Attacker CANNOT WRITE to IDT → Hardware enforced (page fault)
418
+
- Even kernel bugs can't accidentally corrupt IDT after boot
419
+
420
+
---
421
+
422
+
## Security Properties
423
+
424
+
### ✓ Defense-in-Depth
425
+
426
+
The Rune of Permanence complements existing protections:
427
+
428
+
| Protection | Defense Against | Layer |
429
+
|------------|----------------|-------|
430
+
| **W^X** | Code injection | Data ≠ Code |
431
+
| **ASLR** | Address prediction | Randomization |
432
+
| **Capability Sealing** | Capability forgery | Cryptography |
433
+
| **Weaver's Sigil** | Stack overflows | Stack canaries |
434
+
| **Rune of Permanence** | Data corruption | Immutability |
435
+
436
+
### ✓ Hardware Enforcement
437
+
438
+
- **Not bypassable in software:** MMU enforces at CPU level
439
+
- **No performance overhead:** Page permissions checked in hardware
440
+
- **Fail-secure:** Write attempts cause immediate page fault
441
+
442
+
### ✓ Comprehensive Coverage
443
+
444
+
Protected structures include:
445
+
- ✓ IDT (interrupt handlers)
446
+
- ✓ GDT (segment descriptors)
447
+
- ✓ Syscall dispatch table
448
+
- ✓ Security policy flags
449
+
- ✓ Kernel configuration constants
450
+
- ✓ Function pointer tables
451
+
452
+
### ⚠ Limitations
453
+
454
+
**Not Protected:**
455
+
- Runtime state (scheduler queues, memory allocators)
456
+
- Performance statistics
457
+
- Lock state
458
+
459
+
**Attack Scenarios:**
460
+
- **Physical memory access:** Attacker with DMA can modify memory
461
+
- **Mitigation:** Future IOMMU support
462
+
- **Speculative execution bugs:** Spectre/Meltdown-class attacks
463
+
- **Mitigation:** CPU microcode updates, software mitigations
464
+
- **Kernel exploits before sealing:** If attacker compromises kernel during boot
465
+
- **Mitigation:** Secure boot, measured boot
466
+
467
+
---
468
+
469
+
## Testing Strategy
470
+
471
+
### Unit Tests
472
+
473
+
```rust
474
+
#[cfg(test)]
475
+
mod tests {
476
+
use super::*;
477
+
478
+
#[test]
479
+
fn test_rune_section_exists() {
480
+
extern "C" {
481
+
static __rune_start: u8;
482
+
static __rune_end: u8;
483
+
}
484
+
485
+
let start = unsafe { &__rune_start as *const u8 as usize };
486
+
let end = unsafe { &__rune_end as *const u8 as usize };
487
+
488
+
assert!(end > start, "Rune section must have non-zero size");
489
+
assert!(start % 0x1000 == 0, "Rune section must be page-aligned");
490
+
}
491
+
492
+
#[test]
493
+
#[should_panic]
494
+
fn test_write_to_sealed_rune_causes_fault() {
495
+
#[link_section = ".rune"]
496
+
static mut TEST_VAR: u64 = 0;
497
+
498
+
unsafe {
499
+
// Initialize
500
+
TEST_VAR = 42;
501
+
502
+
// Seal
503
+
seal_rune_section();
504
+
505
+
// Attempt write (should page fault!)
506
+
TEST_VAR = 99; // ← PANIC!
507
+
}
508
+
}
509
+
}
510
+
```
511
+
512
+
### Integration Tests
513
+
514
+
```rust
515
+
// In tests/permanence_test.rs
516
+
517
+
#[test_case]
518
+
fn test_idt_cannot_be_modified_after_boot() {
519
+
// Get IDT base address
520
+
let idtr = x86_64::instructions::tables::sidt();
521
+
let idt_base = idtr.base.as_ptr::<u8>();
522
+
523
+
// Attempt to write (should fail)
524
+
let result = std::panic::catch_unwind(|| unsafe {
525
+
core::ptr::write_volatile(idt_base, 0xFF);
526
+
});
527
+
528
+
assert!(result.is_err(), "Writing to IDT after sealing should panic");
529
+
}
530
+
```
531
+
532
+
---
533
+
534
+
## Implementation Phases
535
+
536
+
### Phase 1: Linker Script (Week 1)
537
+
- [ ] Add `.rune` section to `linker.ld`
538
+
- [ ] Add `__rune_start` and `__rune_end` symbols
539
+
- [ ] Verify section alignment (4KB pages)
540
+
- [ ] Test that section is created and non-empty
541
+
542
+
### Phase 2: Page Table Infrastructure (Week 1-2)
543
+
- [ ] Implement `get_pte_for_address()` function
544
+
- [ ] Implement `seal_rune_section()` function
545
+
- [ ] Implement `flush_tlb()` function
546
+
- [ ] Add debug logging for sealed pages
547
+
- [ ] Test on dummy data before real structures
548
+
549
+
### Phase 3: Move Critical Structures (Week 2-3)
550
+
- [ ] Move IDT to `.rune` section
551
+
- [ ] Move GDT to `.rune` section (if applicable)
552
+
- [ ] Move syscall table to `.rune` (future)
553
+
- [ ] Move security policy flags to `.rune`
554
+
- [ ] Test each structure after migration
555
+
556
+
### Phase 4: Boot Integration (Week 3)
557
+
- [ ] Add sealing call to `kernel_main()`
558
+
- [ ] Ensure all `.rune` structures initialized before sealing
559
+
- [ ] Add boot message for sealing
560
+
- [ ] Test that kernel boots successfully
561
+
- [ ] Verify writes fail after sealing
562
+
563
+
### Phase 5: Monitoring & Validation (Week 3-4)
564
+
- [ ] Add page fault handler logging for .rune violations
565
+
- [ ] Add Rune of Permanence status to `wards` command
566
+
- [ ] Implement debug command to inspect page permissions
567
+
- [ ] Create attack simulations (try to corrupt IDT, etc.)
568
+
- [ ] Performance benchmarks (should be zero overhead)
569
+
570
+
---
571
+
572
+
## Eldarin Shell Integration
573
+
574
+
### New Command: `permanence`
575
+
576
+
Display protected structures and their status:
577
+
578
+
```
579
+
◈ The Rune of Permanence - Immutable Kernel Structures
580
+
581
+
Status: ✓ SEALED (hardware-enforced)
582
+
Protection: MMU Read-Only Pages
583
+
584
+
Protected Structures:
585
+
.rune section: 0x00120000 - 0x00124000 (16 KB)
586
+
Pages: 4 (all read-only)
587
+
588
+
◈ Interrupt Descriptor Table (IDT)
589
+
Address: 0x00120000
590
+
Size: 4096 bytes
591
+
Status: ✓ Sealed (cannot modify handlers)
592
+
593
+
◈ Global Descriptor Table (GDT)
594
+
Address: 0x00121000
595
+
Size: 256 bytes
596
+
Status: ✓ Sealed (cannot modify segments)
597
+
598
+
◈ Security Policy
599
+
Address: 0x00121100
600
+
Status: ✓ Sealed
601
+
- Capability enforcement: ENABLED (permanent)
602
+
- W^X enforcement: ENABLED (permanent)
603
+
- Stack canaries: ENABLED (permanent)
604
+
605
+
Violations Detected: 0 (since boot)
606
+
Last Violation: None
607
+
608
+
The foundational laws remain unbroken. The Rune stands eternal.
609
+
```
610
+
611
+
### Update `wards` Command
612
+
613
+
Add Rune of Permanence to the security overview:
614
+
615
+
```rust
616
+
// In wards_command.rs (Page 1)
617
+
618
+
crate::println!(" Rune of Permanence (Immutable Structures): ✓ Active");
619
+
crate::println!(" Protected: IDT, GDT, Security Policy");
620
+
crate::println!(" Pages: 4 read-only");
621
+
crate::println!();
622
+
```
623
+
624
+
---
625
+
626
+
## Page Fault Handler Enhancement
627
+
628
+
Detect writes to `.rune` section and provide helpful diagnostics:
629
+
630
+
```rust
631
+
// In attunement/idt_handlers.rs
632
+
633
+
pub extern "x86-interrupt" fn page_fault_handler(
634
+
stack_frame: InterruptStackFrame,
635
+
error_code: PageFaultErrorCode,
636
+
) {
637
+
use x86_64::registers::control::Cr2;
638
+
639
+
// Get the address that caused the fault
640
+
let fault_addr = Cr2::read().as_u64();
641
+
642
+
// Check if this was a write to the .rune section
643
+
extern "C" {
644
+
static __rune_start: u8;
645
+
static __rune_end: u8;
646
+
}
647
+
let rune_start = unsafe { &__rune_start as *const u8 as u64 };
648
+
let rune_end = unsafe { &__rune_end as *const u8 as u64 };
649
+
650
+
if fault_addr >= rune_start && fault_addr < rune_end {
651
+
if error_code.contains(PageFaultErrorCode::CAUSED_BY_WRITE) {
652
+
// Write to sealed section!
653
+
panic!(
654
+
"◈ RUNE VIOLATION: Attempt to modify permanent structure!\n\
655
+
Address: 0x{:016x}\n\
656
+
Section: .rune (read-only)\n\
657
+
Error: Write to immutable memory after Dawn of Awakening\n\
658
+
\n\
659
+
The Rune of Permanence has been violated. The foundational \n\
660
+
laws cannot be rewritten. The MMU denies this reality break.",
661
+
fault_addr
662
+
);
663
+
}
664
+
}
665
+
666
+
// Standard page fault handling...
667
+
panic!("Page fault at 0x{:016x}: {:?}", fault_addr, error_code);
668
+
}
669
+
```
670
+
671
+
---
672
+
673
+
## Future Enhancements
674
+
675
+
### Incremental Sealing
676
+
677
+
Instead of sealing everything at once, seal structures incrementally:
678
+
679
+
```rust
680
+
pub fn seal_idt() {
681
+
seal_range(&IDT as *const _ as u64, size_of::<IDT>());
682
+
}
683
+
684
+
pub fn seal_gdt() {
685
+
seal_range(&GDT as *const _ as u64, size_of::<GDT>());
686
+
}
687
+
```
688
+
689
+
**Benefit:** More granular control, easier debugging.
690
+
691
+
### Runtime Re-initialization (Emergency Mode)
692
+
693
+
For debugging or emergency patches, temporarily unseal:
694
+
695
+
```rust
696
+
pub unsafe fn unseal_rune_section_emergency(auth_token: &EmergencyAuth) {
697
+
// Verify cryptographic auth token
698
+
if !verify_emergency_auth(auth_token) {
699
+
panic!("Unauthorized unseal attempt!");
700
+
}
701
+
702
+
// Log the event
703
+
log::warn!("EMERGENCY: Unsealing .rune section");
704
+
705
+
// Temporarily make writable
706
+
// ... (reverse the sealing process)
707
+
}
708
+
```
709
+
710
+
**Use case:** Hot-patching critical vulnerabilities without reboot.
711
+
712
+
### Signature Verification
713
+
714
+
Before sealing, compute a cryptographic hash of `.rune` section:
715
+
716
+
```rust
717
+
pub fn seal_with_verification() {
718
+
// Compute hash before sealing
719
+
let hash = compute_rune_hash();
720
+
721
+
// Seal the section
722
+
seal_rune_section();
723
+
724
+
// Store hash in secure location
725
+
store_rune_hash(hash);
726
+
}
727
+
728
+
pub fn verify_rune_integrity() -> bool {
729
+
let current_hash = compute_rune_hash();
730
+
let stored_hash = retrieve_rune_hash();
731
+
732
+
constant_time_compare(¤t_hash, &stored_hash)
733
+
}
734
+
```
735
+
736
+
**Benefit:** Detect if `.rune` was modified before sealing (bootkit attack).
737
+
738
+
---
739
+
740
+
## References
741
+
742
+
- **PaX CONSTIFY:** https://pax.grsecurity.net/docs/constify.txt
743
+
- **Linux RODATA:** https://lwn.net/Articles/666550/
744
+
- **x86_64 Paging:** Intel SDM Volume 3A, Chapter 4
745
+
- **Rust Linker Scripts:** https://doc.rust-lang.org/rustc/codegen-options/index.html#link-args
746
+
747
+
---
748
+
749
+
*"The Rune of Permanence is etched not in ink, but in the silicon itself. It is enforced not by software, but by the immutable laws of the MMU. To break it is to break reality—and reality does not yield."*
750
+
751
+
**Status:** 🚧 Design Complete, Implementation Pending
752
+
**Priority:** High (Security Critical)
753
+
**Estimated Effort:** 3-4 weeks
754
+
**Dependencies:** Functional page table management
+610
docs/WEAVERS_SIGIL_DESIGN.md
+610
docs/WEAVERS_SIGIL_DESIGN.md
···
1
+
# The Weaver's Sigil - Stack Canary Protection Design
2
+
3
+
> *"Each thread weaves its own fate, and marks it with a sigil of purity. Should that mark be corrupted, the thread knows its fate has been tampered with by chaotic forces."*
4
+
5
+
## Overview
6
+
7
+
The Weaver's Sigil is AethelOS's implementation of **stack canary protection**, a runtime defense against buffer overflow attacks that attempt to overwrite return addresses on the stack.
8
+
9
+
### The Philosophy
10
+
11
+
In AethelOS, each thread is a strand in the Loom of Fate. When a thread begins weaving a new spell (calling a function), it places a unique **magical sigil** on its own thread of fate (the stack). This sigil is:
12
+
13
+
- **Personal**: Unique per-thread to prevent cross-thread attacks
14
+
- **Secret**: Unknown to userspace, preventing predictable overwrites
15
+
- **Immutable**: Any change indicates corruption
16
+
17
+
Before completing the spell (returning from the function), the thread inspects its sigil. If corrupted, the thread immediately enters **quarantine** (panic), containing the damage before it spreads.
18
+
19
+
---
20
+
21
+
## Security Model
22
+
23
+
### Threat: Stack Buffer Overflows
24
+
25
+
**Attack Vector:**
26
+
```
27
+
Stack Layout (grows downward):
28
+
┌─────────────────┐ ← High addresses
29
+
│ Return Address │ ← Attacker wants to overwrite this!
30
+
├─────────────────┤
31
+
│ Saved RBP │
32
+
├─────────────────┤
33
+
│ Local Buffer │ ← Overflow starts here
34
+
└─────────────────┘ ← Low addresses
35
+
```
36
+
37
+
If an attacker can overflow `Local Buffer`, they can:
38
+
1. Overwrite the saved return address
39
+
2. Redirect execution to attacker-controlled code
40
+
3. Gain arbitrary code execution
41
+
42
+
### Defense: The Weaver's Sigil
43
+
44
+
**Protected Stack Layout:**
45
+
```
46
+
Stack Layout (grows downward):
47
+
┌─────────────────┐ ← High addresses
48
+
│ Return Address │ ← Protected by canary below
49
+
├─────────────────┤
50
+
│ Saved RBP │
51
+
├─────────────────┤
52
+
│ CANARY (8 bytes)│ ← The Weaver's Sigil! Random, secret value
53
+
├─────────────────┤
54
+
│ Local Buffer │ ← Overflow must corrupt canary to reach return address
55
+
└─────────────────┘ ← Low addresses
56
+
```
57
+
58
+
**Function Prologue (Entry):**
59
+
```rust
60
+
// 1. Load thread-local canary
61
+
let canary = current_thread().sigil;
62
+
63
+
// 2. Push canary onto stack
64
+
push(canary);
65
+
66
+
// 3. Continue with normal prologue (save RBP, allocate locals, etc.)
67
+
```
68
+
69
+
**Function Epilogue (Exit):**
70
+
```rust
71
+
// 1. Load canary from stack
72
+
let stack_canary = pop_canary();
73
+
74
+
// 2. Compare with thread-local canary
75
+
if stack_canary != current_thread().sigil {
76
+
panic!("◈ SIGIL CORRUPTED: Stack overflow detected!");
77
+
}
78
+
79
+
// 3. Continue with normal epilogue (restore RBP, return)
80
+
```
81
+
82
+
---
83
+
84
+
## Implementation Strategy
85
+
86
+
### Phase 1: Infrastructure (Canary Storage)
87
+
88
+
**Per-Thread Canary Storage:**
89
+
90
+
Each `Thread` structure stores its unique Weaver's Sigil:
91
+
92
+
```rust
93
+
// In loom_of_fate/mod.rs
94
+
pub struct Thread {
95
+
// ... existing fields ...
96
+
97
+
/// The Weaver's Sigil - unique per-thread stack canary
98
+
/// This value is secret and should never be exposed to userspace
99
+
pub(crate) sigil: u64,
100
+
}
101
+
```
102
+
103
+
**Canary Generation:**
104
+
105
+
Use existing entropy infrastructure (ChaCha8Rng with RDTSC):
106
+
107
+
```rust
108
+
// In loom_of_fate/mod.rs
109
+
impl Thread {
110
+
pub fn new(entry_point: fn() -> !, priority: ThreadPriority) -> Self {
111
+
// Generate unique sigil for this thread
112
+
let sigil = {
113
+
let mut rng = mana_pool::entropy::ChaCha8Rng::from_hardware_fast();
114
+
let high = rng.next_u32() as u64;
115
+
let low = rng.next_u32() as u64;
116
+
(high << 32) | low
117
+
};
118
+
119
+
Thread {
120
+
// ... other fields ...
121
+
sigil,
122
+
}
123
+
}
124
+
}
125
+
```
126
+
127
+
**Thread-Local Access:**
128
+
129
+
Provide fast access to current thread's sigil:
130
+
131
+
```rust
132
+
// In loom_of_fate/mod.rs
133
+
134
+
/// Get the current thread's Weaver's Sigil (stack canary)
135
+
///
136
+
/// # Safety
137
+
/// This function is unsafe because it accesses thread-local state.
138
+
/// It MUST only be called from kernel code, never exposed to userspace.
139
+
#[inline(always)]
140
+
pub unsafe fn get_current_sigil() -> u64 {
141
+
get_loom().lock().current_thread().sigil
142
+
}
143
+
```
144
+
145
+
---
146
+
147
+
### Phase 2: Compiler Integration
148
+
149
+
Rust doesn't natively support stack canaries for custom targets like `x86_64-aethelos.json`. We have two approaches:
150
+
151
+
#### Option A: LLVM Stack Protector (Preferred)
152
+
153
+
Enable LLVM's built-in stack protector in our target JSON:
154
+
155
+
```json
156
+
{
157
+
"llvm-target": "x86_64-unknown-none",
158
+
"target-endian": "little",
159
+
"target-pointer-width": "64",
160
+
"features": "-mmx,-sse,+soft-float",
161
+
"disable-redzone": true,
162
+
"panic-strategy": "abort",
163
+
164
+
"stack-probes": {
165
+
"kind": "inline-or-call",
166
+
"min-llvm-version-for-inline": [16, 0, 0]
167
+
},
168
+
169
+
"// NEW": "Enable LLVM stack protector",
170
+
"stack-protector": "strong"
171
+
}
172
+
```
173
+
174
+
**Stack Protector Modes:**
175
+
- `"basic"` - Protect functions with buffers > 8 bytes
176
+
- `"strong"` - Protect all functions with local arrays or address-taken locals
177
+
- `"all"` - Protect ALL functions (performance overhead)
178
+
179
+
**For AethelOS:** Use `"strong"` mode for security without protecting trivial functions.
180
+
181
+
**Implement `__stack_chk_fail`:**
182
+
183
+
LLVM expects a symbol `__stack_chk_fail` to be called when canary check fails:
184
+
185
+
```rust
186
+
// In lib.rs or boot.rs
187
+
188
+
/// Stack canary failure handler
189
+
/// Called by LLVM when a stack corruption is detected
190
+
#[no_mangle]
191
+
pub extern "C" fn __stack_chk_fail() -> ! {
192
+
// Log the corruption
193
+
serial_println!("◈ FATAL: The Weaver's Sigil has been corrupted!");
194
+
serial_println!(" Stack overflow detected. Thread integrity compromised.");
195
+
196
+
// Get current thread info for debugging
197
+
unsafe {
198
+
if let Some(thread) = loom_of_fate::get_current_thread_debug_info() {
199
+
serial_println!(" Thread: #{} ({})", thread.id, thread.name);
200
+
serial_println!(" Stack: 0x{:016x} - 0x{:016x}",
201
+
thread.stack_bottom, thread.stack_top);
202
+
}
203
+
}
204
+
205
+
// Panic with stack trace
206
+
panic!("Stack canary violation - buffer overflow detected!");
207
+
}
208
+
```
209
+
210
+
**Implement `__stack_chk_guard`:**
211
+
212
+
LLVM uses a global symbol `__stack_chk_guard` as the canary value. We need to provide this and populate it per-thread:
213
+
214
+
```rust
215
+
// In lib.rs
216
+
217
+
/// Global stack canary value (thread-local via TLS)
218
+
/// LLVM uses this symbol for stack protection
219
+
#[no_mangle]
220
+
pub static mut __stack_chk_guard: u64 = 0xDEADBEEF_CAFEBABE; // Placeholder
221
+
222
+
/// Initialize stack canary for current thread
223
+
/// Called when switching to a new thread
224
+
pub unsafe fn init_thread_canary(thread_sigil: u64) {
225
+
core::ptr::write_volatile(&mut __stack_chk_guard, thread_sigil);
226
+
}
227
+
```
228
+
229
+
**Thread Context Switch Integration:**
230
+
231
+
Update scheduler to set canary when switching threads:
232
+
233
+
```rust
234
+
// In loom_of_fate/mod.rs - schedule() function
235
+
236
+
fn schedule(&mut self) {
237
+
// ... existing scheduling logic ...
238
+
239
+
let next_thread = self.find_next_thread();
240
+
241
+
// Update stack canary for new thread
242
+
unsafe {
243
+
crate::init_thread_canary(next_thread.sigil);
244
+
}
245
+
246
+
// ... continue with context switch ...
247
+
}
248
+
```
249
+
250
+
---
251
+
252
+
#### Option B: Manual Instrumentation (Fallback)
253
+
254
+
If LLVM stack protector doesn't work, we manually instrument critical functions:
255
+
256
+
**Macro for Protected Functions:**
257
+
258
+
```rust
259
+
/// Macro to add stack canary protection to a function
260
+
///
261
+
/// Usage:
262
+
/// ```
263
+
/// #[stack_protected]
264
+
/// fn vulnerable_function(buffer: &mut [u8]) {
265
+
/// // ... potentially unsafe buffer operations ...
266
+
/// }
267
+
/// ```
268
+
#[macro_export]
269
+
macro_rules! stack_protected {
270
+
(fn $name:ident($($args:tt)*) -> $ret:ty $body:block) => {
271
+
fn $name($($args)*) -> $ret {
272
+
// Prologue: Save canary on stack
273
+
let __sigil = unsafe { $crate::loom_of_fate::get_current_sigil() };
274
+
275
+
// Execute function body
276
+
let __result = (|| $body)();
277
+
278
+
// Epilogue: Check canary
279
+
let __current_sigil = unsafe { $crate::loom_of_fate::get_current_sigil() };
280
+
if __sigil != __current_sigil {
281
+
panic!("Stack canary violation in {}", stringify!($name));
282
+
}
283
+
284
+
__result
285
+
}
286
+
};
287
+
}
288
+
```
289
+
290
+
**Usage:**
291
+
292
+
```rust
293
+
stack_protected! {
294
+
fn parse_user_input(buffer: &mut [u8; 256]) -> Result<Command, ParseError> {
295
+
// Function is now protected by stack canary
296
+
// ...
297
+
}
298
+
}
299
+
```
300
+
301
+
---
302
+
303
+
### Phase 3: Kernel Integration
304
+
305
+
**1. Boot Initialization:**
306
+
307
+
```rust
308
+
// In main.rs
309
+
310
+
fn kernel_main() {
311
+
// ... existing init ...
312
+
313
+
// Initialize Weaver's Sigil protection
314
+
println!("◈ Weaving protective sigils...");
315
+
unsafe {
316
+
// Set initial canary for boot thread
317
+
let boot_sigil = 0xBADC0FFE_DEADBEEF; // Random initial value
318
+
crate::init_thread_canary(boot_sigil);
319
+
}
320
+
println!(" ✓ The Weaver's Sigil protects the kernel");
321
+
322
+
// ... continue boot ...
323
+
}
324
+
```
325
+
326
+
**2. Thread Creation:**
327
+
328
+
```rust
329
+
// In loom_of_fate/mod.rs
330
+
331
+
impl LoomOfFate {
332
+
pub fn spawn(&mut self, entry: fn() -> !, priority: ThreadPriority)
333
+
-> Result<ThreadId, LoomError> {
334
+
335
+
// Create thread (generates unique sigil)
336
+
let thread = Thread::new(entry, priority);
337
+
338
+
serial_println!("◈ New thread sigil: 0x{:016x}", thread.sigil);
339
+
340
+
// ... rest of spawn logic ...
341
+
}
342
+
}
343
+
```
344
+
345
+
**3. Context Switch:**
346
+
347
+
Already covered above - update `__stack_chk_guard` on every context switch.
348
+
349
+
---
350
+
351
+
### Phase 4: Userspace Protection (Future)
352
+
353
+
When userspace processes are implemented:
354
+
355
+
1. **Separate Canaries:** Each process has its own canary namespace
356
+
2. **Syscall Barrier:** Kernel saves/restores `__stack_chk_guard` on syscall entry/exit
357
+
3. **ASLR Integration:** Combine with ASLR for layered defense
358
+
359
+
---
360
+
361
+
## Security Properties
362
+
363
+
### ✓ Defense-in-Depth
364
+
365
+
The Weaver's Sigil complements existing AethelOS protections:
366
+
367
+
| Protection | Defense Against | Status |
368
+
|------------|----------------|--------|
369
+
| **W^X** | Code injection | ✅ Active |
370
+
| **ASLR** | Return-to-libc, ROP | ✅ Active |
371
+
| **Capability Sealing** | Capability forgery | ✅ Active |
372
+
| **Opaque Handles** | Direct capability manipulation | ✅ Active |
373
+
| **Weaver's Sigil** | Stack buffer overflows | 🚧 Planned |
374
+
375
+
### ✓ Entropy Quality
376
+
377
+
- **Per-Thread:** Each thread has unique canary (64-bit)
378
+
- **Source:** ChaCha8Rng seeded from RDTSC
379
+
- **Renewal:** New canary on every thread creation
380
+
- **Secret:** Never exposed to userspace
381
+
382
+
### ✓ Performance
383
+
384
+
- **LLVM Mode:** Negligible overhead (~1-3% for "strong" mode)
385
+
- **Manual Mode:** Only protected functions pay cost
386
+
- **Context Switch:** Single write to `__stack_chk_guard` (fast)
387
+
388
+
### ⚠ Limitations
389
+
390
+
**Not Defeated By:**
391
+
- Blind overwrites (canary is random)
392
+
- Partial overwrites (full 8 bytes checked)
393
+
- Thread confusion (per-thread canaries)
394
+
395
+
**Can Be Bypassed If:**
396
+
- Attacker can read canary (info leak) → Mitigated by ASLR + secret storage
397
+
- Attacker can write to arbitrary memory (skips canary check) → W^X prevents code injection
398
+
- Attacker can corrupt heap metadata (not stack) → Future: heap canaries
399
+
400
+
---
401
+
402
+
## Testing Strategy
403
+
404
+
### Unit Tests
405
+
406
+
```rust
407
+
#[cfg(test)]
408
+
mod tests {
409
+
use super::*;
410
+
411
+
#[test]
412
+
fn test_canary_generation() {
413
+
let thread1 = Thread::new(test_fn, ThreadPriority::Normal);
414
+
let thread2 = Thread::new(test_fn, ThreadPriority::Normal);
415
+
416
+
// Each thread should have unique sigil
417
+
assert_ne!(thread1.sigil, thread2.sigil);
418
+
419
+
// Sigils should be non-zero
420
+
assert_ne!(thread1.sigil, 0);
421
+
assert_ne!(thread2.sigil, 0);
422
+
}
423
+
424
+
#[test]
425
+
#[should_panic(expected = "Stack canary violation")]
426
+
fn test_canary_detection() {
427
+
// Simulate stack overflow by corrupting __stack_chk_guard
428
+
unsafe {
429
+
let original = __stack_chk_guard;
430
+
__stack_chk_guard = 0xDEADBEEF; // Corrupt!
431
+
__stack_chk_fail(); // Should panic
432
+
}
433
+
}
434
+
}
435
+
```
436
+
437
+
### Integration Tests
438
+
439
+
```rust
440
+
// In tests/stack_overflow_test.rs
441
+
442
+
#[test_case]
443
+
fn test_stack_overflow_detection() {
444
+
// Create function with buffer overflow vulnerability
445
+
#[stack_protected]
446
+
fn vulnerable_function() {
447
+
let mut buffer = [0u8; 16];
448
+
449
+
// Simulate overflow (writes past buffer end)
450
+
unsafe {
451
+
core::ptr::write_bytes(buffer.as_mut_ptr(), 0xFF, 64);
452
+
}
453
+
454
+
// Should panic before reaching here
455
+
}
456
+
457
+
// Test should catch panic
458
+
let result = panic::catch_unwind(|| vulnerable_function());
459
+
assert!(result.is_err());
460
+
}
461
+
```
462
+
463
+
---
464
+
465
+
## Implementation Phases
466
+
467
+
### Phase 1: Infrastructure (Week 1)
468
+
- [ ] Add `sigil` field to `Thread` struct
469
+
- [ ] Implement canary generation in `Thread::new()`
470
+
- [ ] Add `get_current_sigil()` function
471
+
- [ ] Write unit tests
472
+
473
+
### Phase 2: LLVM Integration (Week 1-2)
474
+
- [ ] Update `x86_64-aethelos.json` with `"stack-protector": "strong"`
475
+
- [ ] Implement `__stack_chk_fail()` handler
476
+
- [ ] Implement `__stack_chk_guard` global
477
+
- [ ] Add `init_thread_canary()` function
478
+
- [ ] Test compilation with canaries enabled
479
+
480
+
### Phase 3: Scheduler Integration (Week 2)
481
+
- [ ] Update context switch to set `__stack_chk_guard`
482
+
- [ ] Update thread creation to initialize canary
483
+
- [ ] Add boot-time canary initialization
484
+
- [ ] Test thread switching preserves canaries
485
+
486
+
### Phase 4: Validation & Monitoring (Week 2-3)
487
+
- [ ] Add Weaver's Sigil status to `wards` command
488
+
- [ ] Implement canary violation logging
489
+
- [ ] Add debug command to inspect thread sigils
490
+
- [ ] Performance benchmarks
491
+
492
+
### Phase 5: Documentation (Week 3)
493
+
- [ ] Update DESIGN.md with Weaver's Sigil section
494
+
- [ ] Update CLAUDE.md with canary guidelines
495
+
- [ ] Add security audit checklist
496
+
- [ ] Create attack simulation tests
497
+
498
+
---
499
+
500
+
## Eldarin Shell Integration
501
+
502
+
### New Command: `sigils`
503
+
504
+
Display active Weaver's Sigils for debugging:
505
+
506
+
```
507
+
◈ The Weaver's Sigils - Stack Canary Protection
508
+
509
+
Mode: LLVM Strong (all functions with buffers)
510
+
Status: ✓ ACTIVE
511
+
512
+
Active Sigils:
513
+
Thread #0 (Boot): 0xDEADBEEF_CAFEBABE
514
+
Thread #1 (Shell): 0x12345678_9ABCDEF0
515
+
Thread #2 (Worker): 0xFEDCBA98_76543210
516
+
Thread #3 (Idle): 0xABCDEF01_23456789
517
+
518
+
Violations Detected: 0 (since boot)
519
+
Last Violation: None
520
+
521
+
The sigils remain pure. The threads weave in harmony.
522
+
```
523
+
524
+
### Update `wards` Command
525
+
526
+
Add Weaver's Sigil to security status:
527
+
528
+
```rust
529
+
// In wards_command.rs
530
+
531
+
pub fn show_wards_page(page: usize) {
532
+
match page {
533
+
0 => {
534
+
// ... existing W^X, ASLR output ...
535
+
536
+
// Weaver's Sigil Status
537
+
crate::println!(" Stack Canary Protection (Weaver's Sigil): ✓ Active");
538
+
crate::println!(" Mode: LLVM Strong (all functions with buffers)");
539
+
crate::println!(" Violations detected: 0");
540
+
crate::println!();
541
+
542
+
// ... rest of page ...
543
+
}
544
+
}
545
+
}
546
+
```
547
+
548
+
---
549
+
550
+
## Future Enhancements
551
+
552
+
### Heap Canaries
553
+
554
+
Extend sigil concept to heap allocations:
555
+
556
+
```rust
557
+
pub struct HeapObject {
558
+
size: usize,
559
+
sigil_prefix: u64, // Before allocation
560
+
data: [u8],
561
+
sigil_suffix: u64, // After allocation
562
+
}
563
+
```
564
+
565
+
### Sigil Renewal
566
+
567
+
Periodically refresh canaries to limit attack windows:
568
+
569
+
```rust
570
+
pub fn renew_sigil(thread: &mut Thread) {
571
+
thread.sigil = generate_new_canary();
572
+
unsafe {
573
+
if thread.id == current_thread_id() {
574
+
init_thread_canary(thread.sigil);
575
+
}
576
+
}
577
+
}
578
+
```
579
+
580
+
### Safe Stack
581
+
582
+
Separate safety-critical stack data (return addresses, saved registers) from unsafe data (buffers):
583
+
584
+
```
585
+
┌─────────────────┐
586
+
│ Safe Stack │ ← Return addresses, canaries
587
+
│ (Protected) │
588
+
├─────────────────┤
589
+
│ Unsafe Stack │ ← Buffers, local variables
590
+
│ (Unprotected) │
591
+
└─────────────────┘
592
+
```
593
+
594
+
---
595
+
596
+
## References
597
+
598
+
- **PaX STACKLEAK:** https://pax.grsecurity.net/docs/stackleak.txt
599
+
- **GCC Stack Protector:** https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
600
+
- **LLVM SafeStack:** https://clang.llvm.org/docs/SafeStack.html
601
+
- **Microsoft Control Flow Guard:** https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
602
+
603
+
---
604
+
605
+
*"The Weaver's Sigil is not merely a guard—it is a promise. A promise that each thread's fate remains its own, untainted by the chaos of corruption."*
606
+
607
+
**Status:** 🚧 Design Complete, Implementation Pending
608
+
**Priority:** High (Security Critical)
609
+
**Estimated Effort:** 2-3 weeks
610
+
**Dependencies:** Existing entropy infrastructure (ChaCha8Rng)
+56
-54
heartwood/src/drivers/ata.rs
+56
-54
heartwood/src/drivers/ata.rs
···
60
60
/// Detect and initialize primary master drive (drive 0)
61
61
/// Based on r3 kernel's ATA driver: https://github.com/Narasimha1997/r3
62
62
pub fn detect_primary_master() -> Option<Self> {
63
-
Self::detect_drive(ATA_PRIMARY_BASE, 0)
63
+
unsafe { Self::detect_drive(ATA_PRIMARY_BASE, 0) }
64
64
}
65
65
66
66
/// Detect and initialize primary slave drive (drive 1)
67
67
pub fn detect_primary_slave() -> Option<Self> {
68
-
Self::detect_drive(ATA_PRIMARY_BASE, 1)
68
+
unsafe { Self::detect_drive(ATA_PRIMARY_BASE, 1) }
69
69
}
70
70
71
71
/// Internal function to detect a drive on a specific bus and drive number
72
-
fn detect_drive(bus: u16, drive: u8) -> Option<Self> {
73
-
74
-
unsafe {
75
-
// Serial marker: 'A' = Starting detection
76
-
Self::debug_char(b'A');
72
+
///
73
+
/// SAFETY: Performs raw I/O port access to detect and initialize ATA drives.
74
+
unsafe fn detect_drive(bus: u16, drive: u8) -> Option<Self> {
75
+
// Serial marker: 'A' = Starting detection
76
+
Self::debug_char(b'A');
77
77
78
-
// Step 0: Disable interrupts on ATA controller
79
-
// Write to Device Control Register (0x3F6): set nIEN bit (bit 1)
80
-
outb(0x3F6, 0x02); // Disable interrupts
78
+
// Step 0: Disable interrupts on ATA controller
79
+
// Write to Device Control Register (0x3F6): set nIEN bit (bit 1)
80
+
outb(0x3F6, 0x02); // Disable interrupts
81
81
82
-
// Step 1: Select drive (0xA0 = master, 0xB0 = slave)
83
-
let drive_select = 0xA0 | ((drive & 1) << 4);
84
-
outb(bus + ATA_REG_DRIVE, drive_select);
82
+
// Step 1: Select drive (0xA0 = master, 0xB0 = slave)
83
+
let drive_select = 0xA0 | ((drive & 1) << 4);
84
+
outb(bus + ATA_REG_DRIVE, drive_select);
85
85
86
86
// Step 2: Wait 400ns for drive selection to settle
87
87
for _ in 0..4 {
···
147
147
148
148
// Check for error bit
149
149
if (status & ATA_STATUS_ERR) != 0 {
150
+
let error = inb(bus + ATA_REG_ERROR);
150
151
Self::debug_char(b'!'); // Error
151
152
Self::debug_hex(status);
153
+
Self::debug_char(b'E'); // Error register
154
+
Self::debug_hex(error);
152
155
return None;
153
156
}
154
157
···
194
197
// If sector count is 0, use a default
195
198
let sectors = if sectors > 0 { sectors } else { 2048 };
196
199
197
-
// Serial marker: 'Z' = Success!
198
-
Self::debug_char(b'Z');
200
+
// Serial marker: 'Z' = Success!
201
+
Self::debug_char(b'Z');
199
202
200
-
Some(AtaDrive {
201
-
bus,
202
-
drive,
203
-
sectors,
204
-
sector_size: 512,
205
-
})
206
-
}
203
+
Some(AtaDrive {
204
+
bus,
205
+
drive,
206
+
sectors,
207
+
sector_size: 512,
208
+
})
207
209
}
208
210
209
211
/// Write a debug string to COM1 serial port
212
+
///
213
+
/// SAFETY: Must be called from unsafe context. Performs raw I/O port access.
210
214
fn debug_str(s: &[u8]) {
211
-
unsafe {
212
-
for &byte in s {
213
-
while (inb(0x3FD) & 0x20) == 0 {}
214
-
outb(0x3F8, byte);
215
-
}
215
+
for &byte in s {
216
+
while (inb(0x3FD) & 0x20) == 0 {}
217
+
outb(0x3F8, byte);
216
218
}
217
219
}
218
220
219
221
/// Write a debug character to COM1 serial port with compact prefix
222
+
///
223
+
/// SAFETY: Must be called from unsafe context. Performs raw I/O port access.
220
224
fn debug_char(c: u8) {
221
225
// Use compact format: "*X " - write directly without waiting
222
226
// to avoid potential conflicts with ATA port access
223
-
unsafe {
224
-
outb(0x3F8, b'*');
225
-
outb(0x3F8, c);
226
-
outb(0x3F8, b' ');
227
-
}
227
+
outb(0x3F8, b'*');
228
+
outb(0x3F8, c);
229
+
outb(0x3F8, b' ');
228
230
}
229
231
230
232
/// Write a byte as two hex digits to serial port
233
+
///
234
+
/// SAFETY: Must be called from unsafe context. Performs raw I/O port access.
231
235
fn debug_hex(value: u8) {
232
236
const HEX: &[u8] = b"0123456789ABCDEF";
233
-
unsafe {
234
-
let high = (value >> 4) & 0x0F;
235
-
let low = value & 0x0F;
237
+
let high = (value >> 4) & 0x0F;
238
+
let low = value & 0x0F;
236
239
237
-
// Write directly without waiting
238
-
outb(0x3F8, b'=');
239
-
outb(0x3F8, b'0');
240
-
outb(0x3F8, b'x');
241
-
outb(0x3F8, HEX[high as usize]);
242
-
outb(0x3F8, HEX[low as usize]);
243
-
}
240
+
// Write directly without waiting
241
+
outb(0x3F8, b'=');
242
+
outb(0x3F8, b'0');
243
+
outb(0x3F8, b'x');
244
+
outb(0x3F8, HEX[high as usize]);
245
+
outb(0x3F8, HEX[low as usize]);
244
246
}
245
247
246
248
/// Send IDENTIFY command to drive
···
344
346
/// Read a single sector (28-bit LBA)
345
347
/// Read a single sector using PIO mode
346
348
/// Based on r3 kernel's read_sectors_lba28()
347
-
fn read_sector_pio(&self, lba: u64) -> Result<Vec<u8>, BlockDeviceError> {
349
+
///
350
+
/// SAFETY: Performs raw I/O port access to read sectors from ATA drive.
351
+
unsafe fn read_sector_pio(&self, lba: u64) -> Result<Vec<u8>, BlockDeviceError> {
348
352
if lba >= self.sectors {
349
353
return Err(BlockDeviceError::InvalidSector);
350
354
}
351
355
352
-
unsafe {
353
-
// Wait for drive to not be busy
354
-
let mut timeout = 0;
356
+
// Wait for drive to not be busy
357
+
let mut timeout = 0;
355
358
loop {
356
359
let status = inb(self.bus + ATA_REG_STATUS);
357
360
if status & ATA_STATUS_BSY == 0 {
···
419
422
}
420
423
421
424
// Convert to Vec<u8>
422
-
let mut buffer = Vec::with_capacity(512);
423
-
for word in data.iter() {
424
-
buffer.push((word & 0xFF) as u8);
425
-
buffer.push((word >> 8) as u8);
426
-
}
425
+
let mut buffer = Vec::with_capacity(512);
426
+
for word in data.iter() {
427
+
buffer.push((word & 0xFF) as u8);
428
+
buffer.push((word >> 8) as u8);
429
+
}
427
430
428
-
Ok(buffer)
429
-
}
431
+
Ok(buffer)
430
432
}
431
433
432
434
/// Wait for drive to not be busy (with timeout)
···
466
468
}
467
469
468
470
fn read_sector(&self, sector: u64) -> Result<Vec<u8>, BlockDeviceError> {
469
-
self.read_sector_pio(sector)
471
+
unsafe { self.read_sector_pio(sector) }
470
472
}
471
473
472
474
fn read_sectors(&self, start_sector: u64, count: u32) -> Result<Vec<u8>, BlockDeviceError> {
+12
-4
heartwood/src/eldarin.rs
+12
-4
heartwood/src/eldarin.rs
···
457
457
"preempt" => cmd_preempt(args),
458
458
"uptime" => cmd_uptime(),
459
459
"wards" => cmd_wards(), // Security wards (ASLR, W^X)
460
+
"sigils" => cmd_sigils(), // Weaver's Sigils (stack canaries)
460
461
// Filesystem commands (Eldarin naming)
461
462
"reveal" => cmd_vfs_ls(args), // vfs-ls → reveal
462
463
"recite" => cmd_vfs_cat(args), // vfs-cat → recite
···
907
908
// Test 1: Check filesystem info
908
909
crate::println!();
909
910
crate::println!(" 3. Testing filesystem metadata...");
910
-
crate::println!(" Volume label: {}", fat32.bpb.volume_label);
911
-
crate::println!(" Filesystem: {}", fat32.bpb.fs_type);
911
+
crate::println!(" Volume label: {}",
912
+
core::str::from_utf8(&fat32.bpb.volume_label).unwrap_or("INVALID").trim_end());
913
+
crate::println!(" Filesystem: {}",
914
+
core::str::from_utf8(&fat32.bpb.fs_type).unwrap_or("INVALID").trim_end());
912
915
crate::println!(" Bytes per sector: {}", fat32.bpb.bytes_per_sector);
913
916
crate::println!(" Cluster size: {} bytes", fat32.bpb.cluster_size());
914
917
···
984
987
985
988
/// VFS LS - List directory contents
986
989
fn cmd_vfs_ls(args: &str) {
987
-
use crate::vfs::{FileSystem, Path};
990
+
use crate::vfs::Path;
988
991
use crate::vfs::global as vfs_global;
989
992
990
993
let path = if args.is_empty() { "/" } else { args.trim() };
···
1034
1037
1035
1038
/// VFS CAT - Display file contents
1036
1039
fn cmd_vfs_cat(args: &str) {
1037
-
use crate::vfs::{FileSystem, Path};
1040
+
use crate::vfs::Path;
1038
1041
use crate::vfs::global as vfs_global;
1039
1042
1040
1043
if args.is_empty() {
···
1106
1109
// Delegate to the paged wards command with capability testing
1107
1110
crate::wards_command::cmd_wards();
1108
1111
}
1112
+
1113
+
/// SIGILS - Display The Weaver's Sigils (stack canaries)
1114
+
fn cmd_sigils() {
1115
+
crate::sigils_command::cmd_sigils();
1116
+
}
+2
heartwood/src/lib.rs
+2
heartwood/src/lib.rs
···
44
44
pub mod rtl; // Runtime library with memcpy, etc.
45
45
pub mod eldarin; // The Eldarin Shell
46
46
pub mod wards_command; // Security wards command
47
+
pub mod sigils_command; // Weaver's Sigils command
48
+
pub mod stack_protection; // Stack canary runtime (LLVM support)
47
49
pub mod irq_safe_mutex; // Interrupt-safe mutex primitive
48
50
pub mod vfs; // Virtual File System layer
49
51
pub mod drivers; // Hardware device drivers
+8
heartwood/src/loom_of_fate/scheduler.rs
+8
heartwood/src/loom_of_fate/scheduler.rs
···
229
229
let from_ctx_ptr = &mut self.threads[from_idx].context as *mut ThreadContext;
230
230
let to_ctx_ptr = &self.threads[to_idx].context as *const ThreadContext;
231
231
232
+
// Update The Weaver's Sigil (stack canary) for the new thread
233
+
// SECURITY: This MUST happen before the context switch so that
234
+
// LLVM-generated code in the new thread uses the correct canary
235
+
let next_sigil = self.threads[to_idx].sigil;
236
+
unsafe {
237
+
crate::stack_protection::set_current_canary(next_sigil);
238
+
}
239
+
232
240
// Update current thread ID
233
241
self.current_thread = Some(next_id);
234
242
self.context_switches += 1;
+26
heartwood/src/loom_of_fate/thread.rs
+26
heartwood/src/loom_of_fate/thread.rs
···
51
51
#[allow(dead_code)]
52
52
pub(crate) stack_top: u64,
53
53
54
+
/// The Weaver's Sigil - unique per-thread stack canary
55
+
/// This secret value protects against stack buffer overflows
56
+
/// It should NEVER be exposed to userspace
57
+
pub(crate) sigil: u64,
58
+
54
59
// Harmony tracking
55
60
pub(crate) resource_usage: ResourceUsage,
56
61
pub(crate) harmony_score: f32,
···
80
85
// Create initial context for this thread
81
86
let context = ThreadContext::new(entry_point as u64, stack_top);
82
87
88
+
// Generate unique Weaver's Sigil (stack canary) for this thread
89
+
let sigil = Self::generate_sigil();
90
+
83
91
// DEBUG: Verify context was created correctly
84
92
crate::println!(" Thread::new - id={}, entry={:#x}, stack_top={:#x}, context.rsp={:#x}",
85
93
id.0, entry_point as u64, stack_top, context.rsp);
94
+
crate::println!(" Weaver's Sigil: 0x{:016x}", sigil);
86
95
87
96
Self {
88
97
id,
···
92
101
context,
93
102
stack_bottom,
94
103
stack_top,
104
+
sigil,
95
105
resource_usage: ResourceUsage::default(),
96
106
harmony_score: 1.0, // Start in perfect harmony
97
107
time_slices_used: 0,
98
108
yields: 0,
99
109
last_run_time: 0,
100
110
}
111
+
}
112
+
113
+
/// Generate a unique Weaver's Sigil (stack canary) for this thread
114
+
///
115
+
/// Uses ChaCha8 RNG seeded from hardware (RDTSC) to generate
116
+
/// a cryptographically strong 64-bit random value.
117
+
///
118
+
/// # Security
119
+
/// This value MUST remain secret and never be exposed to userspace.
120
+
fn generate_sigil() -> u64 {
121
+
use crate::mana_pool::entropy::ChaCha8Rng;
122
+
123
+
let mut rng = ChaCha8Rng::from_hardware_fast();
124
+
let high = rng.next_u32() as u64;
125
+
let low = rng.next_u32() as u64;
126
+
(high << 32) | low
101
127
}
102
128
103
129
/// Get a mutable reference to the thread's context
+19
heartwood/src/main.rs
+19
heartwood/src/main.rs
···
193
193
unsafe { mana_pool::sealing::init(); }
194
194
println!(" ✓ Capability sealing ready (HMAC-SHA256)");
195
195
196
+
// Initialize The Weaver's Sigil (stack canary protection)
197
+
println!("◈ Weaving the protective sigils...");
198
+
unsafe {
199
+
// Set initial canary for boot thread
200
+
// This will be updated per-thread by the scheduler during context switches
201
+
use mana_pool::entropy::ChaCha8Rng;
202
+
let mut rng = ChaCha8Rng::from_hardware_fast();
203
+
let boot_sigil = ((rng.next_u32() as u64) << 32) | (rng.next_u32() as u64);
204
+
heartwood::stack_protection::set_current_canary(boot_sigil);
205
+
}
206
+
println!(" ✓ The Weaver's Sigil active (stack canary: LLVM strong mode)");
207
+
println!(" Protecting all functions with buffers or address-taken locals");
208
+
196
209
// Initialize the Nexus (IPC)
197
210
unsafe { serial_out(b'E'); }
198
211
println!("◈ Opening the Nexus...");
···
206
219
loom_of_fate::init();
207
220
unsafe { serial_out(b'H'); }
208
221
println!(" ✓ Loom ready");
222
+
223
+
// Initialize heap canaries (after thread creation to avoid early boot issues)
224
+
unsafe {
225
+
mana_pool::heap_canaries::init();
226
+
}
227
+
println!(" ✓ Heap canaries active (pre/post allocation protection)");
209
228
210
229
// Initialize the Attunement Layer
211
230
unsafe { serial_out(b'I'); }
+63
-8
heartwood/src/mana_pool/buddy.rs
+63
-8
heartwood/src/mana_pool/buddy.rs
···
120
120
121
121
/// Allocate a block of at least `size` bytes
122
122
///
123
-
/// Returns the address of the allocated block, or None if out of memory
123
+
/// Returns the address of the allocated block (user data, after pre-canary),
124
+
/// or None if out of memory.
125
+
///
126
+
/// Memory layout:
127
+
/// ```
128
+
/// [PRE_CANARY (8B)] [USER DATA (size)] [POST_CANARY (8B)]
129
+
/// ^ ^
130
+
/// block address returned address
131
+
/// ```
124
132
pub fn allocate(&mut self, size: usize) -> Option<usize> {
125
133
if size == 0 {
126
134
return None;
127
135
}
128
136
137
+
// Check if heap canaries are enabled
138
+
let canaries_enabled = super::heap_canaries::are_enabled();
139
+
140
+
// Add space for heap canaries only if enabled
141
+
let total_size = if canaries_enabled {
142
+
size + super::heap_canaries::TOTAL_CANARY_OVERHEAD
143
+
} else {
144
+
size
145
+
};
146
+
129
147
// Find the order needed for this size
130
-
let needed_order = self.size_to_order(size);
148
+
let needed_order = self.size_to_order(total_size);
131
149
if needed_order > MAX_ORDER {
132
150
return None; // Allocation too large
133
151
}
···
141
159
// Split block if it's larger than needed
142
160
self.split_block(block, alloc_order, needed_order);
143
161
144
-
Some(block.as_ptr() as usize)
162
+
let block_addr = block.as_ptr() as usize;
163
+
164
+
// Write heap canaries around the allocation (only if enabled)
165
+
if canaries_enabled {
166
+
unsafe {
167
+
super::heap_canaries::write_canaries(block_addr, size);
168
+
}
169
+
// Return pointer to user data (after pre-canary)
170
+
Some(block_addr + super::heap_canaries::CANARY_SIZE)
171
+
} else {
172
+
// Return block address directly (no canary offset)
173
+
Some(block_addr)
174
+
}
145
175
}
146
176
147
177
/// Deallocate a block
···
149
179
/// # Safety
150
180
///
151
181
/// The caller must ensure:
152
-
/// - `addr` was returned by a previous call to `allocate`
153
-
/// - `size` matches the size passed to `allocate`
182
+
/// - `addr` was returned by a previous call to `allocate` (points to user data)
183
+
/// - `size` matches the size passed to `allocate` (user data size, not including canaries)
154
184
/// - The block has not already been deallocated
155
185
pub unsafe fn deallocate(&mut self, addr: usize, size: usize) {
156
186
if size == 0 {
157
187
return;
158
188
}
159
189
160
-
let order = self.size_to_order(size);
190
+
// Check if heap canaries are enabled
191
+
let canaries_enabled = super::heap_canaries::are_enabled();
192
+
193
+
let (block_addr, total_size) = if canaries_enabled {
194
+
// Calculate the actual block address (before pre-canary)
195
+
let blk_addr = addr - super::heap_canaries::CANARY_SIZE;
196
+
197
+
// Verify heap canaries before deallocation
198
+
if !super::heap_canaries::verify_canaries(blk_addr, size) {
199
+
panic!("◈ HEAP CORRUPTION: Canary violation detected during deallocation!\n\
200
+
\n\
201
+
Address: 0x{:016x}\n\
202
+
Size: {} bytes\n\
203
+
\n\
204
+
The Weaver's Sigil has detected heap buffer overflow.\n\
205
+
This allocation's protective canaries were corrupted.", addr, size);
206
+
}
207
+
208
+
// Calculate total size including canaries
209
+
(blk_addr, size + super::heap_canaries::TOTAL_CANARY_OVERHEAD)
210
+
} else {
211
+
// No canaries - addr is the block address, size is actual size
212
+
(addr, size)
213
+
};
214
+
215
+
let order = self.size_to_order(total_size);
161
216
if order > MAX_ORDER {
162
217
return;
163
218
}
164
219
165
-
// Create a block at this address
166
-
let block = Block::new(addr);
220
+
// Create a block at the actual block address
221
+
let block = Block::new(block_addr);
167
222
168
223
// Try to coalesce with buddy
169
224
self.coalesce_and_free(block, order);
+138
heartwood/src/mana_pool/heap_canaries.rs
+138
heartwood/src/mana_pool/heap_canaries.rs
···
1
+
//! Heap Canaries - The Weaver's Sigil for Heap Protection
2
+
//!
3
+
//! Protects heap allocations from buffer overflows by placing canary values
4
+
//! before and after each allocation. If a buffer overflow occurs, it will
5
+
//! corrupt a canary, which is detected when the memory is freed.
6
+
//!
7
+
//! ## Philosophy
8
+
//! Just as threads are protected by the Weaver's Sigil on the stack,
9
+
//! heap allocations are guarded by sigils woven into the fabric of memory.
10
+
//! Each allocation is wrapped in protective marks that reveal corruption.
11
+
12
+
use crate::mana_pool::entropy::ChaCha8Rng;
13
+
use core::sync::atomic::{AtomicU64, Ordering};
14
+
15
+
/// Size of each canary (8 bytes)
16
+
pub const CANARY_SIZE: usize = 8;
17
+
18
+
/// Total overhead per allocation (pre-canary + post-canary)
19
+
pub const TOTAL_CANARY_OVERHEAD: usize = CANARY_SIZE * 2;
20
+
21
+
/// Global canary secret (XORed with allocation address for uniqueness)
22
+
static CANARY_SECRET: AtomicU64 = AtomicU64::new(0);
23
+
24
+
/// Statistics for heap canary violations
25
+
static VIOLATIONS_DETECTED: AtomicU64 = AtomicU64::new(0);
26
+
27
+
/// Enable/disable heap canary checking
28
+
static CANARIES_ENABLED: AtomicU64 = AtomicU64::new(0);
29
+
30
+
/// Check if heap canaries are currently enabled
31
+
#[inline(always)]
32
+
pub fn are_enabled() -> bool {
33
+
CANARIES_ENABLED.load(Ordering::Acquire) != 0
34
+
}
35
+
36
+
/// Initialize heap canary system
37
+
///
38
+
/// # Safety
39
+
/// Must be called exactly once during kernel initialization
40
+
pub unsafe fn init() {
41
+
// Generate a random canary secret
42
+
let mut rng = ChaCha8Rng::from_hardware_fast();
43
+
let secret = ((rng.next_u32() as u64) << 32) | (rng.next_u32() as u64);
44
+
CANARY_SECRET.store(secret, Ordering::Relaxed);
45
+
46
+
// Enable heap canaries
47
+
CANARIES_ENABLED.store(1, Ordering::Release);
48
+
}
49
+
50
+
/// Generate a canary value for a specific allocation address
51
+
///
52
+
/// The canary is unique per-allocation by XORing the secret with the address.
53
+
/// This prevents an attacker from learning the canary from one allocation
54
+
/// and using it to bypass protection in another.
55
+
#[inline(always)]
56
+
pub fn generate_canary(addr: usize) -> u64 {
57
+
let secret = CANARY_SECRET.load(Ordering::Relaxed);
58
+
secret ^ (addr as u64)
59
+
}
60
+
61
+
/// Write canaries around an allocation
62
+
///
63
+
/// Memory layout:
64
+
/// ```
65
+
/// [PRE_CANARY][USER DATA][POST_CANARY]
66
+
/// 8 bytes size bytes 8 bytes
67
+
/// ```
68
+
///
69
+
/// # Safety
70
+
/// - `addr` must point to valid memory with at least `size + TOTAL_CANARY_OVERHEAD` bytes
71
+
/// - Memory must be writable
72
+
pub unsafe fn write_canaries(addr: usize, size: usize) {
73
+
let canary = generate_canary(addr);
74
+
75
+
// Write pre-canary before user data
76
+
let pre_canary_ptr = addr as *mut u64;
77
+
core::ptr::write_volatile(pre_canary_ptr, canary);
78
+
79
+
// Write post-canary after user data
80
+
let post_canary_ptr = (addr + CANARY_SIZE + size) as *mut u64;
81
+
core::ptr::write_volatile(post_canary_ptr, canary);
82
+
}
83
+
84
+
/// Verify canaries and detect heap corruption
85
+
///
86
+
/// Returns true if canaries are intact, false if corrupted
87
+
///
88
+
/// # Safety
89
+
/// - `addr` must point to the start of an allocation (including pre-canary)
90
+
/// - Memory must be readable
91
+
pub unsafe fn verify_canaries(addr: usize, size: usize) -> bool {
92
+
// Skip verification if canaries not enabled yet (during early boot)
93
+
if CANARIES_ENABLED.load(Ordering::Acquire) == 0 {
94
+
return true;
95
+
}
96
+
97
+
let expected_canary = generate_canary(addr);
98
+
99
+
// Check pre-canary
100
+
let pre_canary_ptr = addr as *const u64;
101
+
let pre_canary = core::ptr::read_volatile(pre_canary_ptr);
102
+
103
+
// Check post-canary
104
+
let post_canary_ptr = (addr + CANARY_SIZE + size) as *const u64;
105
+
let post_canary = core::ptr::read_volatile(post_canary_ptr);
106
+
107
+
if pre_canary != expected_canary {
108
+
log_violation(addr, size, "PRE-CANARY", expected_canary, pre_canary);
109
+
return false;
110
+
}
111
+
112
+
if post_canary != expected_canary {
113
+
log_violation(addr, size, "POST-CANARY", expected_canary, post_canary);
114
+
return false;
115
+
}
116
+
117
+
true
118
+
}
119
+
120
+
/// Log a canary violation
121
+
unsafe fn log_violation(_addr: usize, _size: usize, _location: &str, _expected: u64, _found: u64) {
122
+
// Track violation count
123
+
// Detailed logging is done in the panic message when deallocation fails
124
+
VIOLATIONS_DETECTED.fetch_add(1, Ordering::Relaxed);
125
+
}
126
+
127
+
/// Get the number of heap canary violations detected since boot
128
+
pub fn violations_count() -> u64 {
129
+
VIOLATIONS_DETECTED.load(Ordering::Relaxed)
130
+
}
131
+
132
+
/// Get the current canary secret (for debugging only)
133
+
///
134
+
/// # Security Warning
135
+
/// This should NEVER be exposed to userspace!
136
+
pub fn get_canary_secret() -> u64 {
137
+
CANARY_SECRET.load(Ordering::Relaxed)
138
+
}
+1
heartwood/src/mana_pool/mod.rs
+1
heartwood/src/mana_pool/mod.rs
···
26
26
pub mod entropy; // Random number generation for ASLR
27
27
pub mod aslr; // Address Space Layout Randomization
28
28
pub mod sealing; // Cryptographic capability sealing
29
+
pub mod heap_canaries; // Heap buffer overflow protection
29
30
30
31
pub use object_manager::{ObjectManager, ObjectHandle, ObjectType, ObjectInfo};
31
32
pub use capability::{Capability, CapabilityRights, CapabilityId, SealedCapability};
+1
-1
heartwood/src/mana_pool/sealing.rs
+1
-1
heartwood/src/mana_pool/sealing.rs
···
11
11
///! - **Kernel-Only**: Secret key never leaves kernel space
12
12
13
13
use core::mem::MaybeUninit;
14
-
use crate::mana_pool::entropy::{HardwareRng, ChaCha8Rng};
14
+
use crate::mana_pool::entropy::ChaCha8Rng;
15
15
16
16
/// Capability sealer using HMAC-SHA256
17
17
pub struct CapabilitySealer {
+105
heartwood/src/sigils_command.rs
+105
heartwood/src/sigils_command.rs
···
1
+
/// SIGILS - Display The Weaver's Sigils (stack canaries) for all threads
2
+
///
3
+
/// This command shows the unique stack canary values protecting each thread.
4
+
/// Each sigil is a 64-bit cryptographically random value that guards against
5
+
/// buffer overflow attacks.
6
+
7
+
pub fn cmd_sigils() {
8
+
use crate::loom_of_fate::{without_interrupts, ThreadState};
9
+
use alloc::vec::Vec;
10
+
11
+
crate::println!("◈ The Weaver's Sigils - Stack Canary Protection");
12
+
crate::println!();
13
+
14
+
crate::println!(" Status: ✓ ACTIVE (Phase 2: LLVM stack protection)");
15
+
crate::println!(" Mode: LLVM strong mode + per-thread canaries");
16
+
crate::println!(" Protection: All functions with buffers or address-taken locals");
17
+
crate::println!();
18
+
19
+
// Show current global canary value
20
+
let current_canary = crate::stack_protection::get_current_canary();
21
+
crate::println!(" Current __stack_chk_guard: 0x{:016x}", current_canary);
22
+
crate::println!();
23
+
24
+
// Get thread information
25
+
without_interrupts(|| {
26
+
unsafe {
27
+
let loom = crate::loom_of_fate::get_loom().lock();
28
+
29
+
// Collect threads (not fading)
30
+
let threads: Vec<_> = loom.threads.iter()
31
+
.filter(|t| !matches!(t.state, ThreadState::Fading))
32
+
.collect();
33
+
34
+
crate::println!(" Active Sigils ({} threads):", threads.len());
35
+
crate::println!();
36
+
37
+
for thread in threads {
38
+
let state_str = match thread.state {
39
+
ThreadState::Weaving => "Weaving",
40
+
ThreadState::Resting => "Resting",
41
+
ThreadState::Tangled => "Tangled",
42
+
ThreadState::Fading => "Fading",
43
+
};
44
+
45
+
let priority_str = match thread.priority {
46
+
crate::loom_of_fate::ThreadPriority::Critical => "Critical",
47
+
crate::loom_of_fate::ThreadPriority::High => "High",
48
+
crate::loom_of_fate::ThreadPriority::Normal => "Normal",
49
+
crate::loom_of_fate::ThreadPriority::Low => "Low",
50
+
crate::loom_of_fate::ThreadPriority::Idle => "Idle",
51
+
};
52
+
53
+
crate::println!(" Thread #{} [{}|{}]",
54
+
thread.id.0, state_str, priority_str);
55
+
crate::println!(" Sigil: 0x{:016x}", thread.sigil);
56
+
crate::println!();
57
+
}
58
+
59
+
// Verify uniqueness
60
+
let mut sigils: Vec<u64> = loom.threads.iter()
61
+
.filter(|t| !matches!(t.state, ThreadState::Fading))
62
+
.map(|t| t.sigil)
63
+
.collect();
64
+
65
+
let original_len = sigils.len();
66
+
sigils.sort_unstable();
67
+
sigils.dedup();
68
+
let unique_len = sigils.len();
69
+
70
+
if original_len == unique_len {
71
+
crate::println!(" ✓ All sigils are unique ({} distinct values)", unique_len);
72
+
} else {
73
+
crate::println!(" ✗ WARNING: {} duplicate sigils detected!",
74
+
original_len - unique_len);
75
+
}
76
+
77
+
crate::println!();
78
+
crate::println!(" Protection Status:");
79
+
crate::println!(" [✓] Per-thread storage - Active");
80
+
crate::println!(" [✓] Cryptographic generation - Active");
81
+
crate::println!(" [✓] Stack canary placement - Active (LLVM inserts at function entry)");
82
+
crate::println!(" [✓] Overflow detection - Active (LLVM checks before return)");
83
+
crate::println!();
84
+
crate::println!(" Phase 2 Complete: LLVM stack-protector (strong mode) is active.");
85
+
crate::println!(" All functions with buffers or address-taken locals are protected.");
86
+
}
87
+
});
88
+
89
+
// Heap Canary Status
90
+
crate::println!();
91
+
crate::println!(" Heap Protection (Buffer Overflow Detection):");
92
+
crate::println!(" [✓] Pre-allocation canaries - Active (8 bytes before each allocation)");
93
+
crate::println!(" [✓] Post-allocation canaries - Active (8 bytes after each allocation)");
94
+
95
+
let violations = crate::mana_pool::heap_canaries::violations_count();
96
+
if violations == 0 {
97
+
crate::println!(" [✓] Violations detected - None (heap integrity maintained)");
98
+
} else {
99
+
crate::println!(" [✗] Violations detected - {} (HEAP CORRUPTION!)", violations);
100
+
}
101
+
102
+
crate::println!();
103
+
crate::println!("The sigils remain pure and distinct.");
104
+
crate::println!("Stack and heap are guarded by the Weaver's protective marks.");
105
+
}
+162
heartwood/src/stack_protection.rs
+162
heartwood/src/stack_protection.rs
···
1
+
//! Stack Protection Runtime Support
2
+
//!
3
+
//! This module provides the runtime support required by LLVM's stack protector.
4
+
//! When `-fstack-protector` is enabled, LLVM generates code that:
5
+
//! 1. Reads from `__stack_chk_guard` at function entry
6
+
//! 2. Places the value on the stack (the canary)
7
+
//! 3. Checks the value before function return
8
+
//! 4. Calls `__stack_chk_fail()` if the canary was corrupted
9
+
10
+
use core::sync::atomic::{AtomicU64, Ordering};
11
+
12
+
/// Global stack canary value (thread-local via context switch)
13
+
///
14
+
/// LLVM-generated code reads this value at function entry and checks it
15
+
/// at function exit. We update this value on every context switch to use
16
+
/// the current thread's unique Weaver's Sigil.
17
+
///
18
+
/// # Security
19
+
/// This value changes per-thread, making it harder for attackers to
20
+
/// predict the canary. However, an attacker with arbitrary read can
21
+
/// still extract it, which is why we combine this with ASLR and other
22
+
/// defenses.
23
+
#[no_mangle]
24
+
pub static __stack_chk_guard: AtomicU64 = AtomicU64::new(0xDEADBEEF_CAFEBABE);
25
+
26
+
/// Stack canary failure handler
27
+
///
28
+
/// This function is called by LLVM-generated code when a stack canary
29
+
/// check fails, indicating that a buffer overflow has corrupted the stack.
30
+
///
31
+
/// # Behavior
32
+
/// This function NEVER returns. It logs diagnostic information and then
33
+
/// panics to prevent the corrupted thread from continuing execution.
34
+
///
35
+
/// # Safety
36
+
/// This function is called by compiler-generated code in a potentially
37
+
/// corrupted context. We must not trust any stack-allocated data and
38
+
/// should minimize operations that might use corrupted state.
39
+
#[no_mangle]
40
+
pub extern "C" fn __stack_chk_fail() -> ! {
41
+
// Log the violation (use serial port directly to avoid stack operations)
42
+
unsafe {
43
+
log_canary_violation();
44
+
}
45
+
46
+
// Panic with a clear message
47
+
panic!("◈ STACK CANARY VIOLATION: The Weaver's Sigil has been corrupted!\n\
48
+
\n\
49
+
A buffer overflow has been detected. The thread's stack canary\n\
50
+
was overwritten, indicating memory corruption. Execution cannot\n\
51
+
continue safely.\n\
52
+
\n\
53
+
This protection prevented the overflow from hijacking control flow.\n\
54
+
The Weaver's Sigil stands vigilant.");
55
+
}
56
+
57
+
/// Log diagnostic information about the canary violation
58
+
///
59
+
/// This function is called from `__stack_chk_fail()` to provide debugging
60
+
/// information about which thread detected the corruption.
61
+
///
62
+
/// # Safety
63
+
/// Must be called with interrupts disabled to avoid corruption of diagnostic
64
+
/// output. Avoids using the stack as much as possible.
65
+
unsafe fn log_canary_violation() {
66
+
// Print to VGA (safer than serial which might use stack)
67
+
crate::println!("\n╔════════════════════════════════════════════════════════╗");
68
+
crate::println!("║ ⚠ STACK CANARY VIOLATION DETECTED ⚠ ║");
69
+
crate::println!("╚════════════════════════════════════════════════════════╝");
70
+
crate::println!();
71
+
crate::println!(" The Weaver's Sigil has been corrupted!");
72
+
crate::println!(" A buffer overflow has overwritten the stack canary.");
73
+
crate::println!();
74
+
75
+
// Try to get current thread info (might fail if stack is corrupted)
76
+
// Note: We can't use catch_unwind in no_std, so if this fails, we'll panic anyway
77
+
if let Some(thread_id) = crate::loom_of_fate::current_thread() {
78
+
crate::println!(" Thread ID: {}", thread_id.0);
79
+
} else {
80
+
crate::println!(" Thread ID: <unable to determine>");
81
+
}
82
+
83
+
crate::println!();
84
+
crate::println!(" Expected canary: 0x{:016x}", __stack_chk_guard.load(Ordering::Relaxed));
85
+
crate::println!(" Actual canary: <corrupted>");
86
+
crate::println!();
87
+
crate::println!(" This overflow was BLOCKED by The Weaver's Sigil.");
88
+
crate::println!(" Execution halted before control flow hijacking.");
89
+
crate::println!();
90
+
91
+
// Increment violation counter (if we have one in the future)
92
+
// CANARY_VIOLATIONS.fetch_add(1, Ordering::Relaxed);
93
+
}
94
+
95
+
/// Initialize the stack canary for a thread
96
+
///
97
+
/// This function should be called during context switch to update
98
+
/// `__stack_chk_guard` with the current thread's unique sigil.
99
+
///
100
+
/// # Arguments
101
+
/// * `sigil` - The thread's unique Weaver's Sigil (64-bit random value)
102
+
///
103
+
/// # Safety
104
+
/// This function must be called during context switch, with interrupts
105
+
/// disabled, to avoid race conditions where LLVM-generated code reads
106
+
/// a canary from one thread while executing another thread's code.
107
+
#[inline(always)]
108
+
pub unsafe fn set_current_canary(sigil: u64) {
109
+
__stack_chk_guard.store(sigil, Ordering::Relaxed);
110
+
}
111
+
112
+
/// Get the current canary value
113
+
///
114
+
/// This is primarily for debugging and testing. Production code should
115
+
/// not need to read this value directly (LLVM-generated code handles it).
116
+
///
117
+
/// # Returns
118
+
/// The current value of `__stack_chk_guard`
119
+
#[inline(always)]
120
+
pub fn get_current_canary() -> u64 {
121
+
__stack_chk_guard.load(Ordering::Relaxed)
122
+
}
123
+
124
+
/// Test helper: Simulate a stack canary violation
125
+
///
126
+
/// This function is used in tests to verify that canary checking works.
127
+
/// DO NOT call this in production code!
128
+
#[cfg(test)]
129
+
pub fn simulate_violation() -> ! {
130
+
__stack_chk_fail()
131
+
}
132
+
133
+
#[cfg(test)]
134
+
mod tests {
135
+
use super::*;
136
+
137
+
#[test]
138
+
fn test_canary_get_set() {
139
+
unsafe {
140
+
set_current_canary(0x1234567890ABCDEF);
141
+
}
142
+
assert_eq!(get_current_canary(), 0x1234567890ABCDEF);
143
+
144
+
unsafe {
145
+
set_current_canary(0xFEDCBA0987654321);
146
+
}
147
+
assert_eq!(get_current_canary(), 0xFEDCBA0987654321);
148
+
}
149
+
150
+
#[test]
151
+
fn test_canary_default() {
152
+
// Default value should be non-zero (our placeholder)
153
+
let default = get_current_canary();
154
+
assert_ne!(default, 0, "Canary should have non-zero default");
155
+
}
156
+
157
+
#[test]
158
+
#[should_panic(expected = "STACK CANARY VIOLATION")]
159
+
fn test_stack_chk_fail_panics() {
160
+
simulate_violation();
161
+
}
162
+
}
+20
-14
heartwood/src/vfs/fat32/bpb.rs
+20
-14
heartwood/src/vfs/fat32/bpb.rs
···
102
102
pub fsinfo_sector: u16,
103
103
/// Backup boot sector location (usually 6, 0xFFFF if not present)
104
104
pub backup_boot_sector: u16,
105
-
/// Volume label (11 characters, space-padded)
106
-
pub volume_label: String,
107
-
/// Filesystem type string (should be "FAT32 ")
108
-
pub fs_type: String,
105
+
/// Volume label (11 bytes, space-padded)
106
+
pub volume_label: [u8; 11],
107
+
/// Filesystem type string (8 bytes, should be "FAT32 ")
108
+
pub fs_type: [u8; 8],
109
109
/// Cached FSInfo data (if available)
110
110
pub fsinfo: Option<FSInfo>,
111
111
}
···
183
183
// Backup boot sector at offset 50 (usually 6)
184
184
let backup_boot_sector = u16::from_le_bytes([boot_sector[50], boot_sector[51]]);
185
185
186
-
// Volume label at offset 71 (11 bytes)
187
-
let volume_label = core::str::from_utf8(&boot_sector[71..82])
188
-
.unwrap_or("INVALID ")
189
-
.trim_end()
190
-
.to_string();
186
+
// Volume label at offset 71 (11 bytes) - copy directly, no String allocation
187
+
let mut volume_label = [0u8; 11];
188
+
volume_label.copy_from_slice(&boot_sector[71..82]);
191
189
192
190
// Filesystem type at offset 82 (8 bytes, should be "FAT32 ")
193
-
let fs_type = core::str::from_utf8(&boot_sector[82..90])
194
-
.unwrap_or("UNKNOWN ")
195
-
.trim_end()
196
-
.to_string();
191
+
let mut fs_type = [0u8; 8];
192
+
fs_type.copy_from_slice(&boot_sector[82..90]);
197
193
198
194
// Validate it's actually FAT32
199
-
if !fs_type.starts_with("FAT32") {
195
+
if !fs_type.starts_with(b"FAT32") {
200
196
return Err("Filesystem type is not FAT32");
201
197
}
202
198
···
285
281
/// 2. If that fails, try the backup boot sector (if specified)
286
282
/// 3. Parse FSInfo (if present and valid)
287
283
pub fn from_device(device: &dyn BlockDevice) -> Result<Self, &'static str> {
284
+
// Debug: entering from_device
285
+
unsafe {
286
+
core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'A', options(nomem, nostack));
287
+
}
288
+
288
289
// Try primary boot sector first
289
290
let boot_sector_result = device.read_sector(0);
291
+
292
+
// Debug: read_sector returned
293
+
unsafe {
294
+
core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'B', options(nomem, nostack));
295
+
}
290
296
291
297
let (boot_sector, used_backup) = match boot_sector_result {
292
298
Ok(data) => {
+10
heartwood/src/vfs/fat32/mod.rs
+10
heartwood/src/vfs/fat32/mod.rs
···
57
57
/// * `Ok(Fat32)` - Successfully mounted FAT32 filesystem
58
58
/// * `Err(FsError)` - Invalid or corrupted filesystem
59
59
pub fn new(device: Box<dyn BlockDevice>) -> Result<Self, FsError> {
60
+
// Debug: entering Fat32::new
61
+
unsafe {
62
+
core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'1', options(nomem, nostack));
63
+
}
64
+
60
65
let bpb = Fat32Bpb::from_device(&*device)
61
66
.map_err(|_| FsError::IoError)?;
67
+
68
+
// Debug: BPB parsed successfully
69
+
unsafe {
70
+
core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'2', options(nomem, nostack));
71
+
}
62
72
63
73
Ok(Self { device, bpb })
64
74
}
+2
-4
heartwood/src/wards_command.rs
+2
-4
heartwood/src/wards_command.rs
···
155
155
crate::println!(" ✓ Derived capability ID: {}", derived_id.raw());
156
156
157
157
// Store for page 2
158
-
unsafe {
159
-
STORED_CAP_ID = Some(cap_id);
160
-
STORED_DERIVED_ID = Some(derived_id);
161
-
}
158
+
STORED_CAP_ID = Some(cap_id);
159
+
STORED_DERIVED_ID = Some(derived_id);
162
160
}
163
161
Err(e) => {
164
162
crate::println!(" ✗ Derivation failed: {:?}", e);
+2
-1
heartwood/x86_64-aethelos.json
+2
-1
heartwood/x86_64-aethelos.json
isodir/boot/aethelos/heartwood.bin
isodir/boot/aethelos/heartwood.bin
This is a binary file and will not be displayed.