Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions executor/common_kvm_amd64_syzos.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ typedef enum {
SYZOS_API_NESTED_VMLAUNCH = 303,
SYZOS_API_NESTED_VMRESUME = 304,
SYZOS_API_NESTED_INTEL_VMWRITE_MASK = 340,
SYZOS_API_NESTED_AMD_VMCB_WRITE_MASK = 380,
SYZOS_API_STOP, // Must be the last one
} syzos_api_id;

Expand Down Expand Up @@ -111,6 +112,7 @@ GUEST_CODE static void guest_handle_nested_load_code(struct api_call_nested_load
GUEST_CODE static void guest_handle_nested_vmlaunch(struct api_call_1* cmd, uint64 cpu_id);
GUEST_CODE static void guest_handle_nested_vmresume(struct api_call_1* cmd, uint64 cpu_id);
GUEST_CODE static void guest_handle_nested_intel_vmwrite_mask(struct api_call_5* cmd, uint64 cpu_id);
GUEST_CODE static void guest_handle_nested_amd_vmcb_write_mask(struct api_call_5* cmd, uint64 cpu_id);

typedef enum {
UEXIT_END = (uint64)-1,
Expand Down Expand Up @@ -223,6 +225,9 @@ guest_main(uint64 size, uint64 cpu)
} else if (call == SYZOS_API_NESTED_INTEL_VMWRITE_MASK) {
// Write to a VMCS field using masks.
guest_handle_nested_intel_vmwrite_mask((struct api_call_5*)cmd, cpu);
} else if (call == SYZOS_API_NESTED_AMD_VMCB_WRITE_MASK) {
// Write to a VMCB field using masks.
guest_handle_nested_amd_vmcb_write_mask((struct api_call_5*)cmd, cpu);
}
addr += cmd->size;
size -= cmd->size;
Expand Down Expand Up @@ -1256,4 +1261,22 @@ guest_handle_nested_intel_vmwrite_mask(struct api_call_5* cmd, uint64 cpu_id)
vmwrite(field, new_value);
}

GUEST_CODE static noinline void
guest_handle_nested_amd_vmcb_write_mask(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 set_mask = cmd->args[2];
uint64 unset_mask = cmd->args[3];
uint64 flip_mask = cmd->args[4];

uint64 current_value = vmcb_read64((volatile uint8*)vmcb_addr, offset);
uint64 new_value = (current_value & ~unset_mask) | set_mask;
new_value ^= flip_mask;
vmcb_write64(vmcb_addr, offset, new_value);
}

#endif // EXECUTOR_COMMON_KVM_AMD64_SYZOS_H
16 changes: 16 additions & 0 deletions sys/linux/dev_kvm_amd64.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ syzos_api_nested_intel_vmwrite_mask {
flip_mask int64
}

# See AMD Secure Virtual Machine Architecture Reference Manual,
# Appendix C: Layout of VMCB.
vmcb_offset [
control_area int64[0x0:0xc0]
save_area int64[0x400:0x698]
]

syzos_api_nested_amd_vmcb_write_mask {
vm_id syzos_api_vm_id
offset vmcb_offset
set_mask int64
unset_mask int64
flip_mask int64
}

# IDs here must match those in executor/common_kvm_amd64_syzos.h.
syzos_api_call$x86 [
uexit syzos_api$x86[0, intptr]
Expand All @@ -149,6 +164,7 @@ syzos_api_call$x86 [
nested_vmlaunch syzos_api$x86[303, syzos_api_vm_id]
nested_vmresume syzos_api$x86[304, syzos_api_vm_id]
nested_intel_vmwrite_mask syzos_api$x86[340, syzos_api_nested_intel_vmwrite_mask]
nested_amd_vmcb_write_mask syzos_api$x86[380, syzos_api_nested_amd_vmcb_write_mask]
] [varlen]

kvm_text_x86 [
Expand Down
43 changes: 43 additions & 0 deletions sys/linux/test/amd64-syz_kvm_nested_amd_vmcb_write_mask
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#
# 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)

# Create a nested AMD VM that performs HLT and INVD to test vmcb_write_mask.
# This is the AMD equivalent of the amd64-syz_kvm_nested_vmwrite_mask test.
# 1. L2 executes HLT -> exit to L1 (HLT is intercepted by default).
# 2. L1 disables HLT intercept via vmcb_write_mask.
# 3. L1 resumes L2.
# 4. L2 executes HLT (no L2->L1 exit), causing an L1->L0 KVM_EXIT_HLT.
# 5. L0 resumes L1, which resumes L2.
# 6. L2 executes INVD -> exit to L1.
#
# The HLT intercept bit is bit 24 in the VMCB's intercept vector 3 (offset 0xC).
# We unset this bit (0x1000000).
#
r3 = syz_kvm_add_vcpu$x86(r2, &AUTO={0x0, &AUTO=[@enable_nested={AUTO, AUTO, 0x0}, @nested_create_vm={AUTO, AUTO, 0x0}, @nested_load_code={AUTO, AUTO, {0x0, "f40f08"}}, @nested_vmlaunch={AUTO, AUTO, 0x0}, @nested_amd_vmcb_write_mask={AUTO, AUTO, {0x0, @control_area=0xc, 0x0, 0x1000000, 0x0}}, @nested_vmresume={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)

# L2 VM executes HLT. Exit reason is mapped to 0xe2e20001.
#
ioctl$KVM_RUN(r3, AUTO, 0x0)
syz_kvm_assert_syzos_uexit$x86(r5, 0xe2e20001)

# L1 disables HLT intercept and resumes L2. L2 executes HLT, causing KVM_EXIT_HLT (0x5) from L1 to L0.
# This confirms that the vmcb_write_mask command was successful.
#
ioctl$KVM_RUN(r3, AUTO, 0x0)
syz_kvm_assert_syzos_kvm_exit$x86(r5, 0x5)

# After resuming, L2 executes INVD. Exit reason is mapped to 0xe2e20002.
#
ioctl$KVM_RUN(r3, AUTO, 0x0)
syz_kvm_assert_syzos_uexit$x86(r5, 0xe2e20002)

# guest_main should finish with guest_uexit(-1).
#
ioctl$KVM_RUN(r3, AUTO, 0x0)
syz_kvm_assert_syzos_uexit$x86(r5, 0xffffffff)