Notes about force_emulation_prefix in KVM.

1. Materials

commit id: 6c86eedc206dd1f9d37a2796faa8e6f2278215d2
https://lore.kernel.org/kvm/1522798129-14588-1-git-send-email-wanpengli@tencent.com/

2. Motivation

There is no easy way to force KVM to run an instruction through the emulator(by design as that will expose the x86 emulator as a significant attack-surface).

However, we do wish to expose the x86 emulator in case we are testing it(e.g. via kvm-unit-tests). Therefore, this patch adds a “force emulation prefix” that is designed to raise #UD which KVM will trap and it’s #UD exit-handler will match “force emulation prefix” to run instruction after prefix by the x86 emulator.

To not expose the x86 emulator by default, we add a module parameter that should be off by default.

The x86 emulator is the function: kvm_emulate_instruction

1
2
kvm_emulate_instruction
x86_emulate_instruction

3. Example

  • use kvm.force_emulation_prefix=1 to enable

emulator.c

1
2
3
4
5
6
7
8
/* Forced emulation prefix, used to invoke the emulator unconditionally.  */
#define KVM_FEP "ud2; .byte 'k', 'v', 'm';"
#define KVM_FEP_LENGTH 5

static int fep_available = 1;

handle_exception(UD_VECTOR, record_no_fep);
asm(KVM_FEP "nop");
1
2
3
4
5
static void record_no_fep(struct ex_regs *regs)
{
fep_available = 0;
regs->rip += KVM_FEP_LENGTH;
}
  • 在guest中不会因为执行asm("nop")而发生VM Exit.
  • 如果kvm.force_emulation_prefix=1 ,那么在kvm unit test中,执行asm(KVM_FEP "nop")的结果就是执行asm("nop")的结果,在guest中不会产生#UD.
  • 如果kvm.force_emulation_prefix=0 ,那么在kvm unit test中,执行asm(KVM_FEP "nop")的结果就是在guest中产生#UD,那么#UD handler会被调用,全局变量fep_available就会被设置为0.

Test cases:

1
2
3
4
5
// https://gitlab.com/kvm-unit-tests/kvm-unit-tests/-/blob/master/x86/emulator.c#L1185-1190
if (fep_available) {
...
test_nop(mem);
...

目的是测试kvm_emulate_instruction是否正确模拟了nop指令。

在guest中不会因为执行asm("nop")而发生VM Exit,就无法测试kvm_emulate_instruction是否正确模拟了nop指令。force_emulation_prefix就帮上了忙,细节请参考下节的内容。

4. Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//https://elixir.bootlin.com/linux/v5.15-rc5/source/arch/x86/kvm/x86.c#L6541
int handle_ud(struct kvm_vcpu *vcpu)
{
static const char kvm_emulate_prefix[] = { __KVM_EMULATE_PREFIX };
int emul_type = EMULTYPE_TRAP_UD;
char sig[5]; /* ud2; .ascii "kvm" */
struct x86_exception e;

if (unlikely(!static_call(kvm_x86_can_emulate_instruction)(vcpu, NULL, 0)))
return 1;

if (force_emulation_prefix &&
kvm_read_guest_virt(vcpu, kvm_get_linear_rip(vcpu),
sig, sizeof(sig), &e) == 0 &&
memcmp(sig, kvm_emulate_prefix, sizeof(sig)) == 0) {
kvm_rip_write(vcpu, kvm_rip_read(vcpu) + sizeof(sig));
emul_type = EMULTYPE_TRAP_UD_FORCED;
}

return kvm_emulate_instruction(vcpu, emul_type);
}

//https://elixir.bootlin.com/linux/v5.15-rc5/source/arch/x86/include/asm/emulate_prefix.h#L12
#define __KVM_EMULATE_PREFIX 0x0f,0x0b,0x6b,0x76,0x6d /* ud2 ; .ascii "kvm" */

在上节的例子中,handle_ud 会执行kvm_rip_write(vcpu, kvm_rip_read(vcpu) + sizeof(sig))kvm_emulate_instruction将模拟nop指令。