Skip to content

Commit 7a35e51

Browse files
vittyvkbonzini
authored andcommitted
KVM: VMX: Properly handle kvm_read/write_guest_virt*() result
Syzbot reports the following issue: WARNING: CPU: 0 PID: 6819 at arch/x86/kvm/x86.c:618 kvm_inject_emulated_page_fault+0x210/0x290 arch/x86/kvm/x86.c:618 ... Call Trace: ... RIP: 0010:kvm_inject_emulated_page_fault+0x210/0x290 arch/x86/kvm/x86.c:618 ... nested_vmx_get_vmptr+0x1f9/0x2a0 arch/x86/kvm/vmx/nested.c:4638 handle_vmon arch/x86/kvm/vmx/nested.c:4767 [inline] handle_vmon+0x168/0x3a0 arch/x86/kvm/vmx/nested.c:4728 vmx_handle_exit+0x29c/0x1260 arch/x86/kvm/vmx/vmx.c:6067 'exception' we're trying to inject with kvm_inject_emulated_page_fault() comes from: nested_vmx_get_vmptr() kvm_read_guest_virt() kvm_read_guest_virt_helper() vcpu->arch.walk_mmu->gva_to_gpa() but it is only set when GVA to GPA conversion fails. In case it doesn't but we still fail kvm_vcpu_read_guest_page(), X86EMUL_IO_NEEDED is returned and nested_vmx_get_vmptr() calls kvm_inject_emulated_page_fault() with zeroed 'exception'. This happen when the argument is MMIO. Paolo also noticed that nested_vmx_get_vmptr() is not the only place in KVM code where kvm_read/write_guest_virt*() return result is mishandled. VMX instructions along with INVPCID have the same issue. This was already noticed before, e.g. see commit 541ab2a ("KVM: x86: work around leak of uninitialized stack contents") but was never fully fixed. KVM could've handled the request correctly by going to userspace and performing I/O but there doesn't seem to be a good need for such requests in the first place. Introduce vmx_handle_memory_failure() as an interim solution. Note, nested_vmx_get_vmptr() now has three possible outcomes: OK, PF, KVM_EXIT_INTERNAL_ERROR and callers need to know if userspace exit is needed (for KVM_EXIT_INTERNAL_ERROR) in case of failure. We don't seem to have a good enum describing this tristate, just add "int *ret" to nested_vmx_get_vmptr() interface to pass the information. Reported-by: [email protected] Suggested-by: Sean Christopherson <[email protected]> Signed-off-by: Vitaly Kuznetsov <[email protected]> Message-Id: <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]>
1 parent 34d2618 commit 7a35e51

File tree

3 files changed

+74
-40
lines changed

3 files changed

+74
-40
lines changed

arch/x86/kvm/vmx/nested.c

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4624,19 +4624,24 @@ void nested_vmx_pmu_entry_exit_ctls_update(struct kvm_vcpu *vcpu)
46244624
}
46254625
}
46264626

4627-
static int nested_vmx_get_vmptr(struct kvm_vcpu *vcpu, gpa_t *vmpointer)
4627+
static int nested_vmx_get_vmptr(struct kvm_vcpu *vcpu, gpa_t *vmpointer,
4628+
int *ret)
46284629
{
46294630
gva_t gva;
46304631
struct x86_exception e;
4632+
int r;
46314633

46324634
if (get_vmx_mem_address(vcpu, vmx_get_exit_qual(vcpu),
46334635
vmcs_read32(VMX_INSTRUCTION_INFO), false,
4634-
sizeof(*vmpointer), &gva))
4635-
return 1;
4636+
sizeof(*vmpointer), &gva)) {
4637+
*ret = 1;
4638+
return -EINVAL;
4639+
}
46364640

