summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-02 23:13:46 +0200
committersalmonik <salmonik@tuta.io>2026-08-02 23:13:46 +0200
commitc10eb2333f277f815234f9fc26a5441e68ef7b40 (patch)
tree9bff5b38a21c7edfa997a25a0ee51cb43c570a68
parentv0.5.7: guard report 4-words-per-line — 16 stack words survive band hand-copy (diff)
downloadrs-dos-c10eb2333f277f815234f9fc26a5441e68ef7b40.tar.gz
rs-dos-c10eb2333f277f815234f9fc26a5441e68ef7b40.zip
v0.5.7: guard reports the drop's size/align — identifies the clobbered struct
The OptiPlex crashes free garbage (0xaaaaaa, 0xaaaaaa00aaaaaa) whose value is the firmware's 0x00AAAAAA scrub pattern left in stack/heap. The drop size/align (the dealloc layout) now prints on the band, so the next repro shows both the garbage ptr AND the size it was freed with — cap×elem_size pinpoints which struct's fields were clobbered.
-rw-r--r--rsdos/src/main.rs25
-rw-r--r--rsnix/src/memory.rs91
2 files changed, 102 insertions, 14 deletions
diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs
index 806f150..52b37ec 100644
--- a/rsdos/src/main.rs
+++ b/rsdos/src/main.rs
@@ -352,28 +352,37 @@ pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut
352 // visible on QEMU (serial + gdb available) instead of only on the 352 // visible on QEMU (serial + gdb available) instead of only on the
353 // OptiPlex. 353 // OptiPlex.
354 stack_fill_aa(); 354 stack_fill_aa();
355 // Poison the free heap AND the free pages with the OptiPlex's
356 // 0x00AAAAAA scrub pattern — the firmware's leftover fills both the
357 // heap (freed pool) and the pages the allocators hand out (heap
358 // growth, task stacks). Together with the stack fill this makes
359 // any uninitialized read reproduce the real machine's crashes in
360 // QEMU (garbage sizes → alloc panics, garbage ptrs → dealloc guard).
361 rsnix::memory::poison_free_blocks_aa();
362 rsnix::memory::poison_free_pages_aa();
355 shell::run(); 363 shell::run();
356} 364}
357 365
358/// Write 0xAA over the stack region the shell's frames will reuse, 366/// Write the OptiPlex's 0x00AAAAAA scrub pattern over the stack region
359/// leaving the current frames (top 4 KiB) intact. 128 KiB covers the 367/// the shell's frames will reuse, leaving only 256 B of live frame
360/// whole shell frame chain on QEMU and the OptiPlex. Best-effort: the 368/// headroom. 128 KiB covers the whole shell frame chain on QEMU and
361/// region is below rsp inside the boot stack mapping. 369/// the OptiPlex. Best-effort: the region is below rsp inside the boot
370/// stack mapping; the call into `shell::run` overwrites the top word.
362fn stack_fill_aa() { 371fn stack_fill_aa() {
363 let rsp: usize; 372 let rsp: usize;
364 unsafe { 373 unsafe {
365 core::arch::asm!("mov {}, rsp", out(reg) rsp); 374 core::arch::asm!("mov {}, rsp", out(reg) rsp);
366 } 375 }
367 let top = rsp - 0x1000; 376 let top = rsp - 0x100;
368 let bottom = rsp - 0x21000; 377 let bottom = rsp - 0x21100;
369 unsafe { 378 unsafe {
370 let mut p = bottom; 379 let mut p = bottom;
371 while p < top { 380 while p < top {
372 core::ptr::write_volatile(p as *mut u64, 0xAAAAAAAAAAAAAAAA); 381 core::ptr::write_volatile(p as *mut u64, 0x00AAAAAA00AAAAAA);
373 p += 8; 382 p += 8;
374 } 383 }
375 } 384 }
376 rsnix::serial_println!("stack: 0xAA-fill 0x{:x}..0x{:x}", bottom, top); 385 rsnix::serial_println!("stack: 0x00AAAAAA-fill 0x{:x}..0x{:x}", bottom, top);
377} 386}
378 387
379// --------------------------------------------------------------------------- 388// ---------------------------------------------------------------------------
diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs
index ecaf806..e0f0142 100644
--- a/rsnix/src/memory.rs
+++ b/rsnix/src/memory.rs
@@ -71,6 +71,72 @@ struct FreeLinks {
71/// Head of the address-sorted free list (lowest address first). 71/// Head of the address-sorted free list (lowest address first).
72static mut FREE_LIST: *mut Header = ptr::null_mut(); 72static mut FREE_LIST: *mut Header = ptr::null_mut();
73 73
74/// TEST HOOK: fill every free block's payload with 0xAA (header + links
75/// preserved). Mirrors the OptiPlex, whose heap pages carry 0xAA
76/// firmware leftover — any code that reads uninitialized heap memory
77/// then sees the same pattern the real machine shows in its crashes
78/// (ptr=0xaaaaaa…, allocation-size panics, zeroed block headers).
79pub fn poison_free_blocks_aa() {
80 unsafe {
81 let mut b = FREE_LIST;
82 let mut blocks = 0u32;
83 let mut bytes = 0u64;
84 while !b.is_null() {
85 let size = (*b).size;
86 let off = HEADER_SIZE + core::mem::size_of::<FreeLinks>();
87 if size > off {
88 let p = (b as *mut u8).add(off);
89 let n = size - off;
90 // 8-byte words, then a byte tail (blocks are 16-aligned).
91 let mut i = 0;
92 while i + 8 <= n {
93 (p.add(i) as *mut u64).write_volatile(0xAAAAAAAAAAAAAAAA);
94 i += 8;
95 }
96 while i < n {
97 p.add(i).write_volatile(0xAA);
98 i += 1;
99 }
100 bytes += n as u64;
101 }
102 blocks += 1;
103 b = (*(*b).links()).next;
104 }
105 crate::serial_println!(
106 "heap: poisoned {} free block(s), {} KiB with 0x00AAAAAA",
107 blocks,
108 bytes / 1024
109 );
110 }
111}
112
113/// TEST HOOK: fill every free page (per the bitmap) with the OptiPlex's
114/// 0x00AAAAAA scrub pattern — the firmware's leftover fills pages the
115/// page allocator will hand out (task stacks, heap growth), so any
116/// uninitialized read there matches the real machine byte-for-byte.
117pub fn poison_free_pages_aa() {
118 unsafe {
119 if BITMAP.is_null() {
120 crate::serial_println!("pages: poison skipped (no bitmap)");
121 return;
122 }
123 let mut n = 0u32;
124 for idx in 0..SPAN_PAGES {
125 let free = (*BITMAP.add(idx / 8) >> (idx % 8)) & 1 == 0;
126 if !free {
127 continue;
128 }
129 let base = (BASE_PAGE + idx) * PAGE_SIZE;
130 let p = base as *mut u64;
131 for k in 0..(PAGE_SIZE / 8) {
132 p.add(k).write_volatile(0x00AAAAAA00AAAAAA);
133 }
134 n += 1;
135 }
136 crate::serial_println!("pages: poisoned {} free page(s) with 0x00AAAAAA", n);
137 }
138}
139
74/// Heap region bounds (set by `init_heap`). 140/// Heap region bounds (set by `init_heap`).
75static mut HEAP_START: usize = 0; 141static mut HEAP_START: usize = 0;
76static mut HEAP_END: usize = 0; 142static mut HEAP_END: usize = 0;
@@ -300,7 +366,7 @@ unsafe impl GlobalAlloc for Allocator {
300 } 366 }
301 } 367 }
302 368
303 unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 369 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
304 if ptr.is_null() { 370 if ptr.is_null() {
305 return; 371 return;
306 } 372 }
@@ -319,7 +385,7 @@ unsafe impl GlobalAlloc for Allocator {
319 out(reg) rsp, 385 out(reg) rsp,
320 options(nostack, preserves_flags) 386 options(nostack, preserves_flags)
321 ); 387 );
322 heap_guard_report(p, rsp); 388 heap_guard_report(p, rsp, layout.size(), layout.align());
323 } 389 }
324 let block = *((ptr as *const usize).sub(1)) as *mut Header; 390 let block = *((ptr as *const usize).sub(1)) as *mut Header;
325 debug_assert!( 391 debug_assert!(
@@ -376,7 +442,7 @@ unsafe fn back_pointer_ok(ptr: usize) -> bool {
376/// Heap-corruption report, called from `dealloc` when the back-pointer 442/// Heap-corruption report, called from `dealloc` when the back-pointer
377/// check fails. Heap-free by construction (we ARE the allocator): raw 443/// check fails. Heap-free by construction (we ARE the allocator): raw
378/// serial, the crash band, crash.log, then halt. 444/// serial, the crash band, crash.log, then halt.
379fn heap_guard_report(ptr: usize, rsp: usize) -> ! { 445fn heap_guard_report(ptr: usize, rsp: usize, size: usize, align: usize) -> ! {
380 let (hs, he) = heap_range().unwrap_or((0, 0)); 446 let (hs, he) = heap_range().unwrap_or((0, 0));
381 let block = if ptr >= hs + 8 && ptr <= he { unsafe { *(ptr.wrapping_sub(8) as *const usize) } } else { 0 }; 447 let block = if ptr >= hs + 8 && ptr <= he { unsafe { *(ptr.wrapping_sub(8) as *const usize) } } else { 0 };
382 448
@@ -397,7 +463,19 @@ fn heap_guard_report(ptr: usize, rsp: usize) -> ! {
397 let mut w = crate::strbuf::FmtBuf::new(&mut b3); 463 let mut w = crate::strbuf::FmtBuf::new(&mut b3);
398 let _ = fmt::write( 464 let _ = fmt::write(
399 &mut w, 465 &mut w,
400 core::format_args!("heap={:#x}..{:#x} rsp={:#x}", hs, he, rsp), 466 core::format_args!(
467 "heap={:#x}..{:#x} rsp={:#x}",
468 hs, he, rsp
469 ),
470 );
471 w.len()
472 };
473 let mut b3b = [0u8; 96];
474 let n3b = {
475 let mut w = crate::strbuf::FmtBuf::new(&mut b3b);
476 let _ = fmt::write(
477 &mut w,
478 core::format_args!("drop size={:#x} align={:#x}", size, align),
401 ); 479 );
402 w.len() 480 w.len()
403 }; 481 };
@@ -482,17 +560,18 @@ fn heap_guard_report(ptr: usize, rsp: usize) -> ! {
482 let l1 = core::str::from_utf8(&b1[..n1]).unwrap_or("!!!"); 560 let l1 = core::str::from_utf8(&b1[..n1]).unwrap_or("!!!");
483 let l2 = core::str::from_utf8(&b2[..n2]).unwrap_or(""); 561 let l2 = core::str::from_utf8(&b2[..n2]).unwrap_or("");
484 let l3 = core::str::from_utf8(&b3[..n3]).unwrap_or(""); 562 let l3 = core::str::from_utf8(&b3[..n3]).unwrap_or("");
563 let l3b = core::str::from_utf8(&b3b[..n3b]).unwrap_or("");
485 let l4 = core::str::from_utf8(&b4[..n4]).unwrap_or(""); 564 let l4 = core::str::from_utf8(&b4[..n4]).unwrap_or("");
486 let l4b = if n4b > 0 { core::str::from_utf8(&b4b[..n4b]).unwrap_or("") } else { "" }; 565 let l4b = if n4b > 0 { core::str::from_utf8(&b4b[..n4b]).unwrap_or("") } else { "" };
487 let l5 = core::str::from_utf8(&b5[..n5]).unwrap_or(""); 566 let l5 = core::str::from_utf8(&b5[..n5]).unwrap_or("");
488 let l6 = core::str::from_utf8(&b6[..n6]).unwrap_or(""); 567 let l6 = core::str::from_utf8(&b6[..n6]).unwrap_or("");
489 let l7 = core::str::from_utf8(&b7[..n7]).unwrap_or(""); 568 let l7 = core::str::from_utf8(&b7[..n7]).unwrap_or("");
490 let l8 = core::str::from_utf8(&b8[..n8]).unwrap_or(""); 569 let l8 = core::str::from_utf8(&b8[..n8]).unwrap_or("");
491 for l in [l2, l3, l4, l4b, l5, l6, l7, l8] { 570 for l in [l2, l3, l3b, l4, l4b, l5, l6, l7, l8] {
492 crate::serial::write_raw(l); 571 crate::serial::write_raw(l);
493 crate::serial::write_raw("\r\n"); 572 crate::serial::write_raw("\r\n");
494 } 573 }
495 let lines = [l1, l2, l3, l4, l4b, l5, l6, l7, l8]; 574 let lines = [l1, l2, l3, l3b, l4, l4b, l5, l6, l7, l8];
496 crate::gop::dump_lines(&lines); 575 crate::gop::dump_lines(&lines);
497 crate::fs::write_crash_log(&lines); 576 crate::fs::write_crash_log(&lines);
498 crate::interrupts::halt() 577 crate::interrupts::halt()