Skip to content

Commit 8ee2ff4

Browse files
committed
fix: clear DF before calling exception handler
Exception and interrupt gates do not clear RFLAGS.DF, so a guest exception raised with DF set entered hl_exception_handler with DF still set. The x86-64 ABI requires DF clear on function entry: The direction flag DF in the %rFLAGS register must be clear (set to forward direction) on function entry and return. See: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf 3.2.1 A concrete failure scenario is: 1. A guest memory move routine sets DF to copy overlapping memory backward, 2. a write hits a cow page and causes a page fault, before the routine clears DF, 3. the cpu saves the interrupted flags but does not clear DF before entering our exception handler, 4. the handler calls Rust with the wrong abi state. Its own memory operations, including the CoW page copy, could run backward and corrupt guest memory or cause another fault.
1 parent 7d244de commit 8ee2ff4

3 files changed

Lines changed: 43 additions & 31 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
7070
* Embedded byte payload tables and their value-union variants.
7171

7272
### Fixed
73+
* AMD64 exception handlers clear the direction flag before calling Rust.
7374
* Linear-time segment consumption for fragmented virtqueue messages.
7475
* Allow reclaimed virtqueue completions to span multiple ring reuse cycles.
7576
* Virtqueue consumers return errors when payload copies or runtime bookkeeping

‎src/hyperlight_guest_bin/src/arch/amd64/exception/entry.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ macro_rules! generate_exceptions {
123123
// bottom of the context struct, which happens to be the
124124
// stack pointer just before it was called.
125125
" mov rdi, rsp\n",
126+
// Rust requires DF clear. IRETQ restores the interrupted flags.
127+
" cld\n",
126128
" call {hl_exception_handler}\n",
127129
context::restore!(),
128130
" add rsp, 8\n", // error code

‎src/tests/rust_guests/simpleguest/src/main.rs‎

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ const TEST_R9_VALUE: u64 = 0x1234567890ABCDEF;
5858
const TEST_R9_MODIFIED_VALUE: u64 = 0xBADC0FFEE;
5959
#[cfg(target_arch = "x86_64")]
6060
const TEST_R10_VALUE: u64 = 0xDEADBEEF;
61+
#[cfg(target_arch = "x86_64")]
62+
const RFLAGS_DF: u64 = 1 << 10;
6163

6264
#[guest_function("SetStatic")]
6365
fn set_static() -> i32 {
@@ -74,7 +76,7 @@ fn echo_double(value: f64) -> f64 {
7476
value
7577
}
7678

77-
// Test exception handler that validates stack layout and records invocation
79+
// Test exception handler that validates DF, stack layout and records invocation
7880
// It is designed to interact with the trigger_int3 breakpoint exception function below
7981
#[cfg(target_arch = "x86_64")]
8082
fn test_exception_handler(
@@ -83,6 +85,15 @@ fn test_exception_handler(
8385
context: *mut Context,
8486
_page_fault_address: u64,
8587
) -> bool {
88+
let flags: u64;
89+
90+
// SAFETY: Read DF and clear it so a failed assertion can call Rust safely.
91+
unsafe {
92+
core::arch::asm!("pushfq", "pop {flags}", "cld", flags = out(reg) flags);
93+
}
94+
95+
assert_eq!(flags & RFLAGS_DF, 0, "exception handler requires DF clear");
96+
8697
// Record invocation
8798
HANDLER_INVOCATION_COUNT.fetch_add(1, Ordering::SeqCst);
8899

@@ -145,44 +156,42 @@ fn get_exception_handler_call_count() -> i32 {
145156
count as i32
146157
}
147158

148-
/// Trigger an INT3 breakpoint exception (vector 3)
159+
/// Check the exception-entry ABI and preservation of interrupted state.
149160
#[guest_function("TriggerInt3")]
150161
#[cfg(target_arch = "x86_64")]
151162
fn trigger_int3() -> i32 {
152-
// Set up test value in R9 before triggering exception
153-
let test_value: u64 = TEST_R9_VALUE;
163+
let r9_result: u64;
164+
let r10_result: u64;
165+
let flags: u64;
154166

167+
// SAFETY: The installed handler resumes at the next instruction. DF is
168+
// cleared before Rust resumes, after reading the flags restored by IRETQ.
155169
unsafe {
156-
// Store test value in R9 register
157-
core::arch::asm!(
158-
"mov r9, {0}",
159-
in(reg) test_value
160-
);
161-
162-
// This will trigger exception vector 3 (#BP - Breakpoint)
163-
core::arch::asm!("int3");
164-
165-
// After returning from exception handler, verify registers
166-
let r9_result: u64;
167-
let r10_result: u64;
168170
core::arch::asm!(
169-
"mov {0}, r9",
170-
"mov {1}, r10",
171-
out(reg) r9_result,
172-
out(reg) r10_result
173-
);
174-
175-
// R9 should be restored to original value (context restore working)
176-
assert_eq!(
177-
r9_result, test_value,
178-
"R9 register was not properly restored by exception handler"
179-
);
180-
// R10 should have the value written to context
181-
assert_eq!(
182-
r10_result, TEST_R10_VALUE,
183-
"R10 register was not modified via context by exception handler"
171+
"std",
172+
"int3",
173+
"pushfq",
174+
"pop {flags}",
175+
"cld",
176+
flags = lateout(reg) flags,
177+
inout("r9") TEST_R9_VALUE => r9_result,
178+
lateout("r10") r10_result,
184179
);
185180
}
181+
182+
assert_eq!(
183+
flags & RFLAGS_DF,
184+
RFLAGS_DF,
185+
"interrupted DF was not restored"
186+
);
187+
assert_eq!(
188+
r9_result, TEST_R9_VALUE,
189+
"R9 register was not properly restored by exception handler"
190+
);
191+
assert_eq!(
192+
r10_result, TEST_R10_VALUE,
193+
"R10 register was not modified via context by exception handler"
194+
);
186195
0
187196
}
188197

0 commit comments

Comments
 (0)