diff mbox series

[kvm-unit-tests,v2,2/2] x86/emulator: Add some tests for ljmp instruction emulation

Message ID 4d8a505095cc6106371462db2513fbbe000d8b4d.1644311445.git.houwenlong.hwl@antgroup.com (mailing list archive)
State New, archived
Headers show
Series x86/emulator: Add some tests for loading segment descriptor in emulator | expand

Commit Message

Hou Wenlong Feb. 8, 2022, 9:30 a.m. UTC
Per Intel's SDM on the "Instruction Set Reference", when
loading segment descriptor for ljmp, 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 ljmp instruction, and it will test
those tests in hardware and emulator. Enable
kvm.force_emulation_prefix when try to test them in emulator.

Signed-off-by: Hou Wenlong <houwenlong.hwl@antgroup.com>
---
 x86/emulator.c | 75 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 62 insertions(+), 13 deletions(-)

Comments

Sean Christopherson Feb. 9, 2022, 10:13 p.m. UTC | #1
On Tue, Feb 08, 2022, Hou Wenlong wrote:
> Per Intel's SDM on the "Instruction Set Reference", when
> loading segment descriptor for ljmp, 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 ljmp instruction, and it will test
> those tests in hardware and emulator. Enable
> kvm.force_emulation_prefix when try to test them in emulator.

Many of the same comments from patch 01 apply here.  A few more below.

> @@ -1007,6 +1034,27 @@ static void test_far_xfer(bool force_emulation, struct far_xfer_test *test)
>  	handle_exception(NP_VECTOR, 0);
>  }
>  
> +static void test_ljmp(uint64_t *mem)

test_far_jmp(), the existing code is wrong ;-)

> +{
> +	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, "ljmp");

It'd be helfup to explain what this is doing (took me a while to decipher...), e.g.

	report(res, "far jmp, via emulated MMIO");

And as a delta patch...

---
 x86/emulator.c | 55 ++++++++++++++++++++++++--------------------------
 1 file changed, 26 insertions(+), 29 deletions(-)

diff --git a/x86/emulator.c b/x86/emulator.c
index 4e7c6d1..110d10d 100644
--- a/x86/emulator.c
+++ b/x86/emulator.c
@@ -66,16 +66,17 @@ static struct far_xfer_test far_ret_test = {
 };

 static struct far_xfer_test_case far_jmp_testcases[] = {
-	{0, DS_TYPE, 0, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "ljmp desc.type!=code && desc.p=0"},
-	{0, NON_CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "jmp non-conforming && dpl!=cpl && desc.p=0"},
-	{3, NON_CONFORM_CS_TYPE, 0, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "ljmp conforming && rpl>cpl && desc.p=0"},
-	{0, CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "ljmp conforming && dpl>cpl && desc.p=0"},
-	{0, NON_CONFORM_CS_TYPE, 0, 0, false, NP_VECTOR, FIRST_SPARE_SEL, "ljmp desc.p=0"},
-	{3, CONFORM_CS_TYPE, 0, 1, true, -1, -1, "ljmp dpl<cpl"},
+	{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),
 };
@@ -95,23 +96,16 @@ static unsigned long *fep_jmp_buf_ptr = &fep_jmp_buf[0];
 		     : "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");
-
-static inline void test_far_jmp_asm(uint16_t seg, bool force_emulation)
-{
-	if (force_emulation) {
-		TEST_FAR_JMP_ASM(seg, KVM_FEP);
-	} else {
-		TEST_FAR_JMP_ASM(seg, "");
-	}
-}
+#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;
@@ -991,7 +985,10 @@ static void __test_far_xfer(enum far_xfer_insn insn, uint16_t seg,
 			TEST_FAR_RET_ASM(seg, "");
 		break;
 	case FAR_XFER_JMP:
-		test_far_jmp_asm(seg, force_emulation);
+		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);
@@ -1039,7 +1036,7 @@ static void test_far_xfer(bool force_emulation, struct far_xfer_test *test)
 	handle_exception(NP_VECTOR, 0);
 }

-static void test_ljmp(uint64_t *mem)
+static void test_far_jmp(uint64_t *mem)
 {
 	unsigned char *m = (unsigned char *)mem;
 	volatile int res = 1;
@@ -1049,12 +1046,12 @@ static void test_ljmp(uint64_t *mem)
 	asm volatile ("rex64 ljmp *%0"::"m"(*m));
 	res = 0;
 jmpf:
-	report(res, "far jmp, self-modifying code");
+	report(res, "far jmp, via emulated MMIO");

 	test_far_xfer(false, &far_jmp_test);
 }

