Re: RFC: Task switch emulation fails for VM86 mode
From: Nadav Amit
Date: Mon Jul 10 2017 - 11:48:01 EST
Wanpeng Li <kernellwp@xxxxxxxxx> wrote:
> Cc Nadav, Jan,
> 2017-07-09 15:30 GMT+08:00 Wanpeng Li <kernellwp@xxxxxxxxx>:
>> Hi all,
>>
>> I found that task switch emulation fails to work for VM86 mode if
>> guest state is invalid. It can be reproduced by running
>> kvm-unit-tests/taskswitch2.flat, EPT = 0 or EPT=1,
>> unrestricted_guest=N, emulate_invalid_guest_state=Y.
>>
>> When EPT=1, unrestricted_guest=Y, emulate_invalid_state=Y, the trace
>> of kvm-unit-tests/taskswitch2.flat is like below:
>>
>> kvm_entry: vcpu 0
>> kvm_exit: reason TASK_SWITCH rip 0x4008d0 info 40000058 0
>> kvm_entry: vcpu 0
>> kvm_exit: reason EXCEPTION_NMI rip 0x0 info 0 80000306
>> kvm_emulate_insn: 42000:0:0f 0b (0x2)
>> kvm_inj_exception: #UD (0x0)
>> kvm_entry: vcpu 0
>> kvm_exit: reason TASK_SWITCH rip 0x0 info c0000050 0
>> kvm_entry: vcpu 0
>>
>> When EPT=1, unrestricted_guest=Y, emulate_invalid_state=Y, the trace
>> of kvm-unit-tests/taskswitch2.flat is like below, when emulating "0f
>> 0b(#UD)" fails, it injects another #UD and triggers a task switch in
>> the kvm-unit-tests/taskswitch2.flat again.
>>
>> kvm_exit: reason TASK_SWITCH rip 0x0 info 40000058 0
>> kvm_emulate_insn: 42000:0:0f 0b (0x2)
>> kvm_emulate_insn: 42000:0:0f 0b (0x2) failed
>> kvm_inj_exception: #UD (0x0)
>> kvm_entry: vcpu 0
>> kvm_exit: reason TASK_SWITCH rip 0x0 info 40000058 0
>> kvm_emulate_insn: 42000:0:0f 0b (0x2)
>> kvm_emulate_insn: 42000:0:0f 0b (0x2) failed
>> kvm_inj_exception: #UD (0x0)
>> kvm_entry: vcpu 0
>> kvm_exit: reason TASK_SWITCH rip 0x0 info 40000058 0
>> kvm_emulate_insn: 42000:0:0f 0b (0x2)
>> kvm_emulate_insn: 42000:0:0f 0b (0x2) failed
>> kvm_inj_exception: #UD (0x0)
>> .....................
>>
>> Then I try to add the task switch emulation in the
>> handle_invalid_guest_state() path, however, I will get TRIPLE FAULT.
>>
>> diff --git a/arch/x86/kvm/vmx.c b/arch/x86/kvm/vmx.c
>> index f76efad..758f728 100644
>> --- a/arch/x86/kvm/vmx.c
>> +++ b/arch/x86/kvm/vmx.c
>> @@ -6312,11 +6312,15 @@ static int handle_invalid_guest_state(struct
>> kvm_vcpu *vcpu)
>> u32 cpu_exec_ctrl;
>> bool intr_window_requested;
>> unsigned count = 130;
>> + u32 exit_reason = vmx->exit_reason;
>>
>> cpu_exec_ctrl = vmcs_read32(CPU_BASED_VM_EXEC_CONTROL);
>> intr_window_requested = cpu_exec_ctrl & CPU_BASED_VIRTUAL_INTR_PENDING;
>>
>> while (vmx->emulation_required && count-- != 0) {
>> + if (exit_reason == EXIT_REASON_TASK_SWITCH)
>> + return handle_task_switch(vcpu);
>> +
>> if (intr_window_requested && vmx_interrupt_allowed(vcpu))
>> return handle_interrupt_window(&vmx->vcpu);
>>
>>
>> kvm_exit: reason TASK_SWITCH rip 0x4008d0 info 40000058 0
>> kvm_entry: vcpu 0
>> kvm_exit: reason TASK_SWITCH rip 0x0 info 40000058 0
>> kvm_entry: vcpu 0
>> kvm_exit: reason TRIPLE_FAULT rip 0xffff info 0 0
>> kvm_userspace_exit: reason KVM_EXIT_SHUTDOWN (8)
>>
>> Any proposal is a great appreciated. :)
I donât see a (very) easy solution. The code was (apparently) never built to
deal with a task switch during an instruction emulation.
AFAIU kvm_task_switch() expects information about the task-switch from the
CPU âtask-switch assistâ mechanisms, and this information (or even the fact
that a task-switch is needed due to an exception) are unavailable from the
instruction emulator. The instruction emulator itself does not know to
emulate task-switches, e.g., during far CALL and JMP.
A complete solution is therefore complicated and requires some work. Your
specific problem may be addressed by detecting the injection of an exception
while having invalid guest state in vm86 in vmx_queue_exception() or in
handle_invalid_guest_state(), and emulating the âtask-switch assistâ
mechanism.