From: Sean Christopherson <seanjc@google.com>
To: Michal Luczaj <mhal@rbox.co>
Cc: kvm@vger.kernel.org, pbonzini@redhat.com
Subject: Re: [kvm-unit-tests PATCH] x86/emulator: Test POP-SS blocking
Date: Wed, 24 Aug 2022 18:32:52 +0000 [thread overview]
Message-ID: <YwZu1K5Rgb1sevsy@google.com> (raw)
In-Reply-To: <6d19f78f-120a-936b-3eba-e949ecc3509f@rbox.co>
On Tue, Aug 23, 2022, Michal Luczaj wrote:
> On 8/22/22 17:42, Sean Christopherson wrote:
> >> On 8/22/22 00:06, Michal Luczaj wrote:
> >> test can be simplified to something like
> >>
> >> asm volatile("push %[ss]\n\t"
> >> __ASM_TRY(KVM_FEP "pop %%ss", "1f")
> >> "ex_blocked: mov $1, %[success]\n\t"
> >
> > So I'm 99% certain this only passes because KVM doesn't emulate the `mov $1, %[success]`.
> > kvm_vcpu_check_code_breakpoint() honors EFLAGS.RF, but not MOV/POP_SS blocking.
> > I.e. I would expect this to fail if the `mov` were also tagged KVM_FEP.
>
> I wasn't sure if I understood you correctly
Yep, you got the gist.
> so, FWIW, I've ran:
>
> static void test_pop_ss_blocking_try(void)
> {
> int success;
>
> write_dr7(DR7_FIXED_1 |
> DR7_GLOBAL_ENABLE_DRx(0) |
> DR7_EXECUTE_DRx(0) |
> DR7_LEN_1_DRx(0));
>
> #define POPSS(desc, fep1, fep2) \
> asm volatile("mov $0, %[success]\n\t" \
> "lea 1f, %%eax\n\r" \
Note, hardcoding EAX doesn't compile for 64-bit KUT. It's kinda gross, but we
can fudge around this without #ifdeffery by using a dummy input constraint.
> "mov %%eax, %%dr0\n\r" \
> "push %%ss\n\t" \
> __ASM_TRY(fep1 "pop %%ss", "2f") \
No need for __ASM_TRY if this is thrown into debug.c.
> "1:" fep2 "mov $1, %[success]\n\t" \
> "2:" \
> : [success] "=g" (success) \
> : \
> : "eax", "memory"); \
> report(success, desc ": #DB suppressed after POP SS")
To avoid potential "leakage", wrap this whole thing in ({ ... }), and declare
"success" inside that scope.
Aha! Even better. s/success/r, make it unsigned long, and force it to be a
register. Then the blob can use the register as scratch before clearing it via
XOR to indicate success (though that's somewhat pointless, see below).
> POPSS("no fep", "", "");
> POPSS("fep pop-ss", KVM_FEP, "");
> POPSS("fep mov-1", "", KVM_FEP);
> POPSS("fep pop-ss/fep mov-1", KVM_FEP, KVM_FEP);
>
> write_dr7(DR7_FIXED_1);
> }
>
> and got:
>
> em_pop_sreg unpatched:
> PASS: no fep: #DB suppressed after POP SS
> FAIL: fep pop-ss: #DB suppressed after POP SS
> PASS: fep mov-1: #DB suppressed after POP SS
> FAIL: fep pop-ss/fep mov-1: #DB suppressed after POP SS
>
> em_pop_sreg patched:
> PASS: no fep: #DB suppressed after POP SS
> PASS: fep pop-ss: #DB suppressed after POP SS
> PASS: fep mov-1: #DB suppressed after POP SS
> PASS: fep pop-ss/fep mov-1: #DB suppressed after POP SS
>
> For the sake of completeness: basically the same, but MOV SS, i.e.
>
> asm volatile("mov $0, %[success]\n\t" \
> "lea 1f, %%eax\n\r" \
> "mov %%eax, %%dr0\n\r" \
> "mov %%ss, %%eax\n\t" \
> __ASM_TRY(fep1 "mov %%eax, %%ss", "2f") \
> "1:" fep2 "mov $1, %[success]\n\t" \
> "2:" \
> : [success] "=g" (success) \
> : \
> : "eax", "memory"); \
>
> PASS: no fep: #DB suppressed after MOV SS
> PASS: fep mov-ss: #DB suppressed after MOV SS
> PASS: fep mov-1: #DB suppressed after MOV SS
> PASS: fep mov-ss/fep mov-1: #DB suppressed after MOV SS
This passes for 3 reasons:
1. The test more than likely doesn't skip the instruction on #DB, and so
success will be set regardless of whether or not a #DB occurred.
2. DR0 needs to be loaded with the address of the MOV, not the address of the
FEP prefix. Somewhat amusingly, this means my patch to fix redundant #DB
checking is wrong[*]
3. My personal favorite. On VM-Exits due to exceptions (#UD), Intel CPUs set
EFLAGS.RF=1 and thus effectively suppress #DBs on FEP instructions.
If the VM exit is caused directly by an event that would normally be delivered
through the IDT, the value saved is that which would appear in the saved RFLAGS
image (either that which would be saved on the stack had the event been delivered
through a trap or interrupt gate1 or into the old task-state segment had the event
been delivered through a task gate) had the event been delivered through the IDT.
See below for VM exits due to task switches caused by task gates in the IDT.
I'm pretty sure this inarguably a KVM bug. The SDM states the EFLAGS.RF=0
on instruction-based VM-Exits, and since the intent in the FEP case (but not
the general #UD case) is to simulate an instruction exit...
If the VM exit is caused by an attempt to execute an instruction that
unconditionally causes VM exits or one that was configured to do with a
VM-execution control, the value saved is 0.
I'll fold a fix for #3 and for the MOV/POP-SS blocking code #DB interaction into
the aforementioned series[*].
As expected, I get two failures (FEP on the XOR testcases) with this tweak to KVM,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index fb5cecb19cf5..e5fd0b936a48 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -7261,6 +7261,7 @@ int handle_ud(struct kvm_vcpu *vcpu)
kvm_read_guest_virt(vcpu, kvm_get_linear_rip(vcpu),
sig, sizeof(sig), &e) == 0 &&
memcmp(sig, kvm_emulate_prefix, sizeof(sig)) == 0) {
+ kvm_set_rflags(vcpu, kvm_get_rflags(vcpu) & ~X86_EFLAGS_RF);
kvm_rip_write(vcpu, kvm_rip_read(vcpu) + sizeof(sig));
emul_type = EMULTYPE_TRAP_UD_FORCED;
}
and this as a testcase.
diff --git a/x86/debug.c b/x86/debug.c
index b66bf047..ab29e94e 100644
--- a/x86/debug.c
+++ b/x86/debug.c
@@ -352,8 +352,44 @@ static unsigned long singlestep_with_movss_blocking_and_dr7_gd(void)
return start_rip;
}
+static void test_mov_ss_code_db(bool fep_available)
+{
+ write_dr7(DR7_FIXED_1 |
+ DR7_GLOBAL_ENABLE_DRx(0) |
+ DR7_EXECUTE_DRx(0) |
+ DR7_LEN_1_DRx(0));
+
+#define MOVSS_DB(desc, fep1, fep2) \
+({ \
+ unsigned long r; \
+ \
+ n = 0; \
+ asm volatile("lea 1f(%%rip), %0\n\t" \
+ "mov %0, %%dr0\n\t" \
+ "mov %%ss, %0\n\t" \
+ fep1 "mov %0, %%ss\n\t" \
+ fep2 "1: xor %0, %0\n\t" \
+ "2:" \
+ : "=r" (r) \
+ : \
+ : "memory"); \
+ report(!n && !r, desc ": #DB suppressed after MOV SS"); \
+})
+
+ MOVSS_DB("no fep", "", "");
+ if (fep_available) {
+ MOVSS_DB("fep MOV-SS", KVM_FEP, "");
+ MOVSS_DB("fep XOR", "", KVM_FEP);
+ MOVSS_DB("fep MOV-SS/fep XOR", KVM_FEP, KVM_FEP);
+ }
+
+ write_dr7(DR7_FIXED_1);
+}
+
int main(int ac, char **av)
{
+ /* Check for KVM's FEP support prior to usurpsing the #UD handler. */
+ bool fep_available = is_fep_available();
unsigned long cr4;
handle_exception(DB_VECTOR, handle_db);
@@ -417,6 +453,8 @@ int main(int ac, char **av)
run_ss_db_test(singlestep_with_movss_blocking_and_icebp);
run_ss_db_test(singlestep_with_movss_blocking_and_dr7_gd);
+ test_mov_ss_code_db(fep_available);
+
n = 0;
write_dr1((void *)&value);
write_dr6(DR6_BS);
[*] https://lore.kernel.org/all/20220723005137.1649592-4-seanjc@google.com
> > The reason I say the setup_idt() change is a moot point is because I think it
> > would be better to put this testcase into debug.c (where "this" testcase is really
> > going to be multiple testcases). With macro shenanigans (or code patching), it
> > should be fairly easy to run all testcases with both FEP=0 and FEP=1.
> >
> > Then it's "just" a matter of adding a code #DB testcase. __run_single_step_db_test()
> > and run_ss_db_test() aren't fundamentally tied to single-step, i.e. can simply be
> > renamed. For simplicity, I'd say just skip the usermode test for code #DBs, IMO
> > it's not worth the extra coverage.
>
> So that would involve a 32-bit segment for POP SS and making use of DR0/DR7 instead
> of single stepping? I guess I can try.
Eh, let's completely skip usermode for code #DBs and not tweak __run_single_step_db_test().
It's easier to just have a standalone function.
next prev parent reply other threads:[~2022-08-24 18:33 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-08-21 21:59 [PATCH] KVM: x86/emulator: Fix handing of POP SS to correctly set interruptibility Michal Luczaj
2022-08-21 22:06 ` [kvm-unit-tests PATCH] x86/emulator: Test POP-SS blocking Michal Luczaj
2022-08-22 2:40 ` Michal Luczaj
2022-08-22 15:42 ` Sean Christopherson
2022-08-22 18:30 ` Nadav Amit
2022-08-22 18:37 ` Sean Christopherson
2022-08-23 0:16 ` Michal Luczaj
2022-08-24 18:32 ` Sean Christopherson [this message]
2022-08-24 21:49 ` Michal Luczaj
2022-08-25 17:03 ` Sean Christopherson
2022-08-25 17:32 ` Michal Luczaj
2022-08-25 17:56 ` Sean Christopherson
2022-08-24 0:20 ` [PATCH] KVM: x86/emulator: Fix handing of POP SS to correctly set interruptibility Sean Christopherson
2022-08-24 17:19 ` Paolo Bonzini
2022-08-30 21:41 ` Sean Christopherson
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=YwZu1K5Rgb1sevsy@google.com \
--to=seanjc@google.com \
--cc=kvm@vger.kernel.org \
--cc=mhal@rbox.co \
--cc=pbonzini@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