-static void test_em_ljmp(uint64_t *mem)
+static void test_em_far_jmp(uint64_t *mem)
 {
 	test_far_xfer(true, &far_jmp_test);
 }
@@ -1341,7 +1338,7 @@ int main(void)

 	test_smsw(mem);
 	test_lmsw();
-	test_ljmp(mem);
+	test_far_jmp(mem);
 	test_far_ret(mem);
 	test_stringio();
 	test_incdecnotneg(mem);
@@ -1367,7 +1364,7 @@ int main(void)
 		test_smsw_reg(mem);
 		test_nop(mem);
 		test_mov_dr(mem);
-		test_em_ljmp(mem);
+		test_em_far_jmp(mem);
 		test_em_far_ret(mem);
 	} else {
 		report_skip("skipping register-only tests, "

base-commit: 57a0e341f906f09df1ce45ef7bf080e9737eeff2
--
diff mbox series

Patch

diff --git a/x86/emulator.c b/x86/emulator.c
index a68debaabef0..b4e474356ff7 100644
--- a/x86/emulator.c
+++ b/x86/emulator.c
@@ -35,6 +35,7 @@  struct far_xfer_test_case {
 
 enum far_xfer_insn {
 	FAR_XFER_RET,
+	FAR_XFER_JMP,
 };
 
 struct far_xfer_test {
@@ -61,6 +62,24 @@  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, FIRST_SPARE_SEL, "ljmp desc.type!=code && desc.p=0"},
+	{0, NON_CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "jmp non-conforming && dpl!=cpl && desc.p=0"},
+	{3, NON_CONFORM_CS_TYPE, 0, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "ljmp conforming && rpl>cpl && desc.p=0"},
+	{0, CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "ljmp conforming && dpl>cpl && desc.p=0"},
+	{0, NON_CONFORM_CS_TYPE, 0, 0, false, NP_VECTOR, FIRST_SPARE_SEL, "ljmp desc.p=0"},
+	{3, CONFORM_CS_TYPE, 0, 1, true, -1, -1, "ljmp dpl<cpl"},
+};
+
+static struct far_xfer_test far_jmp_test = {
+	.insn = FAR_XFER_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" \
 		     "pushq %[asm_seg]\n\t"	\
@@ -80,6 +99,24 @@  static inline void test_far_ret_asm(uint16_t seg, bool force_emulation)
 	}
 }
 
+#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");
+
+static inline void test_far_jmp_asm(uint16_t seg, bool force_emulation)
+{
+	if (force_emulation) {
+		TEST_FAR_JMP_ASM(seg, KVM_FEP);
+	} else {
+		TEST_FAR_JMP_ASM(seg, "");
+	}
+}
+
 struct regs {
 	u64 rax, rbx, rcx, rdx;
 	u64 rsi, rdi, rsp, rbp;
@@ -362,19 +399,6 @@  static void test_pop(void *mem)
 	       "enter");
 }
 
-static void test_ljmp(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, "ljmp");
-}
-
 static void test_incdecnotneg(void *mem)
 {
     unsigned long *m = mem, v = 1234;
@@ -965,6 +989,9 @@  static void __test_far_xfer(enum far_xfer_insn insn, uint16_t seg,
 	case FAR_XFER_RET:
 		test_far_ret_asm(seg, force_emulation);
 		break;
+	case FAR_XFER_JMP:
+		test_far_jmp_asm(seg, force_emulation);
+		break;
 	default:
 		report_fail("unknown instructions");
 		break;
@@ -1007,6 +1034,27 @@  static void test_far_xfer(bool force_emulation, struct far_xfer_test *test)
 	handle_exception(NP_VECTOR, 0);
 }
 
+static void test_ljmp(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, "ljmp");
+
+	printf("test ljmp in hw\n");
+	test_far_xfer(false, &far_jmp_test);
+}
+
+static void test_em_ljmp(uint64_t *mem)
+{
+	printf("test ljmp in emulator\n");
+	test_far_xfer(true, &far_jmp_test);
+}
 static void test_lret(uint64_t *mem)
 {
 	printf("test lret in hw\n");
@@ -1318,6 +1366,7 @@  int main(void)
 		test_smsw_reg(mem);
 		test_nop(mem);
 		test_mov_dr(mem);
+		test_em_ljmp(mem);
 		test_em_lret(mem);
 	} else {
 		report_skip("skipping register-only tests, "