4637-
if (kvm_read_guest_virt(vcpu, gva, vmpointer, sizeof(*vmpointer), &e)) {
4638-
kvm_inject_emulated_page_fault(vcpu, &e);
4639-
return 1;
4641+
r = kvm_read_guest_virt(vcpu, gva, vmpointer, sizeof(*vmpointer), &e);
4642+
if (r != X86EMUL_CONTINUE) {
4643+
*ret = vmx_handle_memory_failure(vcpu, r, &e);
4644+
return -EINVAL;
46404645
}
46414646

46424647
return 0;
@@ -4764,8 +4769,8 @@ static int handle_vmon(struct kvm_vcpu *vcpu)
47644769
return 1;
47654770
}
47664771

4767-
if (nested_vmx_get_vmptr(vcpu, &vmptr))
4768-
return 1;
4772+
if (nested_vmx_get_vmptr(vcpu, &vmptr, &ret))
4773+
return ret;
47694774

47704775
/*
47714776
* SDM 3: 24.11.5
@@ -4838,12 +4843,13 @@ static int handle_vmclear(struct kvm_vcpu *vcpu)
48384843
u32 zero = 0;
48394844
gpa_t vmptr;
48404845
u64 evmcs_gpa;
4846+
int r;
48414847

48424848
if (!nested_vmx_check_permission(vcpu))
48434849
return 1;
48444850

4845-
if (nested_vmx_get_vmptr(vcpu, &vmptr))
4846-
return 1;
4851+
if (nested_vmx_get_vmptr(vcpu, &vmptr, &r))
4852+
return r;
48474853

48484854
if (!page_address_valid(vcpu, vmptr))
48494855
return nested_vmx_failValid(vcpu,
@@ -4902,7 +4908,7 @@ static int handle_vmread(struct kvm_vcpu *vcpu)
49024908
u64 value;
49034909
gva_t gva = 0;
49044910
short offset;
4905-
int len;
4911+
int len, r;
49064912

49074913
if (!nested_vmx_check_permission(vcpu))
49084914
return 1;
@@ -4943,10 +4949,9 @@ static int handle_vmread(struct kvm_vcpu *vcpu)
49434949
instr_info, true, len, &gva))
49444950
return 1;
49454951
/* _system ok, nested_vmx_check_permission has verified cpl=0 */
4946-
if (kvm_write_guest_virt_system(vcpu, gva, &value, len, &e)) {
4947-
kvm_inject_emulated_page_fault(vcpu, &e);
4948-
return 1;
4949-
}
4952+
r = kvm_write_guest_virt_system(vcpu, gva, &value, len, &e);
4953+
if (r != X86EMUL_CONTINUE)
4954+
return vmx_handle_memory_failure(vcpu, r, &e);
49504955
}
49514956

49524957
return nested_vmx_succeed(vcpu);
@@ -4987,7 +4992,7 @@ static int handle_vmwrite(struct kvm_vcpu *vcpu)
49874992
unsigned long field;
49884993
short offset;
49894994
gva_t gva;
4990-
int len;
4995+
int len, r;
49914996

