aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Potapenko <glider@google.com>2025-10-01 16:35:10 +0200
committerAlexander Potapenko <glider@google.com>2025-10-17 06:51:20 +0000
commite69835fc40b5e00c0996ce3a85d8287eea57d162 (patch)
treed935f0f54b202ec2402f74852f7d44735b567653
parentb2cf58f4862666d3b14f051b1e8cddcde9f8186f (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.h12
-rw-r--r--executor/common_kvm_amd64_syzos.h71
-rw-r--r--sys/linux/dev_kvm_amd64.txt24
-rw-r--r--sys/linux/test/amd64-syz_kvm_set_irq_handler31
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)