diff options
| author | Alexander Potapenko <glider@google.com> | 2026-01-19 09:38:53 +0100 |
|---|---|---|
| committer | Alexander Potapenko <glider@google.com> | 2026-01-19 11:05:35 +0000 |
| commit | e2d17597c5c0f0be4b4b6e992d9434edef02c2f0 (patch) | |
| tree | a2c78281c94c7a8e244863b40672d07afb3e97f0 | |
| parent | 8a9c6fb791de2ce75347ae863885b4584ac82afe (diff) | |
executor: sys/linux: SYZOS: add AMD SET_INTERCEPT primitive
This patch introduces SYZOS_API_NESTED_AMD_SET_INTERCEPT to SYZOS.
This primitive enables the fuzzer to surgically modify intercept vectors
in the AMD VMCB (Virtual Machine Control Block) Control Area.
It implements a read-modify-write operation on 32-bit VMCB offsets,
allowing the L1 hypervisor (SYZOS) to deterministically set or clear
specific intercept bits (e.g., for RDTSC, HLT, or exceptions) for the L2
guest.
This capability allows syzkaller to systematically explore KVM's nested
SVM emulation logic by toggling intercepts on and off, rather than
relying on static defaults or random memory corruption.
| -rw-r--r-- | executor/common_kvm_amd64_syzos.h | 33 | ||||
| -rw-r--r-- | sys/linux/dev_kvm_amd64.txt | 8 | ||||
| -rw-r--r-- | sys/linux/test/amd64-syz_kvm_nested_amd_set_intercept | 39 |
3 files changed, 80 insertions, 0 deletions
diff --git a/executor/common_kvm_amd64_syzos.h b/executor/common_kvm_amd64_syzos.h index 846569af5..31f62a79c 100644 --- a/executor/common_kvm_amd64_syzos.h +++ b/executor/common_kvm_amd64_syzos.h @@ -37,6 +37,7 @@ typedef enum { SYZOS_API_NESTED_AMD_STGI = 382, SYZOS_API_NESTED_AMD_CLGI = 383, SYZOS_API_NESTED_AMD_INJECT_EVENT = 384, + SYZOS_API_NESTED_AMD_SET_INTERCEPT = 385, SYZOS_API_STOP, // Must be the last one } syzos_api_id; @@ -121,6 +122,7 @@ GUEST_CODE static void guest_handle_nested_amd_invlpga(struct api_call_2* cmd, u GUEST_CODE static void guest_handle_nested_amd_stgi(); GUEST_CODE static void guest_handle_nested_amd_clgi(); GUEST_CODE static void guest_handle_nested_amd_inject_event(struct api_call_5* cmd, uint64 cpu_id); +GUEST_CODE static void guest_handle_nested_amd_set_intercept(struct api_call_5* cmd, uint64 cpu_id); typedef enum { UEXIT_END = (uint64)-1, @@ -248,6 +250,9 @@ guest_main(uint64 size, uint64 cpu) } else if (call == SYZOS_API_NESTED_AMD_INJECT_EVENT) { // Inject an event (IRQ/Exception) into the L2 guest via VMCB. guest_handle_nested_amd_inject_event((struct api_call_5*)cmd, cpu); + } else if (call == SYZOS_API_NESTED_AMD_SET_INTERCEPT) { + // Set/Clear specific intercept bits in the VMCB. + guest_handle_nested_amd_set_intercept((struct api_call_5*)cmd, cpu); } addr += cmd->size; size -= cmd->size; @@ -570,6 +575,11 @@ GUEST_CODE static noinline void vmcb_write32(uint64 vmcb, uint16 offset, uint32 *((volatile uint32*)(vmcb + offset)) = val; } +GUEST_CODE static noinline uint32 vmcb_read32(uint64 vmcb, uint16 offset) +{ + return *((volatile uint32*)(vmcb + offset)); +} + GUEST_CODE static noinline void vmcb_write64(uint64 vmcb, uint16 offset, uint64 val) { *((volatile uint64*)(vmcb + offset)) = val; @@ -1359,4 +1369,27 @@ guest_handle_nested_amd_inject_event(struct api_call_5* cmd, uint64 cpu_id) vmcb_write64(vmcb_addr, 0x60, event_inj); } +GUEST_CODE static noinline void +guest_handle_nested_amd_set_intercept(struct api_call_5* cmd, uint64 cpu_id) +{ + if (get_cpu_vendor() != CPU_VENDOR_AMD) + return; + + uint64 vm_id = cmd->args[0]; + uint64 vmcb_addr = X86_SYZOS_ADDR_VMCS_VMCB(cpu_id, vm_id); + uint64 offset = cmd->args[1]; + uint64 bit_mask = cmd->args[2]; + uint64 action = cmd->args[3]; // 1 = Set, 0 = Clear + + // Read 32-bit intercept field (Offsets 0x00 - 0x14 are all 32-bit vectors). + uint32 current = vmcb_read32(vmcb_addr, (uint16)offset); + + if (action == 1) + current |= (uint32)bit_mask; + else + current &= ~((uint32)bit_mask); + + vmcb_write32(vmcb_addr, (uint16)offset, current); +} + #endif // EXECUTOR_COMMON_KVM_AMD64_SYZOS_H diff --git a/sys/linux/dev_kvm_amd64.txt b/sys/linux/dev_kvm_amd64.txt index acd80540c..0d90ceeab 100644 --- a/sys/linux/dev_kvm_amd64.txt +++ b/sys/linux/dev_kvm_amd64.txt @@ -160,6 +160,13 @@ syzos_api_nested_amd_inject_event { flags int64[0:3] } +syzos_api_nested_amd_set_intercept { + vm_id syzos_api_vm_id + offset int64 + bit_mask int64 + action int64[0:1] +} + # IDs here must match those in executor/common_kvm_amd64_syzos.h. syzos_api_call$x86 [ uexit syzos_api$x86[0, intptr] @@ -183,6 +190,7 @@ syzos_api_call$x86 [ nested_amd_stgi syzos_api$x86[382, void] nested_amd_clgi syzos_api$x86[383, void] nested_amd_inject_event syzos_api$x86[384, syzos_api_nested_amd_inject_event] + nested_amd_set_intercept syzos_api$x86[385, syzos_api_nested_amd_set_intercept] ] [varlen] kvm_text_x86 [ diff --git a/sys/linux/test/amd64-syz_kvm_nested_amd_set_intercept b/sys/linux/test/amd64-syz_kvm_nested_amd_set_intercept new file mode 100644 index 000000000..91293ba0d --- /dev/null +++ b/sys/linux/test/amd64-syz_kvm_nested_amd_set_intercept @@ -0,0 +1,39 @@ +# +# 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) + +# Test AMD Nested Intercepts: The RDTSC Liberation +# +# 1. L1 Setup: +# - SYZOS initializes VMCB with ALL intercepts enabled. +# - We call SET_INTERCEPT to *CLEAR* the RDTSC intercept. +# - Offset: 0xC (Control Vector 1) +# - Bit: 14 (0x4000) = RDTSC +# - Action: 0 (Clear/Disable) +# +# 2. L2 Payload: +# - "rdtsc" (0x0f 0x31) +# - "hlt" (0xf4) +# +# 3. Execution Flow: +# - If RDTSC is intercepted: Exit Reason = RDTSC (0x4). +# - If RDTSC is native: executes RDTSC -> executes HLT -> Exit Reason = HLT (0x1). +# +r3 = syz_kvm_add_vcpu$x86(r2, &AUTO={0x0, &AUTO=[@enable_nested={AUTO, AUTO, 0x0}, @nested_create_vm={AUTO, AUTO, 0x0}, @nested_amd_set_intercept={AUTO, AUTO, {0x0, 0xC, 0x4000, 0x0}}, @nested_load_code={AUTO, AUTO, {0x0, "0f31f4"}}, @nested_vmlaunch={AUTO, AUTO, 0x0}], AUTO}) +r4 = ioctl$KVM_GET_VCPU_MMAP_SIZE(r0, AUTO) +r5 = mmap$KVM_VCPU(&(0x7f0000009000/0x1000)=nil, r4, 0x3, 0x1, r3, 0x0) + +# Run the VCPU. +# Expectation: Success (Native Execution) -> HLT Exit. +# Code: 0xe2e20001 (SYZOS_NESTED_EXIT_REASON_HLT) +# +ioctl$KVM_RUN(r3, AUTO, 0x0) +syz_kvm_assert_syzos_uexit$x86(r3, r5, 0xe2e20001) + +# Cleanup +# +ioctl$KVM_RUN(r3, AUTO, 0x0) +syz_kvm_assert_syzos_uexit$x86(r3, r5, 0xffffffffffffffff) |