49924997
/*
49934998
* The value to write might be 32 or 64 bits, depending on L1's long
@@ -5017,10 +5022,9 @@ static int handle_vmwrite(struct kvm_vcpu *vcpu)
50175022
if (get_vmx_mem_address(vcpu, exit_qualification,
50185023
instr_info, false, len, &gva))
50195024
return 1;
5020-
if (kvm_read_guest_virt(vcpu, gva, &value, len, &e)) {
5021-
kvm_inject_emulated_page_fault(vcpu, &e);
5022-
return 1;
5023-
}
5025+
r = kvm_read_guest_virt(vcpu, gva, &value, len, &e);
5026+
if (r != X86EMUL_CONTINUE)
5027+
return vmx_handle_memory_failure(vcpu, r, &e);
50245028
}
50255029

50265030
field = kvm_register_readl(vcpu, (((instr_info) >> 28) & 0xf));
@@ -5103,12 +5107,13 @@ static int handle_vmptrld(struct kvm_vcpu *vcpu)
51035107
{
51045108
struct vcpu_vmx *vmx = to_vmx(vcpu);
51055109
gpa_t vmptr;
5110+
int r;
51065111

51075112
if (!nested_vmx_check_permission(vcpu))
51085113
return 1;
51095114

5110-
if (nested_vmx_get_vmptr(vcpu, &vmptr))
5111-
return 1;
5115+
if (nested_vmx_get_vmptr(vcpu, &vmptr, &r))
5116+
return r;
51125117

51135118
if (!page_address_valid(vcpu, vmptr))
51145119
return nested_vmx_failValid(vcpu,
@@ -5170,6 +5175,7 @@ static int handle_vmptrst(struct kvm_vcpu *vcpu)
51705175
gpa_t current_vmptr = to_vmx(vcpu)->nested.current_vmptr;
51715176
struct x86_exception e;
51725177
gva_t gva;
5178+
int r;
51735179

51745180
if (!nested_vmx_check_permission(vcpu))
51755181
return 1;
@@ -5181,11 +5187,11 @@ static int handle_vmptrst(struct kvm_vcpu *vcpu)
51815187
true, sizeof(gpa_t), &gva))
51825188
return 1;
51835189
/* *_system ok, nested_vmx_check_permission has verified cpl=0 */
5184-
if (kvm_write_guest_virt_system(vcpu, gva, (void *)&current_vmptr,
5185-
sizeof(gpa_t), &e)) {
5186-
kvm_inject_emulated_page_fault(vcpu, &e);
5187-
return 1;
5188-
}
5190+
r = kvm_write_guest_virt_system(vcpu, gva, (void *)&current_vmptr,
5191+
sizeof(gpa_t), &e);
5192+
if (r != X86EMUL_CONTINUE)
5193+
return vmx_handle_memory_failure(vcpu, r, &e);
5194+
51895195
return nested_vmx_succeed(vcpu);
51905196
}
51915197

@@ -5209,7 +5215,7 @@ static int handle_invept(struct kvm_vcpu *vcpu)
52095215
struct {
52105216
u64 eptp, gpa;
52115217
} operand;
5212-
int i;
5218+
int i, r;
52135219

