summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-02 23:28:29 +0200
committersalmonik <salmonik@tuta.io>2026-08-02 23:28:29 +0200
commitf846a9551bf30d61026fa9c9ccea78479ced321b (patch)
tree10158ca300cc51f44fc9afee9c2b3092554d880b
parentv0.5.7: guard reports the drop's size/align — identifies the clobbered struct (diff)
downloadrs-dos-f846a9551bf30d61026fa9c9ccea78479ced321b.tar.gz
rs-dos-f846a9551bf30d61026fa9c9ccea78479ced321b.zip
v0.5.7: drop test-poison hooks; vt heap-struct verify at shell entry
The OptiPlex's first banner write #GPs when the vt cells VecData holds firmware 0xAA fill instead of a real buffer pointer. The poison/fill test hooks (stack + free-heap + free-page 0x00AAAAAA) were removed from the release image — they changed boot behavior and may walk a corrupt free list over live blocks. Added vt::debug_verify() (cells/rendered ptr/cap/len + in-heap check) at shell entry: the next real-hardware boot shows whether the corruption is present at shell entry or lands during the banner write.
-rw-r--r--rsdos/src/main.rs40
-rw-r--r--rsnix/src/vt.rs42
2 files changed, 49 insertions, 33 deletions
diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs
index 52b37ec..42f95e5 100644
--- a/rsdos/src/main.rs
+++ b/rsdos/src/main.rs
@@ -344,22 +344,13 @@ pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut
344 } else { 344 } else {
345 rsnix::serial_println!("cursor: no mouse — overlay disabled"); 345 rsnix::serial_println!("cursor: no mouse — overlay disabled");
346 } 346 }
347 // TEST HOOK: reproduce the OptiPlex '0xaaaaaa' uninit-drop crash in 347 // Shell-entry self-check: verify the vt grid structs are sane right
348 // QEMU. The real hardware's stack carries 0xAA fill left by 348 // before the shell's first print. The OptiPlex crashes in the first
349 // firmware; any shell value that is dropped without being written 349 // banner write when the cells VecData (or the shadow) holds the
350 // then frees garbage and trips the dealloc guard. Filling the 350 // firmware's 0xAA fill instead of real values — this line shows
351 // shell's future stack region with 0xAA here makes the same bug 351 // whether the corruption is already present at shell entry or lands
352 // visible on QEMU (serial + gdb available) instead of only on the 352 // later, and prints the exact field values when it is.
353 // OptiPlex. 353 rsnix::vt::debug_verify();
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();
363 shell::run(); 354 shell::run();
364} 355}
365 356
@@ -368,23 +359,6 @@ pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut
368/// headroom. 128 KiB covers the whole shell frame chain on QEMU and 359/// headroom. 128 KiB covers the whole shell frame chain on QEMU and
369/// the OptiPlex. Best-effort: the region is below rsp inside the boot 360/// the OptiPlex. Best-effort: the region is below rsp inside the boot
370/// stack mapping; the call into `shell::run` overwrites the top word. 361/// stack mapping; the call into `shell::run` overwrites the top word.
371fn stack_fill_aa() {
372 let rsp: usize;
373 unsafe {
374 core::arch::asm!("mov {}, rsp", out(reg) rsp);
375 }
376 let top = rsp - 0x100;
377 let bottom = rsp - 0x21100;
378 unsafe {
379 let mut p = bottom;
380 while p < top {
381 core::ptr::write_volatile(p as *mut u64, 0x00AAAAAA00AAAAAA);
382 p += 8;
383 }
384 }
385 rsnix::serial_println!("stack: 0x00AAAAAA-fill 0x{:x}..0x{:x}", bottom, top);
386}
387
388// --------------------------------------------------------------------------- 362// ---------------------------------------------------------------------------
389// Print a memory summary from the stashed memory map 363// Print a memory summary from the stashed memory map
390// --------------------------------------------------------------------------- 364// ---------------------------------------------------------------------------
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs
index 4578f6c..6343b78 100644
--- a/rsnix/src/vt.rs
+++ b/rsnix/src/vt.rs
@@ -138,6 +138,48 @@ pub fn size() -> (usize, usize) {
138 unsafe { (ROWS, COLS) } 138 unsafe { (ROWS, COLS) }
139} 139}
140 140
141/// Shell-entry self-check: dump the vt grid's heap structs so a real-
142/// hardware boot shows whether the cells VecData / data arrays are sane
143/// at shell entry (the OptiPlex's first banner write #GPs when the
144/// cells buffer holds firmware 0xAA fill instead of real pointers).
145pub fn debug_verify() {
146 unsafe {
147 let total = ROWS * COLS;
148 let msg = |m: &str| {
149 crate::serial_println!("vt: {}", m);
150 };
151 if CELLS.is_null() || RENDERED.is_null() {
152 msg("verify: cells/renderd NULL — vt::init did not run");
153 return;
154 }
155 let c = &*(CELLS as *const Vec<Cell>);
156 let r = &*(RENDERED as *const Vec<Cell>);
157 msg(&alloc::format!(
158 "verify: cells ptr={:#x} cap={:#x} len={:#x} rendered ptr={:#x} cap={:#x} len={:#x} want={:#x}",
159 c.as_ptr() as usize,
160 c.capacity(),
161 c.len(),
162 r.as_ptr() as usize,
163 r.capacity(),
164 r.len(),
165 total
166 ));
167 // The cells array must cover rows*cols and be inside the heap.
168 let heap_ok = crate::memory::heap_range()
169 .map(|(hs, he)| {
170 let p = c.as_ptr() as usize;
171 let bytes = c.capacity().saturating_mul(core::mem::size_of::<Cell>());
172 p >= hs && p + bytes <= he
173 })
174 .unwrap_or(false);
175 if c.len() != total || r.len() != total || !heap_ok {
176 msg("verify: FAILED — cells/rendered not sane at shell entry");
177 } else {
178 msg("verify: ok");
179 }
180 }
181}
182
141pub fn set_cursor_visible(v: bool) { 183pub fn set_cursor_visible(v: bool) {
142 sync_scroll(); 184 sync_scroll();
143 unsafe { 185 unsafe {