Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: KVM: Add test to verify KVM doesn't explode on "bad" I/O

Add an x86 selftest to verify that KVM doesn't WARN or otherwise explode
if userspace modifies RCX during a userspace exit to handle string I/O.
This is a regression test for a user-triggerable WARN introduced by
commit 3b27de271839 ("KVM: x86: split the two parts of emulator_pio_in").

Signed-off-by: Sean Christopherson <seanjc@google.com>
Message-Id: <20211025201311.1881846-3-seanjc@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

authored by

Sean Christopherson and committed by
Paolo Bonzini
10e7a099 d07898ea

+116
+1
tools/testing/selftests/kvm/.gitignore
··· 30 30 /x86_64/svm_int_ctl_test 31 31 /x86_64/sync_regs_test 32 32 /x86_64/tsc_msrs_test 33 + /x86_64/userspace_io_test 33 34 /x86_64/userspace_msr_exit_test 34 35 /x86_64/vmx_apic_access_test 35 36 /x86_64/vmx_close_while_nested_test
+1
tools/testing/selftests/kvm/Makefile
··· 59 59 TEST_GEN_PROGS_x86_64 += x86_64/svm_vmcall_test 60 60 TEST_GEN_PROGS_x86_64 += x86_64/svm_int_ctl_test 61 61 TEST_GEN_PROGS_x86_64 += x86_64/sync_regs_test 62 + TEST_GEN_PROGS_x86_64 += x86_64/userspace_io_test 62 63 TEST_GEN_PROGS_x86_64 += x86_64/userspace_msr_exit_test 63 64 TEST_GEN_PROGS_x86_64 += x86_64/vmx_apic_access_test 64 65 TEST_GEN_PROGS_x86_64 += x86_64/vmx_close_while_nested_test
+114
tools/testing/selftests/kvm/x86_64/userspace_io_test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #include <fcntl.h> 3 + #include <stdio.h> 4 + #include <stdlib.h> 5 + #include <string.h> 6 + #include <sys/ioctl.h> 7 + 8 + #include "test_util.h" 9 + 10 + #include "kvm_util.h" 11 + #include "processor.h" 12 + 13 + #define VCPU_ID 1 14 + 15 + static void guest_ins_port80(uint8_t *buffer, unsigned int count) 16 + { 17 + unsigned long end; 18 + 19 + if (count == 2) 20 + end = (unsigned long)buffer + 1; 21 + else 22 + end = (unsigned long)buffer + 8192; 23 + 24 + asm volatile("cld; rep; insb" : "+D"(buffer), "+c"(count) : "d"(0x80) : "memory"); 25 + GUEST_ASSERT_1(count == 0, count); 26 + GUEST_ASSERT_2((unsigned long)buffer == end, buffer, end); 27 + } 28 + 29 + static void guest_code(void) 30 + { 31 + uint8_t buffer[8192]; 32 + int i; 33 + 34 + /* 35 + * Special case tests. main() will adjust RCX 2 => 1 and 3 => 8192 to 36 + * test that KVM doesn't explode when userspace modifies the "count" on 37 + * a userspace I/O exit. KVM isn't required to play nice with the I/O 38 + * itself as KVM doesn't support manipulating the count, it just needs 39 + * to not explode or overflow a buffer. 40 + */ 41 + guest_ins_port80(buffer, 2); 42 + guest_ins_port80(buffer, 3); 43 + 44 + /* Verify KVM fills the buffer correctly when not stuffing RCX. */ 45 + memset(buffer, 0, sizeof(buffer)); 46 + guest_ins_port80(buffer, 8192); 47 + for (i = 0; i < 8192; i++) 48 + GUEST_ASSERT_2(buffer[i] == 0xaa, i, buffer[i]); 49 + 50 + GUEST_DONE(); 51 + } 52 + 53 + int main(int argc, char *argv[]) 54 + { 55 + struct kvm_regs regs; 56 + struct kvm_run *run; 57 + struct kvm_vm *vm; 58 + struct ucall uc; 59 + int rc; 60 + 61 + /* Tell stdout not to buffer its content */ 62 + setbuf(stdout, NULL); 63 + 64 + /* Create VM */ 65 + vm = vm_create_default(VCPU_ID, 0, guest_code); 66 + run = vcpu_state(vm, VCPU_ID); 67 + 68 + memset(&regs, 0, sizeof(regs)); 69 + 70 + while (1) { 71 + rc = _vcpu_run(vm, VCPU_ID); 72 + 73 + TEST_ASSERT(rc == 0, "vcpu_run failed: %d\n", rc); 74 + TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, 75 + "Unexpected exit reason: %u (%s),\n", 76 + run->exit_reason, 77 + exit_reason_str(run->exit_reason)); 78 + 79 + if (get_ucall(vm, VCPU_ID, &uc)) 80 + break; 81 + 82 + TEST_ASSERT(run->io.port == 0x80, 83 + "Expected I/O at port 0x80, got port 0x%x\n", run->io.port); 84 + 85 + /* 86 + * Modify the rep string count in RCX: 2 => 1 and 3 => 8192. 87 + * Note, this abuses KVM's batching of rep string I/O to avoid 88 + * getting stuck in an infinite loop. That behavior isn't in 89 + * scope from a testing perspective as it's not ABI in any way, 90 + * i.e. it really is abusing internal KVM knowledge. 91 + */ 92 + vcpu_regs_get(vm, VCPU_ID, &regs); 93 + if (regs.rcx == 2) 94 + regs.rcx = 1; 95 + if (regs.rcx == 3) 96 + regs.rcx = 8192; 97 + memset((void *)run + run->io.data_offset, 0xaa, 4096); 98 + vcpu_regs_set(vm, VCPU_ID, &regs); 99 + } 100 + 101 + switch (uc.cmd) { 102 + case UCALL_DONE: 103 + break; 104 + case UCALL_ABORT: 105 + TEST_FAIL("%s at %s:%ld : argN+1 = 0x%lx, argN+2 = 0x%lx", 106 + (const char *)uc.args[0], __FILE__, uc.args[1], 107 + uc.args[2], uc.args[3]); 108 + default: 109 + TEST_FAIL("Unknown ucall %lu", uc.cmd); 110 + } 111 + 112 + kvm_vm_free(vm); 113 + return 0; 114 + }