52145220
if (!(vmx->nested.msrs.secondary_ctls_high &
52155221
SECONDARY_EXEC_ENABLE_EPT) ||
@@ -5236,10 +5242,9 @@ static int handle_invept(struct kvm_vcpu *vcpu)
52365242
if (get_vmx_mem_address(vcpu, vmx_get_exit_qual(vcpu),
52375243
vmx_instruction_info, false, sizeof(operand), &gva))
52385244
return 1;
5239-
if (kvm_read_guest_virt(vcpu, gva, &operand, sizeof(operand), &e)) {
5240-
kvm_inject_emulated_page_fault(vcpu, &e);
5241-
return 1;
5242-
}
5245+
r = kvm_read_guest_virt(vcpu, gva, &operand, sizeof(operand), &e);
5246+
if (r != X86EMUL_CONTINUE)
5247+
return vmx_handle_memory_failure(vcpu, r, &e);
52435248

52445249
/*
52455250
* Nested EPT roots are always held through guest_mmu,
@@ -5291,6 +5296,7 @@ static int handle_invvpid(struct kvm_vcpu *vcpu)
52915296
u64 gla;
52925297
} operand;
52935298
u16 vpid02;
5299+
int r;
52945300

52955301
if (!(vmx->nested.msrs.secondary_ctls_high &
52965302
SECONDARY_EXEC_ENABLE_VPID) ||
@@ -5318,10 +5324,10 @@ static int handle_invvpid(struct kvm_vcpu *vcpu)
53185324
if (get_vmx_mem_address(vcpu, vmx_get_exit_qual(vcpu),
53195325
vmx_instruction_info, false, sizeof(operand), &gva))
53205326
return 1;
5321-
if (kvm_read_guest_virt(vcpu, gva, &operand, sizeof(operand), &e)) {
5322-
kvm_inject_emulated_page_fault(vcpu, &e);
5323-
return 1;
5324-
}
5327+
r = kvm_read_guest_virt(vcpu, gva, &operand, sizeof(operand), &e);
5328+
if (r != X86EMUL_CONTINUE)
5329+
return vmx_handle_memory_failure(vcpu, r, &e);
5330+
53255331
if (operand.vpid >> 16)
53265332
return nested_vmx_failValid(vcpu,
53275333
VMXERR_INVALID_OPERAND_TO_INVEPT_INVVPID);

arch/x86/kvm/vmx/vmx.c

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,32 @@ static int skip_emulated_instruction(struct kvm_vcpu *vcpu)
16001600
return 1;
16011601
}
16021602

1603+
/*
1604+
* Handles kvm_read/write_guest_virt*() result and either injects #PF or returns
1605+
* KVM_EXIT_INTERNAL_ERROR for cases not currently handled by KVM. Return value
1606+
* indicates whether exit to userspace is needed.
1607+
*/
1608+
int vmx_handle_memory_failure(struct kvm_vcpu *vcpu, int r,
1609+
struct x86_exception *e)
1610+
{
1611+
if (r == X86EMUL_PROPAGATE_FAULT) {
1612+
kvm_inject_emulated_page_fault(vcpu, e);
1613+
return 1;
1614+
}
1615+
1616+
/*
1617+
* In case kvm_read/write_guest_virt*() failed with X86EMUL_IO_NEEDED
1618+
* while handling a VMX instruction KVM could've handled the request
1619+
* correctly by exiting to userspace and performing I/O but there
1620+
* doesn't seem to be a real use-case behind such requests, just return
1621+
* KVM_EXIT_INTERNAL_ERROR for now.
1622+
*/
1623+
vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
1624+
vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION;
1625+
vcpu->run->internal.ndata = 0;
1626+
1627+
return 0;
1628+
}
16031629

16041630
/*
16051631
* Recognizes a pending MTF VM-exit and records the nested state for later
@@ -5486,6 +5512,7 @@ static int handle_invpcid(struct kvm_vcpu *vcpu)
54865512
u64 pcid;
54875513
u64 gla;
54885514
} operand;
5515+
int r;
54895516

54905517
if (!guest_cpuid_has(vcpu, X86_FEATURE_INVPCID)) {
54915518
kvm_queue_exception(vcpu, UD_VECTOR);
@@ -5508,10 +5535,9 @@ static int handle_invpcid(struct kvm_vcpu *vcpu)
55085535
sizeof(operand), &gva))
55095536
return 1;
55105537

5511-
if (kvm_read_guest_virt(vcpu, gva, &operand, sizeof(operand), &e)) {
5512-
kvm_inject_emulated_page_fault(vcpu, &e);
5513-
return 1;
5514-
}
5538+
r = kvm_read_guest_virt(vcpu, gva, &operand, sizeof(operand), &e);
5539+
if (r != X86EMUL_CONTINUE)
5540+
return vmx_handle_memory_failure(vcpu, r, &e);
55155541

55165542
if (operand.pcid >> 12 != 0) {
55175543
kvm_inject_gp(vcpu, 0);

arch/x86/kvm/vmx/vmx.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ struct shared_msr_entry *find_msr_entry(struct vcpu_vmx *vmx, u32 msr);
355355
void pt_update_intercept_for_msr(struct vcpu_vmx *vmx);
356356
void vmx_update_host_rsp(struct vcpu_vmx *vmx, unsigned long host_rsp);
357357
int vmx_find_msr_index(struct vmx_msrs *m, u32 msr);
358+
int vmx_handle_memory_failure(struct kvm_vcpu *vcpu, int r,
359+
struct x86_exception *e);
358360

359361
#define POSTED_INTR_ON 0
360362
#define POSTED_INTR_SN 1

0 commit comments

Comments
 (0)