Skip to content

Commit f9652ea

Browse files
committed
feat: handle double faults and configure a separate stack to prevent stack overflows
1 parent f99fdf1 commit f9652ea

File tree

5 files changed

+154
-1
lines changed

5 files changed

+154
-1
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ test-timeout = 60 # 1 minute in seconds
3535

3636
[[test]]
3737
name = "should_panic"
38-
harness = false # disable test runner from default and custom test framework.
38+
harness = false # disable test runner from default and custom test framework.
39+
40+
[[test]]
41+
name = "stack_overflow"
42+
harness = false

NOTES.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## Double fault
2+
3+
- Uma exception desse tipo acontece quando uma segunda exceção ocorre durante a manipulação de um handler de exception anterior.
4+
5+
## Stack overflow
6+
7+
- Uma guard page é uma página que fica lá embaixo da stack
8+
- Detecta overflows
9+
- Não é mapeada para um frame físico da memória
10+
- Causa um page fault ao invés de corromper silenciosamente outra memória.

src/gdt.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use lazy_static::lazy_static;
2+
use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector};
3+
use x86_64::structures::tss::TaskStateSegment;
4+
use x86_64::VirtAddr;
5+
6+
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
7+
8+
lazy_static! {
9+
static ref TSS: TaskStateSegment = {
10+
let mut tss = TaskStateSegment::new();
11+
12+
tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
13+
const STACK_SIZE: usize = 4096 * 5;
14+
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
15+
16+
let stack_start = VirtAddr::from_ptr(&raw const STACK);
17+
let stack_end = stack_start + STACK_SIZE;
18+
stack_end
19+
};
20+
tss
21+
};
22+
}
23+
24+
lazy_static! {
25+
static ref GDT: (GlobalDescriptorTable, Selectors) = {
26+
let mut gdt = GlobalDescriptorTable::new();
27+
let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
28+
let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
29+
30+
(
31+
gdt,
32+
Selectors {
33+
code_selector,
34+
tss_selector,
35+
},
36+
)
37+
};
38+
}
39+
40+
struct Selectors {
41+
code_selector: SegmentSelector,
42+
tss_selector: SegmentSelector,
43+
}
44+
45+
pub fn init() {
46+
use x86_64::instructions::segmentation::{Segment, CS};
47+
use x86_64::instructions::tables::load_tss;
48+
49+
GDT.0.load();
50+
51+
unsafe {
52+
CS::set_reg(GDT.1.code_selector);
53+
load_tss(GDT.1.tss_selector);
54+
}
55+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use core::panic::PanicInfo;
99

10+
pub mod gdt;
1011
pub mod interrupts;
1112
pub mod serial;
1213
pub mod vga_buffer;
@@ -45,6 +46,7 @@ pub fn test_panic_handler(info: &PanicInfo) -> ! {
4546

4647
pub fn init() {
4748
interrupts::init_idt();
49+
gdt::init();
4850
}
4951

5052
#[cfg(test)]

tests/stack_overflow.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#![no_std]
2+
#![no_main]
3+
#![feature(abi_x86_interrupt)]
4+
5+
use core::panic::PanicInfo;
6+
use lazy_static::lazy_static;
7+
use osdev_rust::{
8+
exit_qemu, gdt::DOUBLE_FAULT_IST_INDEX, serial_print, serial_println, test_panic_handler,
9+
QemuExitCode,
10+
};
11+
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
12+
13+
#[no_mangle]
14+
pub extern "C" fn _start() -> ! {
15+
serial_print!("stack_overflow::stack_overflow...\t");
16+
17+
osdev_rust::gdt::init();
18+
init_test_idt();
19+
20+
stack_overflow();
21+
22+
panic!("Execution was to stop");
23+
}
24+
25+
#[allow(unconditional_recursion)]
26+
fn stack_overflow() {
27+
// o endereço de retorno é puxado para pilha em cada chamada.
28+
// causa um stack overflow.
29+
// isso chega na "guard page" que é criada pelo bootloader.
30+
// essa guard page não permite leitura nem escrita.
31+
// e fica abaixo da stack.
32+
// ao acessarmos a guard page, ocorre um page fault.
33+
// quando isso acontece, a CPU encontra o handler no IDT e tenta puxar um stack frame específico de interrupts.
34+
// no entanto, o ponteiro de pilha está apontando ainda para o guard page.
35+
// isso causa um segundo page fault.
36+
// quando ocorre um page fault seguido de outro, ocorre um double fault.
37+
// se não configurarmos uma pilha separada nesse caso para o handling do double fault, ocorre um triple fault e
38+
// na maioria dos sistemas isso causará um reboot.
39+
stack_overflow();
40+
41+
// garante que o compilador não transforme a recursão em um loop.
42+
// devido a tail recursion optimizations.
43+
// isso quebraria o teste.
44+
volatile::Volatile::new(0).read();
45+
}
46+
47+
lazy_static! {
48+
static ref TEST_IDT: InterruptDescriptorTable = {
49+
let mut idt = InterruptDescriptorTable::new();
50+
51+
unsafe {
52+
// quando executa um double fault devido a um stack overflow,
53+
// a pilha vai está corrompida.
54+
// por isso armazenamos uma stack separada para o handling de double fault
55+
// fazemos isso configurando um ponteiro para um stack separada dentro do IST dentro do TSS.
56+
idt.double_fault
57+
.set_handler_fn(test_double_flat_handler)
58+
.set_stack_index(DOUBLE_FAULT_IST_INDEX);
59+
}
60+
61+
idt
62+
};
63+
}
64+
65+
pub fn init_test_idt() {
66+
TEST_IDT.load();
67+
}
68+
69+
extern "x86-interrupt" fn test_double_flat_handler(
70+
_stack_frame: InterruptStackFrame,
71+
_error_code: u64,
72+
) -> ! {
73+
serial_println!("[ok]");
74+
exit_qemu(QemuExitCode::Success);
75+
// a CPU não permite retorno de handlers de double faults.
76+
loop {}
77+
}
78+
79+
#[panic_handler]
80+
fn panic(info: &PanicInfo) -> ! {
81+
test_panic_handler(info);
82+
}

0 commit comments

Comments
 (0)