From: Gleb Natapov <gleb@redhat.com>
To: Jan Kiszka <jan.kiszka@siemens.com>
Cc: Avi Kivity <avi@redhat.com>,
Marcelo Tosatti <mtosatti@redhat.com>, kvm <kvm@vger.kernel.org>
Subject: Re: [PATCH 2/2] KVM: SVM: Make stepping out of NMI handlers more robust
Date: Tue, 16 Feb 2010 10:04:36 +0200 [thread overview]
Message-ID: <20100216080436.GX2995@redhat.com> (raw)
In-Reply-To: <8cb458e40c9d594f27975b10bb24ecb9a90d8102.1266257833.git.jan.kiszka@siemens.com>
On Mon, Feb 15, 2010 at 07:17:18PM +0100, Jan Kiszka wrote:
> As there is no interception on AMD on the end of an NMI handler but only
> on its iret, we are forced to step out by setting TF in rflags. This can
> collide with host or guest using single-step mode, and it may leak the
> flags onto the guest stack if IRET triggers some exception.
The code is trying to handle the case where debugger used TF flags and we
want to single step from NMI handler simultaneously. Do you see problem with
that code? Uf yes may be it sill be much simpler to fix it? TF leakage is real,
but what problem it may cause? Note that your patch does not solve this problem
too. See the comment that you've deleted:
/* Something prevents NMI from been injected. Single step over
possible problem (IRET or exception injection or interrupt
shadow) */
So the reason for single step is not necessary IRET, _any_ exception
is possible at this point.
>
> This patch addresses the TF leakage by trapping all exceptions that may
> show up on IRET execution and cleaning up the state again before
> reinjecting the exception. Collisions with concurrent users are avoided
> by carefully checking for their presence and forwarding #DB exceptions
> whenever required.
This patch is scary to apply without test cases.
>
> Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
> ---
> arch/x86/include/asm/kvm_host.h | 3 +
> arch/x86/kvm/svm.c | 112 +++++++++++++++++++++++++++++---------
> 2 files changed, 88 insertions(+), 27 deletions(-)
>
> diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
> index f9a2f66..cbae274 100644
> --- a/arch/x86/include/asm/kvm_host.h
> +++ b/arch/x86/include/asm/kvm_host.h
> @@ -62,6 +62,7 @@
> #define GP_VECTOR 13
> #define PF_VECTOR 14
> #define MF_VECTOR 16
> +#define AC_VECTOR 17
> #define MC_VECTOR 18
>
> #define SELECTOR_TI_MASK (1 << 2)
> @@ -131,8 +132,10 @@ enum {
>
> #define KVM_NR_DB_REGS 4
>
> +#define DR6_BP_MASK 0x0000000f
> #define DR6_BD (1 << 13)
> #define DR6_BS (1 << 14)
> +#define DR6_BT (1 << 15)
> #define DR6_FIXED_1 0xffff0ff0
> #define DR6_VOLATILE 0x0000e00f
>
> diff --git a/arch/x86/kvm/svm.c b/arch/x86/kvm/svm.c
> index f63f1db..672f394 100644
> --- a/arch/x86/kvm/svm.c
> +++ b/arch/x86/kvm/svm.c
> @@ -109,7 +109,8 @@ struct vcpu_svm {
>
> struct nested_state nested;
>
> - bool nmi_singlestep;
> + bool iret_singlestep;
> + u64 iret_rflags;
>
> bool int3_injected;
> };
> @@ -1084,15 +1085,21 @@ static void svm_set_segment(struct kvm_vcpu *vcpu,
>
> }
>
> +#define IRET_EXCEPTIONS (1 << NP_VECTOR) | (1 << SS_VECTOR) | \
> + (1 << GP_VECTOR) | (1 << PF_VECTOR) | (1 << AC_VECTOR)
> +
> static void update_db_intercept(struct kvm_vcpu *vcpu)
> {
> struct vcpu_svm *svm = to_svm(vcpu);
>
> svm->vmcb->control.intercept_exceptions &=
> - ~((1 << DB_VECTOR) | (1 << BP_VECTOR));
> + ~((1 << DB_VECTOR) | (1 << BP_VECTOR) | IRET_EXCEPTIONS);
> + if (!npt_enabled)
> + svm->vmcb->control.intercept_exceptions |= 1 << PF_VECTOR;
>
> - if (svm->nmi_singlestep)
> - svm->vmcb->control.intercept_exceptions |= (1 << DB_VECTOR);
> + if (svm->iret_singlestep)
> + svm->vmcb->control.intercept_exceptions |=
> + (1 << DB_VECTOR) | IRET_EXCEPTIONS;
>
> if (vcpu->guest_debug & KVM_GUESTDBG_ENABLE) {
> if (vcpu->guest_debug &
> @@ -1210,11 +1217,45 @@ static int svm_set_dr(struct kvm_vcpu *vcpu, int dr, unsigned long value)
> return EMULATE_DONE;
> }
>
> +static void reset_iret_singlestep(struct vcpu_svm * svm)
> +{
> + svm->iret_singlestep = false;
> + svm->vmcb->save.rflags = svm->iret_rflags;
> + update_db_intercept(&svm->vcpu);
> +
> + /* We did not yet leave the NMI handler. */
> + svm->vcpu.arch.hflags |= HF_NMI_MASK;
> +}
> +
> +static int fault_on_iret(struct vcpu_svm *svm)
> +{
> + u32 exit_code = svm->vmcb->control.exit_code;
> +
> + BUG_ON(!svm->iret_singlestep);
> +
> + reset_iret_singlestep(svm);
> +
> + /*
> + * Requeue the exception IRET triggered and let the guest handle it.
> + * Note that all of them come with an error code.
> + */
> + kvm_queue_exception_e(&svm->vcpu, exit_code - SVM_EXIT_EXCP_BASE,
> + svm->vmcb->control.exit_int_info_err);
> + return 1;
> +}
> +
> static int pf_interception(struct vcpu_svm *svm)
> {
> u64 fault_address;
> u32 error_code;
>
> + if (svm->iret_singlestep) {
> + if (npt_enabled)
> + return fault_on_iret(svm);
> +
> + reset_iret_singlestep(svm);
> + }
> +
> fault_address = svm->vmcb->control.exit_info_2;
> error_code = svm->vmcb->control.exit_info_1;
>
> @@ -1227,32 +1268,43 @@ static int pf_interception(struct vcpu_svm *svm)
> static int db_interception(struct vcpu_svm *svm)
> {
> struct kvm_run *kvm_run = svm->vcpu.run;
> + struct vmcb_save_area *save = &svm->vmcb->save;
>
> - if (!(svm->vcpu.guest_debug &
> - (KVM_GUESTDBG_SINGLESTEP | KVM_GUESTDBG_USE_HW_BP)) &&
> - !svm->nmi_singlestep) {
> - kvm_queue_exception(&svm->vcpu, DB_VECTOR);
> - return 1;
> - }
> + if (svm->iret_singlestep) {
> + if (!(save->dr6 & DR6_BS)) {
> + /* Not yet the step we are waiting for. */
> + reset_iret_singlestep(svm);
> + goto check_guest_debug;
> + }
>
> - if (svm->nmi_singlestep) {
> - svm->nmi_singlestep = false;
> - if (!(svm->vcpu.guest_debug & KVM_GUESTDBG_SINGLESTEP))
> - svm->vmcb->save.rflags &=
> - ~(X86_EFLAGS_TF | X86_EFLAGS_RF);
> + svm->iret_singlestep = false;
> update_db_intercept(&svm->vcpu);
> +
> + if (svm->vcpu.guest_debug & KVM_GUESTDBG_SINGLESTEP)
> + goto debugger_exit;
> +
> + /*
> + * Check if there is some other reason in DR6 or if the guest
> + * is in single-step mode as well. If not, we are done.
> + */
> + if (!(save->dr6 & (DR6_BP_MASK | DR6_BD | DR6_BT)) &&
> + (save->rflags & (X86_EFLAGS_TF | X86_EFLAGS_RF)) !=
> + X86_EFLAGS_TF)
> + return 1;
> }
>
> - if (svm->vcpu.guest_debug &
> - (KVM_GUESTDBG_SINGLESTEP | KVM_GUESTDBG_USE_HW_BP)){
> - kvm_run->exit_reason = KVM_EXIT_DEBUG;
> - kvm_run->debug.arch.pc =
> - svm->vmcb->save.cs.base + svm->vmcb->save.rip;
> - kvm_run->debug.arch.exception = DB_VECTOR;
> - return 0;
> +check_guest_debug:
> + if (!(svm->vcpu.guest_debug &
> + (KVM_GUESTDBG_SINGLESTEP | KVM_GUESTDBG_USE_HW_BP))) {
> + kvm_queue_exception(&svm->vcpu, DB_VECTOR);
> + return 1;
> }
>
> - return 1;
> +debugger_exit:
> + kvm_run->exit_reason = KVM_EXIT_DEBUG;
> + kvm_run->debug.arch.pc = svm->vmcb->save.cs.base + svm->vmcb->save.rip;
> + kvm_run->debug.arch.exception = DB_VECTOR;
> + return 0;
> }
>
> static int bp_interception(struct vcpu_svm *svm)
> @@ -2367,6 +2419,10 @@ static int (*svm_exit_handlers[])(struct vcpu_svm *svm) = {
> [SVM_EXIT_EXCP_BASE + PF_VECTOR] = pf_interception,
> [SVM_EXIT_EXCP_BASE + NM_VECTOR] = nm_interception,
> [SVM_EXIT_EXCP_BASE + MC_VECTOR] = mc_interception,
> + [SVM_EXIT_EXCP_BASE + NP_VECTOR] = fault_on_iret,
> + [SVM_EXIT_EXCP_BASE + SS_VECTOR] = fault_on_iret,
> + [SVM_EXIT_EXCP_BASE + GP_VECTOR] = fault_on_iret,
> + [SVM_EXIT_EXCP_BASE + AC_VECTOR] = fault_on_iret,
> [SVM_EXIT_INTR] = intr_interception,
> [SVM_EXIT_NMI] = nmi_interception,
> [SVM_EXIT_SMI] = nop_on_interception,
> @@ -2598,10 +2654,12 @@ static void enable_nmi_window(struct kvm_vcpu *vcpu)
> == HF_NMI_MASK)
> return; /* IRET will cause a vm exit */
>
> - /* Something prevents NMI from been injected. Single step over
> - possible problem (IRET or exception injection or interrupt
> - shadow) */
> - svm->nmi_singlestep = true;
> + /*
> + * Step over IRET. We will try to inject again after returning from
> + * the NMI handler.
> + */
> + svm->iret_singlestep = true;
> + svm->iret_rflags = svm->vmcb->save.rflags;
> svm->vmcb->save.rflags |= (X86_EFLAGS_TF | X86_EFLAGS_RF);
> update_db_intercept(vcpu);
> }
> --
> 1.6.0.2
--
Gleb.
next prev parent reply other threads:[~2010-02-16 8:04 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2010-02-15 18:17 [PATCH 0/2] KVM: SVM improvements around INT3 and NMI Jan Kiszka
2010-02-15 18:17 ` [PATCH 1/2] KVM: SVM: Emulate nRIP feature when reinjecting INT3 Jan Kiszka
2010-02-16 7:52 ` Gleb Natapov
2010-02-16 8:02 ` Jan Kiszka
2010-02-16 9:50 ` [PATCH v2 " Jan Kiszka
2010-02-15 18:17 ` [PATCH 2/2] KVM: SVM: Make stepping out of NMI handlers more robust Jan Kiszka
2010-02-16 8:04 ` Gleb Natapov [this message]
2010-02-16 9:14 ` Jan Kiszka
2010-02-16 9:34 ` Gleb Natapov
2010-02-16 9:45 ` Jan Kiszka
2010-02-16 9:49 ` Gleb Natapov
2010-02-16 10:05 ` Jan Kiszka
2010-02-16 10:08 ` Gleb Natapov
2010-02-17 13:49 ` Gleb Natapov
2010-02-17 19:16 ` Jan Kiszka
2010-02-18 7:52 ` Gleb Natapov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20100216080436.GX2995@redhat.com \
--to=gleb@redhat.com \
--cc=avi@redhat.com \
--cc=jan.kiszka@siemens.com \
--cc=kvm@vger.kernel.org \
--cc=mtosatti@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox