Skip to content

Commit 79a14af

Browse files
committed
KVM: nVMX: Synthesize nested VM-Exit for supported emulation intercepts
When emulating an instruction on behalf of L2 that L1 wants to intercept, generate a nested VM-Exit instead of injecting a #UD into L2. Now that (most of) the necessary information is available, synthesizing a VM-Exit isn't terribly difficult. Punt on decoding the ModR/M for descriptor table exits for now. There is no evidence that any hypervisor intercepts descriptor table accesses *and* uses the EXIT_QUALIFICATION to expedite emulation, i.e. it's not worth delaying basic support for. To avoid doing more harm than good, e.g. by putting L2 into an infinite or effectively corrupting its code stream, inject #UD if the instruction length is nonsensical. Link: https://lore.kernel.org/r/20250201015518.689704-11-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
1 parent fbd1e0f commit 79a14af

File tree

1 file changed

+56
-14
lines changed

1 file changed

+56
-14
lines changed

arch/x86/kvm/vmx/vmx.c

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8008,20 +8008,13 @@ static __init void vmx_set_cpu_caps(void)
80088008
}
80098009

80108010
static bool vmx_is_io_intercepted(struct kvm_vcpu *vcpu,
8011-
struct x86_instruction_info *info)
8011+
struct x86_instruction_info *info,
8012+
unsigned long *exit_qualification)
80128013
{
80138014
struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
80148015
unsigned short port;
80158016
int size;
8016-
8017-
if (info->intercept == x86_intercept_in ||
8018-
info->intercept == x86_intercept_ins) {
8019-
port = info->src_val;
8020-
size = info->dst_bytes;
8021-
} else {
8022-
port = info->dst_val;
8023-
size = info->src_bytes;
8024-
}
8017+
bool imm;
80258018

80268019
/*
80278020
* If the 'use IO bitmaps' VM-execution control is 0, IO instruction
@@ -8033,6 +8026,30 @@ static bool vmx_is_io_intercepted(struct kvm_vcpu *vcpu,
80338026
if (!nested_cpu_has(vmcs12, CPU_BASED_USE_IO_BITMAPS))
80348027
return nested_cpu_has(vmcs12, CPU_BASED_UNCOND_IO_EXITING);
80358028

8029+
if (info->intercept == x86_intercept_in ||
8030+
info->intercept == x86_intercept_ins) {
8031+
port = info->src_val;
8032+
size = info->dst_bytes;
8033+
imm = info->src_type == OP_IMM;
8034+
} else {
8035+
port = info->dst_val;
8036+
size = info->src_bytes;
8037+
imm = info->dst_type == OP_IMM;
8038+
}
8039+
8040+
8041+
*exit_qualification = ((unsigned long)port << 16) | (size - 1);
8042+
8043+
if (info->intercept == x86_intercept_ins ||
8044+
info->intercept == x86_intercept_outs)
8045+
*exit_qualification |= BIT(4);
8046+
8047+
if (info->rep_prefix)
8048+
*exit_qualification |= BIT(5);
8049+
8050+
if (imm)
8051+
*exit_qualification |= BIT(6);
8052+
80368053
return nested_vmx_check_io_bitmaps(vcpu, port, size);
80378054
}
80388055

@@ -8042,6 +8059,9 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
80428059
struct x86_exception *exception)
80438060
{
80448061
struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
8062+
unsigned long exit_qualification = 0;
8063+
u32 vm_exit_reason;
8064+
u64 exit_insn_len;
80458065

80468066
switch (info->intercept) {
80478067
case x86_intercept_rdpid:
@@ -8062,8 +8082,10 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
80628082
case x86_intercept_ins:
80638083
case x86_intercept_out:
80648084
case x86_intercept_outs:
8065-
if (!vmx_is_io_intercepted(vcpu, info))
8085+
if (!vmx_is_io_intercepted(vcpu, info, &exit_qualification))
80668086
return X86EMUL_CONTINUE;
8087+
8088+
vm_exit_reason = EXIT_REASON_IO_INSTRUCTION;
80678089
break;
80688090

80698091
case x86_intercept_lgdt:
@@ -8076,11 +8098,25 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
80768098
case x86_intercept_str:
80778099
if (!nested_cpu_has2(vmcs12, SECONDARY_EXEC_DESC))
80788100
return X86EMUL_CONTINUE;
8101+
8102+
if (info->intercept == x86_intercept_lldt ||
8103+
info->intercept == x86_intercept_ltr ||
8104+
info->intercept == x86_intercept_sldt ||
8105+
info->intercept == x86_intercept_str)
8106+
vm_exit_reason = EXIT_REASON_LDTR_TR;
8107+
else
8108+
vm_exit_reason = EXIT_REASON_GDTR_IDTR;
8109+
/*
8110+
* FIXME: Decode the ModR/M to generate the correct exit
8111+
* qualification for memory operands.
8112+
*/
80798113
break;
80808114

80818115
case x86_intercept_hlt:
80828116
if (!nested_cpu_has(vmcs12, CPU_BASED_HLT_EXITING))
80838117
return X86EMUL_CONTINUE;
8118+
8119+
vm_exit_reason = EXIT_REASON_HLT;
80848120
break;
80858121

80868122
case x86_intercept_pause:
@@ -8096,15 +8132,21 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
80968132
!nested_cpu_has(vmcs12, CPU_BASED_PAUSE_EXITING))
80978133
return X86EMUL_CONTINUE;
80988134

8135+
vm_exit_reason = EXIT_REASON_PAUSE_INSTRUCTION;
80998136
break;
81008137

81018138
/* TODO: check more intercepts... */
81028139
default:
8103-
break;
8140+
return X86EMUL_UNHANDLEABLE;
81048141
}
81058142

8106-
/* FIXME: produce nested vmexit and return X86EMUL_INTERCEPTED. */
8107-
return X86EMUL_UNHANDLEABLE;
8143+
exit_insn_len = abs_diff((s64)info->next_rip, (s64)info->rip);
8144+
if (!exit_insn_len || exit_insn_len > X86_MAX_INSTRUCTION_LENGTH)
8145+
return X86EMUL_UNHANDLEABLE;
8146+
8147+
__nested_vmx_vmexit(vcpu, vm_exit_reason, 0, exit_qualification,
8148+
exit_insn_len);
8149+
return X86EMUL_INTERCEPTED;
81088150
}
81098151

81108152
#ifdef CONFIG_X86_64

0 commit comments

Comments
 (0)