Coverage Report

Created: 2026-06-07 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/MigTD/deps/td-shim/td-exception/src/interrupt.rs
Line
Count
Source
1
// Copyright (c) 2021 Intel Corporation
2
//
3
// SPDX-License-Identifier: BSD-2-Clause-Patent
4
5
use core::arch::asm;
6
use spin::Mutex;
7
#[cfg(feature = "tdx")]
8
use tdx_tdcall::tdx;
9
10
use crate::{idt::IDT_ENTRY_COUNT, ExceptionError};
11
12
// the order is aligned with scratch_push!() and scratch_pop!()
13
#[repr(C, packed)]
14
pub struct ScratchRegisters {
15
    pub r11: usize,
16
    pub r10: usize,
17
    pub r9: usize,
18
    pub r8: usize,
19
    pub rsi: usize,
20
    pub rdi: usize,
21
    pub rdx: usize,
22
    pub rcx: usize,
23
    pub rax: usize,
24
}
25
26
impl ScratchRegisters {
27
0
    pub fn dump(&self) {
28
0
        log::info!("RAX:   {:>016X}\n", { self.rax });
29
0
        log::info!("RCX:   {:>016X}\n", { self.rcx });
30
0
        log::info!("RDX:   {:>016X}\n", { self.rdx });
31
0
        log::info!("RDI:   {:>016X}\n", { self.rdi });
32
0
        log::info!("RSI:   {:>016X}\n", { self.rsi });
33
0
        log::info!("R8:    {:>016X}\n", { self.r8 });
34
0
        log::info!("R9:    {:>016X}\n", { self.r9 });
35
0
        log::info!("R10:   {:>016X}\n", { self.r10 });
36
0
        log::info!("R11:   {:>016X}\n", { self.r11 });
37
0
    }
38
}
39
40
#[repr(C, packed)]
41
pub struct PreservedRegisters {
42
    pub r15: usize,
43
    pub r14: usize,
44
    pub r13: usize,
45
    pub r12: usize,
46
    pub rbp: usize,
47
    pub rbx: usize,
48
}
49
50
impl PreservedRegisters {
51
0
    pub fn dump(&self) {
52
0
        log::info!("RBX:   {:>016X}\n", { self.rbx });
53
0
        log::info!("RBP:   {:>016X}\n", { self.rbp });
54
0
        log::info!("R12:   {:>016X}\n", { self.r12 });
55
0
        log::info!("R13:   {:>016X}\n", { self.r13 });
56
0
        log::info!("R14:   {:>016X}\n", { self.r14 });
57
0
        log::info!("R15:   {:>016X}\n", { self.r15 });
58
0
    }
59
}
60
61
#[repr(packed)]
62
pub struct IretRegisters {
63
    pub rip: usize,
64
    pub cs: usize,
65
    pub rflags: usize,
66
}
67
68
impl IretRegisters {
69
0
    fn dump(&self) {
70
0
        log::info!("RFLAG: {:>016X}\n", { self.rflags });
71
0
        log::info!("CS:    {:>016X}\n", { self.cs });
72
0
        log::info!("RIP:   {:>016X}\n", { self.rip });
73
0
    }
74
}
75
76
#[repr(packed)]
77
pub struct InterruptStack {
78
    pub preserved: PreservedRegisters,
79
    pub scratch: ScratchRegisters,
80
    pub vector: usize,
81
    pub code: usize,
82
    pub iret: IretRegisters,
83
}
84
85
impl InterruptStack {
86
0
    pub fn dump(&self) {
87
0
        self.iret.dump();
88
0
        log::info!("CODE:  {:>016X}\n", { self.code });
89
0
        log::info!("VECTOR:  {:>016X}\n", { self.vector });
90
0
        self.scratch.dump();
91
0
        self.preserved.dump();
92
0
    }
93
}
94
95
#[derive(Debug, Copy, Clone)]
96
pub struct InterruptCallback {
97
    func: fn(&mut InterruptStack),
98
}
99
100
impl InterruptCallback {
101
0
    pub const fn new(func: fn(&mut InterruptStack)) -> Self {
102
0
        InterruptCallback { func }
103
0
    }
104
}
105
106
struct InterruptCallbackTable {
107
    table: [InterruptCallback; IDT_ENTRY_COUNT],
108
}
109
110
impl InterruptCallbackTable {
111
0
    const fn init() -> Self {
112
0
        InterruptCallbackTable {
113
0
            table: [InterruptCallback::new(default_callback); IDT_ENTRY_COUNT],
114
0
        }
115
0
    }
116
}
117
118
static CALLBACK_TABLE: Mutex<InterruptCallbackTable> = Mutex::new(InterruptCallbackTable::init());
119
120
0
pub(crate) fn init_interrupt_callbacks() {
121
0
    let mut callbacks = CALLBACK_TABLE.lock();
122
    // Set up exceptions handler according to Intel64 & IA32 Software Developer Manual
123
    // Reference: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
124
0
    callbacks.table[0].func = divide_by_zero;
125
0
    callbacks.table[1].func = debug;
126
0
    callbacks.table[2].func = non_maskable;
127
0
    callbacks.table[3].func = breakpoint;
128
0
    callbacks.table[4].func = overflow;
129
0
    callbacks.table[5].func = bound_range;
130
0
    callbacks.table[6].func = invalid_opcode;
131
0
    callbacks.table[7].func = device_not_available;
132
0
    callbacks.table[8].func = double_fault;
133
    // 9 no longer available
134
0
    callbacks.table[10].func = invalid_tss;
135
0
    callbacks.table[11].func = segment_not_present;
136
0
    callbacks.table[12].func = stack_segment;
137
0
    callbacks.table[13].func = protection;
138
0
    callbacks.table[14].func = page;
139
    // 15 reserved
140
0
    callbacks.table[16].func = fpu;
141
0
    callbacks.table[17].func = alignment_check;
142
0
    callbacks.table[18].func = machine_check;
143
0
    callbacks.table[19].func = simd;
144
    #[cfg(feature = "tdx")]
145
0
    {
146
0
        callbacks.table[20].func = virtualization;
147
0
    }
148
0
    callbacks.table[21].func = control_flow;
149
0
}
150
151
0
pub fn register_interrupt_callback(
152
0
    index: usize,
153
0
    callback: InterruptCallback,
154
0
) -> Result<(), ExceptionError> {
155
0
    if index > IDT_ENTRY_COUNT {
156
0
        return Err(ExceptionError::InvalidParameter);
157
0
    }
158
0
    CALLBACK_TABLE.lock().table[index] = callback;
159
0
    Ok(())
160
0
}
161
162
#[cfg(not(feature = "no-interrupt"))]
163
0
fn eoi() {
164
    // Write the end-of-interrupt (EOI) register (0x80B) at the end of the handler
165
    // routine, sometime before the IRET instruction
166
    unsafe {
167
0
        asm!(
168
0
            "
169
0
            mov rcx, 0x80B
170
0
            mov edx, 0
171
0
            mov eax, 0
172
0
            wrmsr
173
0
        "
174
0
        )
175
    }
176
0
}
177
178
#[no_mangle]
179
0
fn generic_interrupt_handler(stack: &mut InterruptStack) {
180
0
    if stack.vector >= IDT_ENTRY_COUNT {
181
0
        log::error!("Invalid interrupt vector number!\n");
182
0
        return;
183
0
    }
184
185
    // We need to allow the re-entry of this handler. For example, virtualization exception may
186
    // happen in a timer interrupt handler. So we need to copy the function pointer out and
187
    // release the lock.
188
0
    let func = CALLBACK_TABLE.lock().table[stack.vector].func;
189
0
    func(stack);
190
191
    // If we are handling an interrupt, signal a end-of-interrupt before return.
192
    // When no-interrupt feature is enabled, do not send EOI —
193
    // VMM interrupts should never reach this point (IDT entries 32-255 are non-present).
194
    #[cfg(not(feature = "no-interrupt"))]
195
0
    if stack.vector > 31 {
196
0
        eoi();
197
0
    }
198
0
}
199
200
0
fn default_callback(stack: &mut InterruptStack) {
201
0
    log::info!("default interrupt callback\n");
202
0
    stack.dump();
203
0
    deadloop();
204
0
}
205
206
#[cfg(feature = "integration-test")]
207
fn divide_by_zero(stack: &mut InterruptStack) {
208
    log::info!("Divide by zero\n");
209
    crate::DIVIDED_BY_ZERO_EVENT_COUNT.fetch_add(1, core::sync::atomic::Ordering::AcqRel);
210
    stack.iret.rip += 7;
211
    log::info!("divide_by_zero done\n");
212
    return;
213
}
214
215
#[cfg(not(feature = "integration-test"))]
216
0
fn divide_by_zero(stack: &mut InterruptStack) {
217
0
    log::info!("Divide by zero\n");
218
0
    stack.dump();
219
0
    deadloop();
220
0
}
221
222
0
fn debug(stack: &mut InterruptStack) {
223
0
    log::info!("Debug trap\n");
224
0
    stack.dump();
225
0
    deadloop();
226
0
}
227
228
0
fn non_maskable(stack: &mut InterruptStack) {
229
0
    log::info!("Non-maskable interrupt\n");
230
0
    stack.dump();
231
0
    deadloop();
232
0
}
233
234
0
fn breakpoint(stack: &mut InterruptStack) {
235
0
    log::info!("Breakpoint trap\n");
236
0
    stack.dump();
237
0
    deadloop();
238
0
}
239
240
0
fn overflow(stack: &mut InterruptStack) {
241
0
    log::info!("Overflow trap\n");
242
0
    stack.dump();
243
0
    deadloop();
244
0
}
245
246
0
fn bound_range(stack: &mut InterruptStack) {
247
0
    log::info!("Bound range exceeded fault\n");
248
0
    stack.dump();
249
0
    deadloop();
250
0
}
251
252
0
fn invalid_opcode(stack: &mut InterruptStack) {
253
0
    log::info!("Invalid opcode fault\n");
254
0
    stack.dump();
255
0
    deadloop();
256
0
}
257
258
0
fn device_not_available(stack: &mut InterruptStack) {
259
0
    log::info!("Device not available fault\n");
260
0
    stack.dump();
261
0
    deadloop();
262
0
}
263
264
0
fn double_fault(stack: &mut InterruptStack) {
265
0
    log::info!("Double fault\n");
266
0
    stack.dump();
267
0
    deadloop();
268
0
}
269
270
0
fn invalid_tss(stack: &mut InterruptStack) {
271
0
    log::info!("Invalid TSS fault\n");
272
0
    stack.dump();
273
0
    deadloop();
274
0
}
275
276
0
fn segment_not_present(stack: &mut InterruptStack) {
277
0
    log::info!("Segment not present fault\n");
278
0
    stack.dump();
279
0
    deadloop();
280
0
}
281
282
0
fn stack_segment(stack: &mut InterruptStack) {
283
0
    log::info!("Stack segment fault\n");
284
0
    stack.dump();
285
0
    deadloop();
286
0
}
287
288
0
fn protection(stack: &mut InterruptStack) {
289
0
    log::info!("Protection fault\n");
290
0
    stack.dump();
291
0
    deadloop();
292
0
}
293
294
0
fn page(stack: &mut InterruptStack) {
295
    let cr2: usize;
296
0
    unsafe {
297
0
        asm!("mov {}, cr2",  out(reg) cr2);
298
0
    }
299
0
    log::info!("Page fault: {:>016X}\n", cr2);
300
0
    stack.dump();
301
0
    deadloop();
302
0
}
303
304
0
fn fpu(stack: &mut InterruptStack) {
305
0
    log::info!("FPU floating point fault\n");
306
0
    stack.dump();
307
0
    deadloop();
308
0
}
309
310
0
fn alignment_check(stack: &mut InterruptStack) {
311
0
    log::info!("Alignment check fault\n");
312
0
    stack.dump();
313
0
    deadloop();
314
0
}
315
316
0
fn machine_check(stack: &mut InterruptStack) {
317
0
    log::info!("Machine check fault\n");
318
0
    stack.dump();
319
0
    deadloop();
320
0
}
321
322
0
fn simd(stack: &mut InterruptStack) {
323
0
    log::info!("SIMD floating point fault\n");
324
0
    stack.dump();
325
0
    deadloop();
326
0
}
327
328
0
fn control_flow(stack: &mut InterruptStack) {
329
0
    log::info!("Control Flow Exception\n");
330
0
    stack.dump();
331
0
    deadloop();
332
0
}
333
334
#[cfg(feature = "tdx")]
335
const EXIT_REASON_CPUID: u32 = 10;
336
#[cfg(feature = "tdx")]
337
const EXIT_REASON_HLT: u32 = 12;
338
#[cfg(feature = "tdx")]
339
const EXIT_REASON_RDPMC: u32 = 15;
340
#[cfg(feature = "tdx")]
341
const EXIT_REASON_VMCALL: u32 = 18;
342
#[cfg(feature = "tdx")]
343
const EXIT_REASON_IO_INSTRUCTION: u32 = 30;
344
#[cfg(feature = "tdx")]
345
const EXIT_REASON_MSR_READ: u32 = 31;
346
#[cfg(feature = "tdx")]
347
const EXIT_REASON_MSR_WRITE: u32 = 32;
348
#[cfg(feature = "tdx")]
349
const EXIT_REASON_MWAIT_INSTRUCTION: u32 = 36;
350
#[cfg(feature = "tdx")]
351
const EXIT_REASON_MONITOR_INSTRUCTION: u32 = 39;
352
#[cfg(feature = "tdx")]
353
const EXIT_REASON_WBINVD: u32 = 54;
354
355
#[cfg(feature = "tdx")]
356
0
fn virtualization(stack: &mut InterruptStack) {
357
    // Firstly get VE information from TDX module, halt it error occurs
358
0
    let ve_info = tdx::tdcall_get_ve_info().expect("#VE handler: fail to get VE info\n");
359
360
    #[cfg(not(feature = "no-tdvmcall"))]
361
0
    match ve_info.exit_reason {
362
0
        EXIT_REASON_HLT => {
363
0
            tdx::tdvmcall_halt();
364
0
        }
365
        EXIT_REASON_IO_INSTRUCTION => {
366
0
            if !handle_tdx_ioexit(&ve_info, stack) {
367
0
                tdx::tdvmcall_halt();
368
0
            }
369
        }
370
0
        EXIT_REASON_MSR_READ => {
371
0
            let msr = tdx::tdvmcall_rdmsr(stack.scratch.rcx as u32)
372
0
                .expect("fail to perform RDMSR operation\n");
373
0
            stack.scratch.rax = (msr as u32 & u32::MAX) as usize; // EAX
374
0
            stack.scratch.rdx = ((msr >> 32) as u32 & u32::MAX) as usize; // EDX
375
0
        }
376
0
        EXIT_REASON_MSR_WRITE => {
377
0
            let data = stack.scratch.rax as u64 | ((stack.scratch.rdx as u64) << 32); // EDX:EAX
378
0
            tdx::tdvmcall_wrmsr(stack.scratch.rcx as u32, data)
379
0
                .expect("fail to perform WRMSR operation\n");
380
0
        }
381
0
        EXIT_REASON_CPUID => {
382
0
            let cpuid = tdx::tdvmcall_cpuid(stack.scratch.rax as u32, stack.scratch.rcx as u32);
383
0
            let mask = 0xFFFF_FFFF_0000_0000_usize;
384
0
            stack.scratch.rax = (stack.scratch.rax & mask) | cpuid.eax as usize;
385
0
            stack.preserved.rbx = (stack.preserved.rbx & mask) | cpuid.ebx as usize;
386
0
            stack.scratch.rcx = (stack.scratch.rcx & mask) | cpuid.ecx as usize;
387
0
            stack.scratch.rdx = (stack.scratch.rdx & mask) | cpuid.edx as usize;
388
0
        }
389
        EXIT_REASON_VMCALL
390
        | EXIT_REASON_MWAIT_INSTRUCTION
391
        | EXIT_REASON_MONITOR_INSTRUCTION
392
        | EXIT_REASON_WBINVD
393
0
        | EXIT_REASON_RDPMC => return,
394
        // Unknown
395
        // And currently CPUID and MMIO handler is not implemented
396
        // Only VMCall is supported
397
        _ => {
398
0
            log::warn!("Unsupported #VE exit reason {:#x} ", ve_info.exit_reason);
399
0
            log::info!("Virtualization fault\n");
400
0
            stack.dump();
401
0
            deadloop();
402
        }
403
    };
404
405
    #[cfg(feature = "no-tdvmcall")]
406
    match ve_info.exit_reason {
407
        EXIT_REASON_HLT
408
        | EXIT_REASON_IO_INSTRUCTION
409
        | EXIT_REASON_MSR_READ
410
        | EXIT_REASON_MSR_WRITE
411
        | EXIT_REASON_CPUID
412
        | EXIT_REASON_VMCALL
413
        | EXIT_REASON_MWAIT_INSTRUCTION
414
        | EXIT_REASON_MONITOR_INSTRUCTION
415
        | EXIT_REASON_WBINVD
416
        | EXIT_REASON_RDPMC => return,
417
        // Unknown
418
        // And currently CPUID and MMIO handler is not implemented
419
        // Only VMCall is supported
420
        _ => {
421
            log::warn!("Unsupported #VE exit reason {:#x} ", ve_info.exit_reason);
422
            log::info!("Virtualization fault\n");
423
            stack.dump();
424
            deadloop();
425
        }
426
    };
427
428
0
    stack.iret.rip += ve_info.exit_instruction_length as usize;
429
430
    // If CET shadow stack is enabled, processor will compare the `LIP` value saved in the shadow
431
    // stack and the `RIP` value saved in the normal stack when executing a return from an
432
    // exception handler and cause a control protection exception if they do not match.
433
    #[cfg(feature = "cet-shstk")]
434
    unsafe {
435
        use x86_64::registers::control::{Cr4, Cr4Flags};
436
        use x86_64::registers::model_specific::Msr;
437
438
        const MSR_IA32_S_CET: u32 = 0x6A2;
439
        const SH_STK_EN: u64 = 1;
440
        const WR_SHSTK_E: u64 = 1 << 1;
441
442
        let mut msr_cet = Msr::new(MSR_IA32_S_CET);
443
444
        // If shadow stack is not enabled, return
445
        if (msr_cet.read() & SH_STK_EN) == 0
446
            || (Cr4::read() & Cr4Flags::CONTROL_FLOW_ENFORCEMENT).is_empty()
447
        {
448
            return;
449
        }
450
451
        // Read the Shadow Stack Pointer
452
        let mut ssp: u64;
453
        asm!(
454
            "rdsspq {ssp}",
455
            ssp = out(reg) ssp,
456
        );
457
458
        // SSP -> return address of func [virtualization]
459
        //        return address of func [generic_interrupt_handler]
460
        //        SSP
461
        //        LIP
462
        //        CS
463
        let lip_ptr = ssp + 0x18;
464
        let lip = *(lip_ptr as *const u64) + ve_info.exit_instruction_length as u64;
465
466
        // Enables the WRSSD/WRSSQ instructions by setting the `WR_SHSTK_E`
467
        // to 1, then we can write the shadow stack
468
        msr_cet.write(msr_cet.read() | WR_SHSTK_E);
469
470
        // Write the new LIP to the shadow stack
471
        asm!(
472
            "wrssq [{lip_ptr}], {lip}",
473
            lip_ptr = in(reg) lip_ptr,
474
            lip = in(reg) lip,
475
        );
476
477
        // Clear the `WR_SHSTK_E`
478
        msr_cet.write(msr_cet.read() & !WR_SHSTK_E);
479
    }
480
0
}
481
482
// Handle IO exit from TDX Module
483
//
484
// Use TDVMCALL to realize IO read/write operation
485
// Return false if VE info is invalid
486
#[cfg(all(feature = "tdx", not(feature = "no-tdvmcall")))]
487
0
fn handle_tdx_ioexit(ve_info: &tdx::TdVeInfo, stack: &mut InterruptStack) -> bool {
488
0
    let size = ((ve_info.exit_qualification & 0x7) + 1) as usize; // 0 - 1bytes, 1 - 2bytes, 3 - 4bytes
489
0
    let read = (ve_info.exit_qualification >> 3) & 0x1 == 1;
490
0
    let string = (ve_info.exit_qualification >> 4) & 0x1 == 1;
491
0
    let _operand = (ve_info.exit_qualification >> 6) & 0x1 == 0; // 0 = DX, 1 = immediate
492
0
    let port = (ve_info.exit_qualification >> 16) as u16;
493
0
    let repeat = if (ve_info.exit_qualification >> 5) & 0x1 == 1 {
494
0
        stack.scratch.rcx
495
    } else {
496
0
        0
497
    };
498
499
    // Size of access should be 1/2/4 bytes
500
0
    if size != 1 && size != 2 && size != 4 {
501
0
        return false;
502
0
    }
503
504
    // Define closure to perform IO port read with different size operands
505
0
    let io_read = |size, port| match size {
506
0
        1 => tdx::tdvmcall_io_read_8(port) as u32,
507
0
        2 => tdx::tdvmcall_io_read_16(port) as u32,
508
0
        4 => tdx::tdvmcall_io_read_32(port),
509
0
        _ => 0,
510
0
    };
511
512
    // Define closure to perform IO port write with different size operands
513
0
    let io_write = |size, port, data| match size {
514
0
        1 => tdx::tdvmcall_io_write_8(port, data as u8),
515
0
        2 => tdx::tdvmcall_io_write_16(port, data as u16),
516
0
        4 => tdx::tdvmcall_io_write_32(port, data),
517
0
        _ => {}
518
0
    };
519
520
    // INS / OUTS
521
0
    if string {
522
0
        for _ in 0..repeat {
523
0
            if read {
524
0
                let val = io_read(size, port);
525
0
                unsafe {
526
0
                    let rsi = core::slice::from_raw_parts_mut(stack.scratch.rdi as *mut u8, size);
527
0
                    // Safety: size is smaller than 4
528
0
                    rsi.copy_from_slice(&u32::to_le_bytes(val)[..size])
529
0
                }
530
0
                stack.scratch.rdi += size;
531
0
            } else {
532
0
                let mut val = 0;
533
                unsafe {
534
0
                    let rsi = core::slice::from_raw_parts(stack.scratch.rsi as *mut u8, size);
535
0
                    for (idx, byte) in rsi.iter().enumerate() {
536
0
                        val |= (*byte as u32) << (idx * 8);
537
0
                    }
538
                }
539
0
                io_write(size, port, val);
540
0
                stack.scratch.rsi += size;
541
            }
542
0
            stack.scratch.rcx -= 1;
543
        }
544
0
    } else if read {
545
0
        // Write the IO read result to the low $size-bytes of rax
546
0
        stack.scratch.rax = (stack.scratch.rax & !(2_usize.pow(size as u32 * 8) - 1))
547
0
            | (io_read(size, port) as usize & (2_usize.pow(size as u32 * 8) - 1));
548
0
    } else {
549
0
        io_write(size, port, stack.scratch.rax as u32);
550
0
    }
551
552
0
    true
553
0
}
554
555
0
fn deadloop() {
556
    #[allow(clippy::empty_loop)]
557
0
    loop {
558
0
        x86_64::instructions::interrupts::enable();
559
0
        x86_64::instructions::hlt();
560
0
    }
561
}