Skip to content

Commit d6a7ad2

Browse files
Maxim Levitskybonzini
authored andcommitted
KVM: nVMX: Synthesize nested VM-Exit for supported emulation intercepts
JIRA: https://issues.redhat.com/browse/RHEL-95318 commit 79a14af Author: Sean Christopherson <seanjc@google.com> Date: Fri Jan 31 17:55:17 2025 -0800 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> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 2eee50c commit d6a7ad2

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
@@ -7920,20 +7920,13 @@ static __init void vmx_set_cpu_caps(void)
79207920
}
79217921

79227922
static bool vmx_is_io_intercepted(struct kvm_vcpu *vcpu,
7923-
struct x86_instruction_info *info)
7923+
struct x86_instruction_info *info,
7924+
unsigned long *exit_qualification)
79247925
{
79257926
struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
79267927
unsigned short port;
79277928
int size;
7928-
7929-
if (info->intercept == x86_intercept_in ||
7930-
info->intercept == x86_intercept_ins) {
7931-
port = info->src_val;
7932-
size = info->dst_bytes;
7933-
} else {
7934-
port = info->dst_val;
7935-
size = info->src_bytes;
7936-
}
7929+
bool imm;
79377930

79387931
/*
79397932
* If the 'use IO bitmaps' VM-execution control is 0, IO instruction
@@ -7945,6 +7938,30 @@ static bool vmx_is_io_intercepted(struct kvm_vcpu *vcpu,
79457938
if (!nested_cpu_has(vmcs12, CPU_BASED_USE_IO_BITMAPS))
79467939
return nested_cpu_has(vmcs12, CPU_BASED_UNCOND_IO_EXITING);
79477940

7941+
if (info->intercept == x86_intercept_in ||
7942+
info->intercept == x86_intercept_ins) {
7943+
port = info->src_val;
7944+
size = info->dst_bytes;
7945+
imm = info->src_type == OP_IMM;
7946+
} else {
7947+
port = info->dst_val;
7948+
size = info->src_bytes;
7949+
imm = info->dst_type == OP_IMM;
7950+
}
7951+
7952+
7953+
*exit_qualification = ((unsigned long)port << 16) | (size - 1);
7954+
7955+
if (info->intercept == x86_intercept_ins ||
7956+
info->intercept == x86_intercept_outs)
7957+
*exit_qualification |= BIT(4);
7958+
7959+
if (info->rep_prefix)
7960+
*exit_qualification |= BIT(5);
7961+
7962+
if (imm)
7963+
*exit_qualification |= BIT(6);
7964+
79487965
return nested_vmx_check_io_bitmaps(vcpu, port, size);
79497966
}
79507967

@@ -7954,6 +7971,9 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
79547971
struct x86_exception *exception)
79557972
{
79567973
struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
7974+
unsigned long exit_qualification = 0;
7975+
u32 vm_exit_reason;
7976+
u64 exit_insn_len;
79577977

79587978
switch (info->intercept) {
79597979
case x86_intercept_rdpid:
@@ -7974,8 +7994,10 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
79747994
case x86_intercept_ins:
79757995
case x86_intercept_out:
79767996
case x86_intercept_outs:
7977-
if (!vmx_is_io_intercepted(vcpu, info))
7997+
if (!vmx_is_io_intercepted(vcpu, info, &exit_qualification))
79787998
return X86EMUL_CONTINUE;
7999+
8000+
vm_exit_reason = EXIT_REASON_IO_INSTRUCTION;
79798001
break;
79808002

79818003
case x86_intercept_lgdt:
@@ -7988,11 +8010,25 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
79888010
case x86_intercept_str:
79898011
if (!nested_cpu_has2(vmcs12, SECONDARY_EXEC_DESC))
79908012
return X86EMUL_CONTINUE;
8013+
8014+
if (info->intercept == x86_intercept_lldt ||
8015+
info->intercept == x86_intercept_ltr ||
8016+
info->intercept == x86_intercept_sldt ||
8017+
info->intercept == x86_intercept_str)
8018+
vm_exit_reason = EXIT_REASON_LDTR_TR;
8019+
else
8020+
vm_exit_reason = EXIT_REASON_GDTR_IDTR;
8021+
/*
8022+
* FIXME: Decode the ModR/M to generate the correct exit
8023+
* qualification for memory operands.
8024+
*/
79918025
break;
79928026

79938027
case x86_intercept_hlt:
79948028
if (!nested_cpu_has(vmcs12, CPU_BASED_HLT_EXITING))
79958029
return X86EMUL_CONTINUE;
8030+
8031+
vm_exit_reason = EXIT_REASON_HLT;
79968032
break;
79978033

79988034
case x86_intercept_pause:
@@ -8008,15 +8044,21 @@ int vmx_check_intercept(struct kvm_vcpu *vcpu,
80088044
!nested_cpu_has(vmcs12, CPU_BASED_PAUSE_EXITING))
80098045
return X86EMUL_CONTINUE;
80108046

8047+
vm_exit_reason = EXIT_REASON_PAUSE_INSTRUCTION;
80118048
break;
80128049

80138050
/* TODO: check more intercepts... */
80148051
default:
8015-
break;
8052+
return X86EMUL_UNHANDLEABLE;
80168053
}
80178054

8018-
/* FIXME: produce nested vmexit and return X86EMUL_INTERCEPTED. */
8019-
return X86EMUL_UNHANDLEABLE;
8055+
exit_insn_len = abs_diff((s64)info->next_rip, (s64)info->rip);
8056+
if (!exit_insn_len || exit_insn_len > X86_MAX_INSTRUCTION_LENGTH)
8057+
return X86EMUL_UNHANDLEABLE;
8058+
8059+
__nested_vmx_vmexit(vcpu, vm_exit_reason, 0, exit_qualification,
8060+
exit_insn_len);
8061+
return X86EMUL_INTERCEPTED;
80208062
}
80218063

80228064
#ifdef CONFIG_X86_64

0 commit comments

Comments
 (0)