@@ -34,6 +34,7 @@ struct fep_test_case {
enum fep_test_inst_type {
FEP_TEST_LRET,
+ FEP_TEST_LJMP,
};
struct fep_test {
@@ -68,6 +69,29 @@ static struct fep_test fep_test_lret = {
.user_testcases_count = sizeof(lret_user_testcases) / sizeof(struct fep_test_case),
};
+static struct fep_test_case ljmp_kernel_testcases[] = {
+ {0, DS_TYPE, 0, 0, GP_VECTOR, FIRST_SPARE_SEL, "ljmp desc.type!=code && desc.p=0"},
+ {0, NON_CONFORM_CS_TYPE, 3, 0, GP_VECTOR, FIRST_SPARE_SEL, "jmp non-conforming && dpl!=cpl && desc.p=0"},
+ {3, NON_CONFORM_CS_TYPE, 0, 0, GP_VECTOR, FIRST_SPARE_SEL, "ljmp conforming && rpl>cpl && desc.p=0"},
+ {0, CONFORM_CS_TYPE, 3, 0, GP_VECTOR, FIRST_SPARE_SEL, "ljmp conforming && dpl>cpl && desc.p=0"},
+ {0, NON_CONFORM_CS_TYPE, 0, 0, NP_VECTOR, FIRST_SPARE_SEL, "ljmp desc.p=0"},
+};
+
+static struct fep_test_case ljmp_user_testcases[] = {
+ {3, CONFORM_CS_TYPE, 0, 1, -1, -1, "ljmp dpl<cpl"},
+};
+
+static struct fep_test fep_test_ljmp = {
+ .type = FEP_TEST_LJMP,
+ .kernel_testcases = ljmp_kernel_testcases,
+ .kernel_testcases_count = sizeof(ljmp_kernel_testcases) / sizeof(struct fep_test_case),
+ .user_testcases = ljmp_user_testcases,
+ .user_testcases_count = sizeof(ljmp_user_testcases) / sizeof(struct fep_test_case),
+};
+
+static unsigned long fep_jmp_buf[2];
+static unsigned long *fep_jmp_buf_ptr = &fep_jmp_buf[0];
+
static void test_in_user(bool emulate, uint16_t rpl, enum fep_test_inst_type type);
#define TEST_LRET_ASM(seg, prefix) \
@@ -79,6 +103,14 @@ static void test_in_user(bool emulate, uint16_t rpl, enum fep_test_inst_type typ
: : [asm_seg]"r"(seg) \
: "memory");
+#define TEST_LJMP_ASM(seg, prefix) \
+ *(uint16_t *)(&fep_jmp_buf[1]) = seg; \
+ asm volatile("movq $1f, (%[mem])\n\t" \
+ prefix "rex64 ljmp *(%[mem])\n\t" \
+ "1:" \
+ : : [mem]"a"(fep_jmp_buf_ptr) \
+ : "memory"); \
+
#define TEST_FEP_RESULT(vector, error_code, msg) \
report(fep_vector == vector && \
fep_error_code == error_code,msg); \
@@ -383,19 +415,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;
@@ -991,6 +1010,13 @@ static void test_in_user(bool emulate, uint16_t rpl, enum fep_test_inst_type typ
TEST_LRET_ASM(seg, "");
}
break;
+ case FEP_TEST_LJMP:
+ if (emulate) {
+ TEST_LJMP_ASM(seg, KVM_FEP);
+ } else {
+ TEST_LJMP_ASM(seg, "");
+ }
+ break;
}
}
@@ -1018,6 +1044,10 @@ static void test_fep_common(bool emulate, struct fep_test *test)
TEST_FEP_INST(emulate, LRET, seg, t[i].vector,
t[i].error_code, t[i].msg);
break;
+ case FEP_TEST_LJMP:
+ TEST_FEP_INST(emulate, LJMP, seg, t[i].vector,
+ t[i].error_code, t[i].msg);
+ break;
}
}
@@ -1034,6 +1064,11 @@ static void test_fep_common(bool emulate, struct fep_test *test)
&dummy, t[i].vector,
t[i].error_code, t[i].msg);
break;
+ case FEP_TEST_LJMP:
+ TEST_FEP_INST_IN_USER(LJMP, emulate, t[i].rpl,
+ &dummy, t[i].vector,
+ t[i].error_code, t[i].msg);
+ break;
}
}
@@ -1041,6 +1076,46 @@ static void test_fep_common(bool emulate, struct fep_test *test)
handle_exception(NP_VECTOR, 0);
}
+static unsigned long get_ljmp_rip_advance(void)
+{
+ extern char ljmp_start, ljmp_end;
+ unsigned long ljmp_rip_advance = &ljmp_end - &ljmp_start;
+
+ fep_jmp_buf[0] = (unsigned long)&ljmp_end;
+ *(uint16_t *)(&fep_jmp_buf[1]) = KERNEL_CS;
+ asm volatile ("ljmp_start: rex64 ljmp *(%0); ljmp_end:\n\t"
+ ::"a"(fep_jmp_buf_ptr) : "memory");
+
+ return ljmp_rip_advance;
+}
+
+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");
+
+ printf("test ljmp in hw\n");
+ fep_test_ljmp.rip_advance = get_ljmp_rip_advance();
+ test_fep_common(false, &fep_test_ljmp);
+ /* reset CS to KERNEL_CS */
+ (void)get_ljmp_rip_advance();
+}
+
+static void test_em_ljmp(void *mem)
+{
+ printf("test ljmp in emulator\n");
+ test_fep_common(true, &fep_test_ljmp);
+ /* reset CS to KERNEL_CS */
+ (void)get_ljmp_rip_advance();
+}
+
static unsigned long get_lret_rip_advance(void)
{
extern char lret_start, lret_end;
@@ -1368,6 +1443,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, "
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 | 102 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 13 deletions(-)