Message ID | 20220731204653.2516-1-mhal@rbox.co |
---|---|
State | New |
Headers | show |
Series | [kvm-unit-tests,v2] x86: Test illegal LEA handling | expand |
On Sun, Jul 31, 2022, Michal Luczaj wrote: > Check if the emulator throws #UD on illegal LEA. Please explicitly state exactly what illegal LEA is being generated. Requiring readers to connect the dots of the LEA opcode and ModR/M encoding is unnecessarily mean :-) > Suggested-by: Sean Christopherson <seanjc@google.com> > Signed-off-by: Michal Luczaj <mhal@rbox.co> > --- > v1 -> v2: Instead of racing decoder make use of force_emulation_prefix > > x86/emulator.c | 19 +++++++++++++++++++ > 1 file changed, 19 insertions(+) > > diff --git a/x86/emulator.c b/x86/emulator.c > index cd78e3c..c3898f2 100644 > --- a/x86/emulator.c > +++ b/x86/emulator.c > @@ -895,6 +895,24 @@ static void test_mov_dr(uint64_t *mem) > report(rax == DR6_ACTIVE_LOW, "mov_dr6"); > } > > +static void illegal_lea_handler(struct ex_regs *regs) > +{ > + extern char illegal_lea_cont; > + > + ++exceptions; > + regs->rip = (ulong)&illegal_lea_cont; > +} > + > +static void test_illegal_lea(uint64_t *mem) @mem isn't needed. > +{ > + exceptions = 0; > + handle_exception(UD_VECTOR, illegal_lea_handler); No need to use a custom handler (ignore any patterns in emulator.c that suggest it's "mandatory", emulator is one of the oldest test). ASM_TRY() can handle all of this without any globals. > + asm(KVM_FEP ".byte 0x48; .byte 0x8d; .byte 0xc0\n\t" > + "illegal_lea_cont:" : : : "rax"); "emulator" is compatible with 32-bit KUT, drop the REX prefix and clobber "eax" instead of "xax". > + report(exceptions == 1, "illegal lea"); Be nice to future debuggers. Call out what is illegal about LEA, capitalize LEA to make it more obvious that its an instruction, and print the expected versus actual. > + handle_exception(UD_VECTOR, 0); > +} So this? static void test_illegal_lea(void) { unsigned int vector; asm volatile (ASM_TRY("1f") KVM_FEP ".byte 0x8d; .byte 0xc0\n\t" "1:" : : : "memory", "eax"); vector = exception_vector(); report(vector == UD_VECTOR, "Wanted #UD on LEA with /reg, got vector = %d", vector); } > + > static void test_push16(uint64_t *mem) > { > uint64_t rsp1, rsp2; > @@ -1193,6 +1211,7 @@ int main(void) > test_smsw_reg(mem); > test_nop(mem); > test_mov_dr(mem); > + test_illegal_lea(mem); > } else { > report_skip("skipping register-only tests, " > "use kvm.force_emulation_prefix=1 to enable"); > -- > 2.32.0 >
On Wed, Aug 03, 2022, Michal Luczaj wrote: > On 8/1/22 18:44, Sean Christopherson wrote: > > On Sun, Jul 31, 2022, Michal Luczaj wrote: > >> +{ > >> + exceptions = 0; > >> + handle_exception(UD_VECTOR, illegal_lea_handler); > > > > No need to use a custom handler (ignore any patterns in emulator.c that suggest > > it's "mandatory", emulator is one of the oldest test). ASM_TRY() can handle all > > of this without any globals. > > ... > > static void test_illegal_lea(void) > > { > > unsigned int vector; > > > > asm volatile (ASM_TRY("1f") > > KVM_FEP ".byte 0x8d; .byte 0xc0\n\t" > > "1:" > > : : : "memory", "eax"); > > > > vector = exception_vector(); > > report(vector == UD_VECTOR, > > "Wanted #UD on LEA with /reg, got vector = %d", vector); > > } > > I must be missing something important. There is > `handle_exception(UD_VECTOR, 0)` early in `main()` which simply undoes > `handle_exception(6, check_exception_table)` set by `setup_idt()`. If > there's no more exception table walk for #UD, `ASM_TRY` alone can't > possibly work, am I corrent? Argh, you're correct, I didn't realize the test zapped the IDT entry. That's a bug, the test shouldn't zap entries, the whole point of handle_exception() returning the old handler is so that the caller can restore it. Grr. > If so, am I supposed to restore the `check_exception_table()` handler? Or > maybe using `test_for_exception()` would be more elegant: Hmm, I prefer ASM_TRY() over test_for_exception(), having to define a function just to emit a single instruction is silly. What I'd really prefer is that we wouldn't have so many ways for doing the same basic thing (obviously not your fault, just ranting/whining). If you have bandwidth, can you create a small series to clean up emulator.c to at least take a step in the right direction? 1. Save/restore the handlers. 2. Use ASM_TRY for the UD_VECTOR cases (KVM_FEP probing and illegal MOVBE) 3. Add this testcase as described above. Ideally the test wouldn't use handle_exception() at all, but that's a much bigger mess and a future problem.
On Wed, Aug 03, 2022, Michal Luczaj wrote: > TRY_ASM() mishandles #UD thrown by the forced-emulation-triggered emulator. > While the faulting address stored in the exception table points at forced > emulation prefix, when #UD comes, RIP is 5 bytes (size of KVM_FEP) ahead > and the exception ends up unhandled. Ah, but that's only the behavior if FEP is allowed. If FEP is disabled, then the #UD will be on the prefix. > Suggested-by: Sean Christopherson <seanjc@google.com> Heh, I didn't really suggest this because I didn't even realize it was a problem :-) > Signed-off-by: Michal Luczaj <mhal@rbox.co> > --- > While here, I've also took the opportunity to merge both 32 and 64-bit > versions of ASM_TRY() (.dc.a for .long and .quad), but perhaps there > were some reasons for not using .dc.a? This should be a separate patch, and probably as the very last patch in case dc.a isn't viable for whatever reason. I've never seen/used dc.a so I really have no idea whether or not it's ok to use. As a "safe" alternative that can be done before adding ASM_TRY_FEP(), we can steal the kernel's __ASM_SEL() approach so that you don't have to implement two versions of ASM_TRY_FEP(). That's worth doing because __ASM_SEL() can probably be used in other places too. Patch at the bottom. > lib/x86/desc.h | 11 +++++------ > x86/emulator.c | 4 ++-- > 2 files changed, 7 insertions(+), 8 deletions(-) > > diff --git a/lib/x86/desc.h b/lib/x86/desc.h > index 2a285eb..99cc224 100644 > --- a/lib/x86/desc.h > +++ b/lib/x86/desc.h > @@ -80,21 +80,20 @@ typedef struct __attribute__((packed)) { > u16 iomap_base; > } tss64_t; > > -#ifdef __x86_64 > #define ASM_TRY(catch) \ > "movl $0, %%gs:4 \n\t" \ > ".pushsection .data.ex \n\t" \ > - ".quad 1111f, " catch "\n\t" \ > + ".dc.a 1111f, " catch "\n\t" \ > ".popsection \n\t" \ > "1111:" > -#else > -#define ASM_TRY(catch) \ > + > +#define ASM_TRY_PREFIXED(prefix, catch) \ Rather than a generic ASM_TRY_PREFIXED, I think it makes sense to add an explicit ASM_TRY_FEP() that takes in only the label and hardcodes the FEP prefix. The "#UD skips the prefix" behavior is unique to "successful" forced emulation, so this is literally the only scenario where skipping a prefix is expected/correct. *sigh* That'll require moving the KVM_FEP definition, but that's a good change on its own. Probably throw it in lib/x86/processor.h? > "movl $0, %%gs:4 \n\t" \ > ".pushsection .data.ex \n\t" \ > - ".long 1111f, " catch "\n\t" \ > + ".dc.a 1111f, " catch "\n\t" \ > ".popsection \n\t" \ > + prefix "\n\t" \ > "1111:" > -#endif > > /* > * selector 32-bit 64-bit > diff --git a/x86/emulator.c b/x86/emulator.c > index df0bc49..d2a5302 100644 > --- a/x86/emulator.c > +++ b/x86/emulator.c > @@ -900,8 +900,8 @@ static void test_illegal_lea(void) > { > unsigned int vector; > > - asm volatile (ASM_TRY("1f") > - KVM_FEP ".byte 0x8d; .byte 0xc0\n\t" > + asm volatile (ASM_TRY_PREFIXED(KVM_FEP, "1f") > + ".byte 0x8d; .byte 0xc0\n\t" Put this patch earlier in the series, before test_illegal_lea() is introduced. Both so that there's no unnecessary churn, and also because running the illegal LEA testcase without this patch will fail. > "1:" > : : : "memory", "eax"); --- From: Sean Christopherson <seanjc@google.com> Date: Wed, 3 Aug 2022 11:09:41 -0700 Subject: [PATCH] x86: Dedup 32-bit vs. 64-bit ASM_TRY() by stealing kernel's __ASM_SEL() Steal the kernel's __ASM_SEL() implementation and use it to consolidate ASM_TRY(). The only difference between the 32-bit and 64-bit versions is the size of the address stored in the table. No functional change intended. Signed-off-by: Sean Christopherson <seanjc@google.com> --- lib/x86/desc.h | 19 +++++-------------- lib/x86/processor.h | 12 ++++++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/x86/desc.h b/lib/x86/desc.h index 2a285eb6..e670ebf2 100644 --- a/lib/x86/desc.h +++ b/lib/x86/desc.h @@ -80,21 +80,12 @@ typedef struct __attribute__((packed)) { u16 iomap_base; } tss64_t; -#ifdef __x86_64 -#define ASM_TRY(catch) \ - "movl $0, %%gs:4 \n\t" \ - ".pushsection .data.ex \n\t" \ - ".quad 1111f, " catch "\n\t" \ - ".popsection \n\t" \ +#define ASM_TRY(catch) \ + "movl $0, %%gs:4 \n\t" \ + ".pushsection .data.ex \n\t" \ + __ASM_SEL(.long, .quad) " 1111f, " catch "\n\t" \ + ".popsection \n\t" \ "1111:" -#else -#define ASM_TRY(catch) \ - "movl $0, %%gs:4 \n\t" \ - ".pushsection .data.ex \n\t" \ - ".long 1111f, " catch "\n\t" \ - ".popsection \n\t" \ - "1111:" -#endif /* * selector 32-bit 64-bit diff --git a/lib/x86/processor.h b/lib/x86/processor.h index 03242206..30e2de87 100644 --- a/lib/x86/processor.h +++ b/lib/x86/processor.h @@ -19,6 +19,18 @@ # define S "4" #endif +#ifdef __ASSEMBLY__ +#define __ASM_FORM(x, ...) x,## __VA_ARGS__ +#else +#define __ASM_FORM(x, ...) " " xstr(x,##__VA_ARGS__) " " +#endif + +#ifndef __x86_64__ +#define __ASM_SEL(a,b) __ASM_FORM(a) +#else +#define __ASM_SEL(a,b) __ASM_FORM(b) +#endif + #define DB_VECTOR 1 #define BP_VECTOR 3 #define UD_VECTOR 6 base-commit: a106b30d39425b7afbaa3bbd4aab16fd26d333e7 --
On 8/3/22 20:16, Sean Christopherson wrote: >> While here, I've also took the opportunity to merge both 32 and 64-bit >> versions of ASM_TRY() (.dc.a for .long and .quad), but perhaps there >> were some reasons for not using .dc.a? > This should be a separate patch, and probably as the very last patch in case dc.a > isn't viable for whatever reason. I've never seen/used dc.a so I really have no > idea whether or not it's ok to use. Yes, for now I'll squash this, which is similar to Michal's idea but using the trusty double underscore prefix: diff --git a/lib/x86/desc.h b/lib/x86/desc.h index 2a285eb..5b21820 100644 --- a/lib/x86/desc.h +++ b/lib/x86/desc.h @@ -81,11 +81,12 @@ typedef struct __attribute__((packed)) { } tss64_t; #ifdef __x86_64 -#define ASM_TRY(catch) \ +#define __ASM_TRY(prefix, catch) \ "movl $0, %%gs:4 \n\t" \ ".pushsection .data.ex \n\t" \ ".quad 1111f, " catch "\n\t" \ ".popsection \n\t" \ + prefix "\n\t" \ "1111:" #else #define ASM_TRY(catch) \ @@ -96,6 +97,8 @@ typedef struct __attribute__((packed)) { "1111:" #endif +#define ASM_TRY(catch) __ASM_TRY("", catch) + /* * selector 32-bit 64-bit * 0x00 NULL descriptor NULL descriptor diff --git a/x86/emulator.c b/x86/emulator.c index df0bc49..6d2f166 100644 --- a/x86/emulator.c +++ b/x86/emulator.c @@ -19,6 +19,7 @@ static int exceptions; /* Forced emulation prefix, used to invoke the emulator unconditionally. */ #define KVM_FEP "ud2; .byte 'k', 'v', 'm';" +#define ASM_TRY_FEP(catch) __ASM_TRY(KVM_FEP, catch) struct regs { u64 rax, rbx, rcx, rdx; @@ -900,8 +901,8 @@ static void test_illegal_lea(void) { unsigned int vector; - asm volatile (ASM_TRY("1f") - KVM_FEP ".byte 0x8d; .byte 0xc0\n\t" + asm volatile (ASM_TRY_FEP("1f") + ".byte 0x8d; .byte 0xc0\n\t" "1:" : : : "memory", "eax"); and the __ASM_SEL() idea can be sent as a separate patch.
diff --git a/x86/emulator.c b/x86/emulator.c index cd78e3c..c3898f2 100644 --- a/x86/emulator.c +++ b/x86/emulator.c @@ -895,6 +895,24 @@ static void test_mov_dr(uint64_t *mem) report(rax == DR6_ACTIVE_LOW, "mov_dr6"); } +static void illegal_lea_handler(struct ex_regs *regs) +{ + extern char illegal_lea_cont; + + ++exceptions; + regs->rip = (ulong)&illegal_lea_cont; +} + +static void test_illegal_lea(uint64_t *mem) +{ + exceptions = 0; + handle_exception(UD_VECTOR, illegal_lea_handler); + asm(KVM_FEP ".byte 0x48; .byte 0x8d; .byte 0xc0\n\t" + "illegal_lea_cont:" : : : "rax"); + report(exceptions == 1, "illegal lea"); + handle_exception(UD_VECTOR, 0); +} + static void test_push16(uint64_t *mem) { uint64_t rsp1, rsp2; @@ -1193,6 +1211,7 @@ int main(void) test_smsw_reg(mem); test_nop(mem); test_mov_dr(mem); + test_illegal_lea(mem); } else { report_skip("skipping register-only tests, " "use kvm.force_emulation_prefix=1 to enable");
Check if the emulator throws #UD on illegal LEA. Suggested-by: Sean Christopherson <seanjc@google.com> Signed-off-by: Michal Luczaj <mhal@rbox.co> --- v1 -> v2: Instead of racing decoder make use of force_emulation_prefix x86/emulator.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)