Skip to content

Commit 4f8782e

Browse files
ryanbreenRyan Breenclaude
authored
fix(interrupts): use proper kernel stack when returning to idle loop (#82)
When returning to idle_loop from exception handlers (page fault, etc.), we were using `current_rsp + 256` as the stack pointer. This is wrong when running on IST stacks (page fault uses IST[1]). IST stacks are small (~4KB) and meant only for exception handling. When idle_loop runs on the IST stack and timer interrupts fire, the interrupt frames and nested calls can overflow the small IST stack, causing memory corruption and crashes. This bug manifested as kernel page faults at 0xffffc97ffffffff0 (top of PML4[402] region) - a corrupted RSP value. It only appeared on QEMU 8.x (GitHub CI) but not QEMU 10.x (local) due to timing differences. Fix: Use per_cpu::kernel_stack_top() which returns the idle thread's actual kernel stack, which is large enough for normal execution. Changed in two places: - kernel/src/interrupts.rs: page fault handler recovery path - kernel/src/interrupts/context_switch.rs: setup_idle_return() Co-authored-by: Ryan Breen <ryanbreen@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9e165dc commit 4f8782e

File tree

2 files changed

+13
-11
lines changed

2 files changed

+13
-11
lines changed

kernel/src/interrupts.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,10 +1217,12 @@ extern "x86-interrupt" fn page_fault_handler(
12171217
let flags_ptr = &mut frame.cpu_flags as *mut x86_64::registers::rflags::RFlags as *mut u64;
12181218
*flags_ptr = 0x202;
12191219

1220-
// Set up kernel stack - use current RSP with some headroom
1221-
let current_rsp: u64;
1222-
core::arch::asm!("mov {}, rsp", out(reg) current_rsp);
1223-
frame.stack_pointer = x86_64::VirtAddr::new(current_rsp + 256);
1220+
// CRITICAL: Use the idle thread's actual kernel stack, NOT the IST stack!
1221+
// The page fault handler runs on IST[1] which is small and not meant
1222+
// for general execution. Using current_rsp would continue on IST stack
1223+
// which can overflow when timer interrupts fire.
1224+
let idle_stack = crate::per_cpu::kernel_stack_top();
1225+
frame.stack_pointer = x86_64::VirtAddr::new(idle_stack);
12241226
});
12251227
}
12261228

kernel/src/interrupts/context_switch.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -668,13 +668,13 @@ fn setup_idle_return(interrupt_frame: &mut InterruptStackFrame) {
668668
let flags_ptr = &mut frame.cpu_flags as *mut x86_64::registers::rflags::RFlags as *mut u64;
669669
*flags_ptr = 0x202;
670670

671-
// CRITICAL: Must set kernel stack pointer when returning to idle!
672-
// The idle thread runs in kernel mode and needs a kernel stack.
673-
// Get the kernel stack pointer from the current CPU stack
674-
let current_rsp: u64;
675-
core::arch::asm!("mov {}, rsp", out(reg) current_rsp);
676-
// Add some space to account for the interrupt frame
677-
frame.stack_pointer = x86_64::VirtAddr::new(current_rsp + 256);
671+
// CRITICAL: Use the idle thread's actual kernel stack!
672+
// Do NOT use current_rsp because this function may be called from
673+
// IST stacks (page fault, NMI, etc.) which are small and not meant
674+
// for general execution. Using per_cpu::kernel_stack_top() ensures
675+
// we return to the proper kernel stack that can handle interrupts.
676+
let idle_stack = crate::per_cpu::kernel_stack_top();
677+
frame.stack_pointer = x86_64::VirtAddr::new(idle_stack);
678678
});
679679

680680
// NOTE: We do NOT switch page tables here. The userspace process page table

0 commit comments

Comments
 (0)