diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-02 23:13:46 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-02 23:13:46 +0200 |
| commit | c10eb2333f277f815234f9fc26a5441e68ef7b40 (patch) | |
| tree | 9bff5b38a21c7edfa997a25a0ee51cb43c570a68 /rsnix/src | |
| parent | v0.5.7: guard report 4-words-per-line — 16 stack words survive band hand-copy (diff) | |
| download | rs-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.
Diffstat (limited to 'rsnix/src')
| -rw-r--r-- | rsnix/src/memory.rs | 91 |
1 files changed, 85 insertions, 6 deletions
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). |
| 72 | static mut FREE_LIST: *mut Header = ptr::null_mut(); | 72 | static 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). | ||
| 79 | pub 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. | ||
| 117 | pub 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`). |
| 75 | static mut HEAP_START: usize = 0; | 141 | static mut HEAP_START: usize = 0; |
| 76 | static mut HEAP_END: usize = 0; | 142 | static 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. |
| 379 | fn heap_guard_report(ptr: usize, rsp: usize) -> ! { | 445 | fn 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() |
