Per Intel's SDM on the "Instruction Set Reference", when loading segment descriptor for far jmp, not-present segment check should be after all type and privilege checks. However, __load_segment_descriptor() in x86's emulator does not-present segment check first, so it would trigger #NP instead of #GP if type or privilege checks fail and the segment is not present. So add some tests for far jmp instruction, and it will test those tests on hardware and emulator. Enable kvm.force_emulation_prefix when try to test them on emulator. Signed-off-by: Hou Wenlong <houwenlong.hwl@xxxxxxxxxxxx> --- x86/emulator.c | 71 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/x86/emulator.c b/x86/emulator.c index 45972c2fe940..7e98bacd714a 100644 --- a/x86/emulator.c +++ b/x86/emulator.c @@ -36,6 +36,7 @@ struct far_xfer_test_case { enum far_xfer_insn { FAR_XFER_RET, + FAR_XFER_JMP, }; struct far_xfer_test { @@ -64,6 +65,25 @@ static struct far_xfer_test far_ret_test = { .nr_testcases = sizeof(far_ret_testcases) / sizeof(struct far_xfer_test_case), }; +static struct far_xfer_test_case far_jmp_testcases[] = { + {0, DS_TYPE, 0, 0, false, GP_VECTOR, "desc.type!=code && desc.p=0"}, + {0, NON_CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, "non-conforming && dpl!=cpl && desc.p=0"}, + {3, NON_CONFORM_CS_TYPE, 0, 0, false, GP_VECTOR, "conforming && rpl>cpl && desc.p=0"}, + {0, CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, "conforming && dpl>cpl && desc.p=0"}, + {0, NON_CONFORM_CS_TYPE, 0, 0, false, NP_VECTOR, "desc.p=0"}, + {3, CONFORM_CS_TYPE, 0, 1, true, -1, "dpl<cpl"}, +}; + +static struct far_xfer_test far_jmp_test = { + .insn = FAR_XFER_JMP, + .insn_name = "far jmp", + .testcases = &far_jmp_testcases[0], + .nr_testcases = sizeof(far_jmp_testcases) / sizeof(struct far_xfer_test_case), +}; + +static unsigned long fep_jmp_buf[2]; +static unsigned long *fep_jmp_buf_ptr = &fep_jmp_buf[0]; + #define TEST_FAR_RET_ASM(seg, prefix) \ ({ \ asm volatile("lea 1f(%%rip), %%rax\n\t" \ @@ -76,6 +96,17 @@ static struct far_xfer_test far_ret_test = { : "eax", "memory"); \ }) +#define TEST_FAR_JMP_ASM(seg, prefix) \ +({ \ + *(uint16_t *)(&fep_jmp_buf[1]) = seg; \ + asm volatile("lea 1f(%%rip), %%rax\n\t" \ + "movq $1f, (%[mem])\n\t" \ + prefix "rex64 ljmp *(%[mem])\n\t"\ + "1:" \ + : : [mem]"r"(fep_jmp_buf_ptr)\ + : "eax", "memory"); \ +}) + struct regs { u64 rax, rbx, rcx, rdx; u64 rsi, rdi, rsp, rbp; @@ -358,19 +389,6 @@ static void test_pop(void *mem) "enter"); } -static void test_far_jmp(void *mem) -{ - unsigned char *m = mem; - volatile int res = 1; - - *(unsigned long**)m = &&jmpf; - asm volatile ("data16 mov %%cs, %0":"=m"(*(m + sizeof(unsigned long)))); - asm volatile ("rex64 ljmp *%0"::"m"(*m)); - res = 0; -jmpf: - report(res, "far jmp, via emulated MMIO"); -} - static void test_incdecnotneg(void *mem) { unsigned long *m = mem, v = 1234; @@ -966,6 +984,12 @@ static void __test_far_xfer(enum far_xfer_insn insn, uint16_t seg, else TEST_FAR_RET_ASM(seg, ""); break; + case FAR_XFER_JMP: + if (force_emulation) + TEST_FAR_JMP_ASM(seg, KVM_FEP); + else + TEST_FAR_JMP_ASM(seg, ""); + break; default: report_fail("Unexpected insn enum = %d\n", insn); break; @@ -1012,6 +1036,26 @@ static void test_far_xfer(bool force_emulation, struct far_xfer_test *test) handle_exception(NP_VECTOR, 0); } +static void test_far_jmp(uint64_t *mem) +{ + unsigned char *m = (unsigned char *)mem; + volatile int res = 1; + + *(unsigned long**)m = &&jmpf; + asm volatile ("data16 mov %%cs, %0":"=m"(*(m + sizeof(unsigned long)))); + asm volatile ("rex64 ljmp *%0"::"m"(*m)); + res = 0; +jmpf: + report(res, "far jmp, via emulated MMIO"); + + test_far_xfer(false, &far_jmp_test); +} + +static void test_em_far_jmp(uint64_t *mem) +{ + test_far_xfer(true, &far_jmp_test); +} + static void test_far_ret(uint64_t *mem) { test_far_xfer(false, &far_ret_test); @@ -1321,6 +1365,7 @@ int main(void) test_smsw_reg(mem); test_nop(mem); test_mov_dr(mem); + test_em_far_jmp(mem); test_em_far_ret(mem); } else { report_skip("skipping register-only tests, " -- 2.31.1