diff options
| author | Alexander Potapenko <glider@google.com> | 2025-10-01 16:35:10 +0200 |
|---|---|---|
| committer | Alexander Potapenko <glider@google.com> | 2025-10-17 06:51:20 +0000 |
| commit | e69835fc40b5e00c0996ce3a85d8287eea57d162 (patch) | |
| tree | d935f0f54b202ec2402f74852f7d44735b567653 | |
| parent | b2cf58f4862666d3b14f051b1e8cddcde9f8186f (diff) | |
executor: sys/linux: implement SYZOS_API_SET_IRQ_HANDLER
The new API call allows to initialize the handler with one of the
three possible values:
- NULL (should cause a page fault)
- dummy_null_handler (should call iret)
- uexit_irq_handler (should perform guest_uexit(UEXIT_IRQ))
Also add a test for uexit_irq_handler()
| -rw-r--r-- | executor/common_kvm_amd64.h | 12 | ||||
| -rw-r--r-- | executor/common_kvm_amd64_syzos.h | 71 | ||||
| -rw-r--r-- | sys/linux/dev_kvm_amd64.txt | 24 | ||||
| -rw-r--r-- | sys/linux/test/amd64-syz_kvm_set_irq_handler | 31 |
4 files changed, 116 insertions, 22 deletions
diff --git a/executor/common_kvm_amd64.h b/executor/common_kvm_amd64.h index ce778792d..3e5200137 100644 --- a/executor/common_kvm_amd64.h +++ b/executor/common_kvm_amd64.h @@ -252,18 +252,6 @@ struct kvm_syz_vm { #endif #if SYZ_EXECUTOR || __NR_syz_kvm_add_vcpu -// See https://wiki.osdev.org/Interrupt_Descriptor_Table#Gate_Descriptor_2. -struct idt_entry_64 { - uint16 offset_low; - uint16 selector; - // Interrupt Stack Table offset in bits 0..2 - uint8 ist; - // Gate Type, P and DPL. - uint8 type_attr; - uint16 offset_mid; - uint32 offset_high; - uint32 reserved; -} __attribute__((packed)); // Post-processing code in pkg/csource/csource.go is very picky and won't let us directly pass // fail() to DEFINE_GUEST_FN_TO_GPA_FN. diff --git a/executor/common_kvm_amd64_syzos.h b/executor/common_kvm_amd64_syzos.h index 9baf9c5f5..d3b9ca421 100644 --- a/executor/common_kvm_amd64_syzos.h +++ b/executor/common_kvm_amd64_syzos.h @@ -22,6 +22,7 @@ typedef enum { SYZOS_API_WR_DRN = 110, SYZOS_API_IN_DX = 130, SYZOS_API_OUT_DX = 170, + SYZOS_API_SET_IRQ_HANDLER = 190, SYZOS_API_STOP, // Must be the last one } syzos_api_id; @@ -61,7 +62,13 @@ struct api_call_3 { uint64 args[3]; }; +#ifdef __cplusplus +extern "C" { +#endif static void guest_uexit(uint64 exit_code); +#ifdef __cplusplus +} +#endif static void guest_execute_code(uint8* insns, uint64 size); static void guest_handle_cpuid(uint32 eax, uint32 ecx); static void guest_handle_wrmsr(uint64 reg, uint64 val); @@ -70,6 +77,7 @@ static void guest_handle_wr_crn(struct api_call_2* cmd); static void guest_handle_wr_drn(struct api_call_2* cmd); static void guest_handle_in_dx(struct api_call_2* cmd); static void guest_handle_out_dx(struct api_call_3* cmd); +static void guest_handle_set_irq_handler(struct api_call_2* cmd); typedef enum { UEXIT_END = (uint64)-1, @@ -84,6 +92,17 @@ dummy_null_handler() asm("iretq"); } +__attribute__((naked)) GUEST_CODE static void uexit_irq_handler() +{ + asm volatile(R"( + // Call guest_uexit(UEXIT_IRQ). + movq $-2, %rdi + call guest_uexit + + iretq + )"); +} + // Main guest function that performs necessary setup and passes the control to the user-provided // payload. __attribute__((used)) @@ -140,6 +159,10 @@ guest_main(uint64 size, uint64 cpu) guest_handle_out_dx((struct api_call_3*)cmd); break; } + case SYZOS_API_SET_IRQ_HANDLER: { + guest_handle_set_irq_handler((struct api_call_2*)cmd); + break; + } } addr += cmd->size; size -= cmd->size; @@ -156,7 +179,12 @@ GUEST_CODE static noinline void guest_execute_code(uint8* insns, uint64 size) // Perform a userspace exit that can be handled by the host. // The host returns from ioctl(KVM_RUN) with kvm_run.exit_reason=KVM_EXIT_MMIO, // and can handle the call depending on the data passed as exit code. -GUEST_CODE static noinline void guest_uexit(uint64 exit_code) + +// Make sure the compiler does not optimize this function away, it is called from +// assembly. +__attribute__((used)) +GUEST_CODE static noinline void +guest_uexit(uint64 exit_code) { volatile uint64* ptr = (volatile uint64*)X86_SYZOS_ADDR_UEXIT; *ptr = exit_code; @@ -322,3 +350,44 @@ GUEST_CODE static noinline void guest_handle_out_dx(struct api_call_3* cmd) return; } } + +// See https://wiki.osdev.org/Interrupt_Descriptor_Table#Gate_Descriptor_2. +struct idt_entry_64 { + uint16 offset_low; + uint16 selector; + // Interrupt Stack Table offset in bits 0..2 + uint8 ist; + // Gate Type, P and DPL. + uint8 type_attr; + uint16 offset_mid; + uint32 offset_high; + uint32 reserved; +} __attribute__((packed)); + +// IDT gate setup should be similar to syzos_setup_idt() in the host code. +GUEST_CODE static void set_idt_gate(uint8 vector, uint64 handler) +{ + volatile struct idt_entry_64* idt = + (volatile struct idt_entry_64*)(X86_SYZOS_ADDR_VAR_IDT); + volatile struct idt_entry_64* idt_entry = &idt[vector]; + idt_entry->offset_low = (uint16)handler; + idt_entry->offset_mid = (uint16)(handler >> 16); + idt_entry->offset_high = (uint32)(handler >> 32); + idt_entry->selector = X86_SYZOS_SEL_CODE; + idt_entry->type_attr = 0x8E; + idt_entry->ist = 0; + idt_entry->reserved = 0; +} + +DEFINE_GUEST_FN_TO_GPA_FN(syzos_fn_address, X86_SYZOS_ADDR_EXECUTOR_CODE, guest_uexit(UEXIT_ASSERT)) +GUEST_CODE static noinline void guest_handle_set_irq_handler(struct api_call_2* cmd) +{ + uint8 vector = (uint8)cmd->args[0]; + uint64 type = cmd->args[1]; + volatile uint64 handler_addr = 0; + if (type == 1) + handler_addr = syzos_fn_address((uintptr_t)dummy_null_handler); + else if (type == 2) + handler_addr = syzos_fn_address((uintptr_t)uexit_irq_handler); + set_idt_gate(vector, handler_addr); +} diff --git a/sys/linux/dev_kvm_amd64.txt b/sys/linux/dev_kvm_amd64.txt index 43474f9f6..ed2880063 100644 --- a/sys/linux/dev_kvm_amd64.txt +++ b/sys/linux/dev_kvm_amd64.txt @@ -89,16 +89,22 @@ syzos_api_out_dx { arg_val int64 } +syzos_api_set_irq_handler { + arg_vector int64[0:255] + arg_handler_type int64[0:2] +} + syzos_api_call$x86 [ - uexit syzos_api$x86[0, intptr] - code syzos_api$x86[10, syzos_api_code$x86] - cpuid syzos_api$x86[20, syzos_api_cpuid] - wrmsr syzos_api$x86[30, syzos_api_wrmsr] - rdmsr syzos_api$x86[50, syzos_api_rdmsr] - wr_crn syzos_api$x86[70, syzos_api_wr_crn] - wr_drn syzos_api$x86[110, syzos_api_wr_drn] - in_dx syzos_api$x86[130, syzos_api_in_dx] - out_dx syzos_api$x86[170, syzos_api_out_dx] + uexit syzos_api$x86[0, intptr] + code syzos_api$x86[10, syzos_api_code$x86] + cpuid syzos_api$x86[20, syzos_api_cpuid] + wrmsr syzos_api$x86[30, syzos_api_wrmsr] + rdmsr syzos_api$x86[50, syzos_api_rdmsr] + wr_crn syzos_api$x86[70, syzos_api_wr_crn] + wr_drn syzos_api$x86[110, syzos_api_wr_drn] + in_dx syzos_api$x86[130, syzos_api_in_dx] + out_dx syzos_api$x86[170, syzos_api_out_dx] + set_irq_handler syzos_api$x86[190, syzos_api_set_irq_handler] ] [varlen] kvm_text_x86 [ diff --git a/sys/linux/test/amd64-syz_kvm_set_irq_handler b/sys/linux/test/amd64-syz_kvm_set_irq_handler new file mode 100644 index 000000000..ad2c9479a --- /dev/null +++ b/sys/linux/test/amd64-syz_kvm_set_irq_handler @@ -0,0 +1,31 @@ +# +# requires: arch=amd64 -threaded +# +r0 = openat$kvm(0, &AUTO='/dev/kvm\x00', 0x0, 0x0) +r1 = ioctl$KVM_CREATE_VM(r0, AUTO, 0x0) +r2 = syz_kvm_setup_syzos_vm$x86(r1, &(0x7f0000c00000/0x400000)=nil) +# +# Set a non-dummy IRQ handler for vector 0x80. +# +r3 = syz_kvm_add_vcpu$x86(r2, &AUTO={0x0, &AUTO=[@set_irq_handler={AUTO, AUTO, {0x80, 0x2}}, @uexit={AUTO, AUTO, 0xaaaa}], AUTO}) +r4 = ioctl$KVM_GET_VCPU_MMAP_SIZE(r0, AUTO) +r5 = mmap$KVM_VCPU(&(0x7f0000009000/0x1000)=nil, r4, 0x3, 0x1, r3, 0x0) + +# Run till uexit(0xaaaa). +# +ioctl$KVM_RUN(r3, AUTO, 0x0) +syz_kvm_assert_syzos_uexit$x86(r5, 0xaaaa) + +# Inject interrupt 0x80. +# +ioctl$KVM_INTERRUPT(r3, AUTO, &AUTO=0x80) +ioctl$KVM_RUN(r3, AUTO, 0x0) + +# Ensure that the uexit code is UEXIT_IRQ. +# +syz_kvm_assert_syzos_uexit$x86(r5, 0xfffffffffffffffe) + +# Run till the end. +# +ioctl$KVM_RUN(r3, AUTO, 0x0) +syz_kvm_assert_syzos_uexit$x86(r5, 0xffffffffffffffff) |
