Skip to content

Commit 10e7a09

Browse files
sean-jcbonzini
authored andcommitted
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 3b27de2 ("KVM: x86: split the two parts of emulator_pio_in"). Signed-off-by: Sean Christopherson <[email protected]> Message-Id: <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]>
1 parent d07898e commit 10e7a09

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

tools/testing/selftests/kvm/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
/x86_64/svm_int_ctl_test
3131
/x86_64/sync_regs_test
3232
/x86_64/tsc_msrs_test
33+
/x86_64/userspace_io_test
3334
/x86_64/userspace_msr_exit_test
3435
/x86_64/vmx_apic_access_test
3536
/x86_64/vmx_close_while_nested_test

tools/testing/selftests/kvm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/vmx_preemption_timer_test
5959
TEST_GEN_PROGS_x86_64 += x86_64/svm_vmcall_test
6060
TEST_GEN_PROGS_x86_64 += x86_64/svm_int_ctl_test
6161
TEST_GEN_PROGS_x86_64 += x86_64/sync_regs_test
62+
TEST_GEN_PROGS_x86_64 += x86_64/userspace_io_test
6263
TEST_GEN_PROGS_x86_64 += x86_64/userspace_msr_exit_test
6364
TEST_GEN_PROGS_x86_64 += x86_64/vmx_apic_access_test
6465
TEST_GEN_PROGS_x86_64 += x86_64/vmx_close_while_nested_test
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
}

0 commit comments

Comments
 (0)