Re: [kvm-unit-tests PATCH v2 1/2] x86/emulator: Add some tests for lret instruction emulation

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Thanks for your kind review.

On Thu, Feb 10, 2022 at 06:07:11AM +0800, Sean Christopherson wrote:
> Nit, "far ret" instead of "lret" in the shortlog.
> 
> On Tue, Feb 08, 2022, Hou Wenlong wrote:
> > Per Intel's SDM on the "Instruction Set Reference", when
> > loading segment descriptor for far return, 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.
> > 
> > And if RPL < CPL, it should trigger #GP, but the check is missing
> > in emulator.
> > 
> > So add some tests for lret 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@xxxxxxxxxxxx>
> > ---
> 
> Was this tested?  There are multiple compilation errors with gcc...
> 
> x86/emulator.c: In function ‘test_far_xfer’:
> x86/emulator.c:1034:10: error: format not a string literal and no format arguments [-Werror=format-security]
>  1034 |          far_xfer_error_code == t->error_code, t->msg);
>       |          ^~~~~~~~~~~~~~~~~~~
> cc1: all warnings being treated as errors
> make: *** [<builtin>: x86/emulator.o] Error 1
>
> x86/emulator.c: Assembler messages:
> x86/emulator.c:81: Error: incorrect register `%si' used with `q' suffix
> x86/emulator.c:83: Error: incorrect register `%si' used with `q' suffix
> make: *** [<builtin>: x86/emulator.o] Error 1
> 
The compilation was OK when I tested on my machine. But my gcc is old and
customted, -Wformat-security is not enabled by default. After switching
to higher version of gcc, I get compilation errors too.

> > +static struct far_xfer_test_case far_ret_testcases[] = {
> > +	{0, DS_TYPE, 0, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "lret desc.type!=code && desc.p=0"},
> > +	{0, NON_CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "lret non-conforming && dpl!=rpl && desc.p=0"},
> > +	{0, CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "lret conforming && dpl>rpl && desc.p=0"},
> > +	{0, NON_CONFORM_CS_TYPE, 0, 0, false, NP_VECTOR, FIRST_SPARE_SEL, "lret desc.p=0"},
> > +	{0, NON_CONFORM_CS_TYPE, 3, 1, true, GP_VECTOR, FIRST_SPARE_SEL, "lret rpl<cpl"},
> 
> Since the framework is responsible for specifying the selector and the expected
> error code is the same for all subtests that expect an exception, just drop the
> error code.  E.g. if the framework decides to use a different selector than these
> would need to be updated too, for no real benefit.
> 
> It'd also be helpful to align everything.
> 
> s/lret/far ret.  Actually to cut down on copy+paste, and because we need formatting
> anyways (see below), how moving the instruction name into the far_xfer_test?
> 
> > +};
> > +
> > +static struct far_xfer_test far_ret_test = {
> > +	.insn = FAR_XFER_RET,
> > +	.testcases = &far_ret_testcases[0],
> > +	.nr_testcases = sizeof(far_ret_testcases) / sizeof(struct far_xfer_test_case),
> > +};
> > +
> > +#define TEST_FAR_RET_ASM(seg, prefix)		\
> > +	asm volatile("lea 1f(%%rip), %%rax\n\t" \
> > +		     "pushq %[asm_seg]\n\t"	\
> > +		     "pushq $2f\n\t"		\
> > +		      prefix "lretq\n\t"	\
> > +		     "1: addq $16, %%rsp\n\t"	\
> > +		     "2:"			\
> > +		     : : [asm_seg]"r"(seg)	\
> 
> Because @seg is a u16, gcc emites "pushq SI".  Kinda dumb, but...
> 
> @@ -72,7 +71,7 @@ static struct far_xfer_test far_ret_test = {
>                       prefix "lretq\n\t"        \
>                      "1: addq $16, %%rsp\n\t"   \
>                      "2:"                       \
> -                    : : [asm_seg]"r"(seg)      \
> +                    : : [asm_seg]"r"((u64)seg) \
>                      : "eax", "memory");
> 
> > +		     : "eax", "memory");
> > +
> > +static inline void test_far_ret_asm(uint16_t seg, bool force_emulation)
> > +{
> > +	if (force_emulation) {
> 
> No need for braces.  Ah, the macro is missing ({ ... }) to force it to be evaluated
> as a single statement.  It should be.
> 
> Side topic, a ternary operator would be nice, but I don't know how to force string
> concatentation for this case...
>
I don't know too, so I use if/else statement here.

> #define TEST_FAR_RET_ASM(seg, prefix)		\
> ({						\
> 	asm volatile("lea 1f(%%rip), %%rax\n\t" \
> 		     "pushq %[asm_seg]\n\t"	\
> 		     "pushq $2f\n\t"		\
> 		      prefix "lretq\n\t"	\
> 		     "1: addq $16, %%rsp\n\t"	\
> 		     "2:"			\
> 		     : : [asm_seg]"r"((u64)seg)	\
> 		     : "eax", "memory");	\
> })
> 
> > +		TEST_FAR_RET_ASM(seg, KVM_FEP);
> > +	} else {
> > +		TEST_FAR_RET_ASM(seg, "");
> > +	}
> > +}
> 
> This only has a single caller, just open code the macros in the case statements
> of __test_far_xfer().  (My apologies if I suggested this in the last version).
> 
> >  struct regs {
> >  	u64 rax, rbx, rcx, rdx;
> > @@ -891,6 +951,74 @@ static void test_mov_dr(uint64_t *mem)
> >  	report(rax == dr6_fixed_1, "mov_dr6");
> >  }
> >  
> > +static void far_xfer_exception_handler(struct ex_regs *regs)
> > +{
> > +	far_xfer_vector = regs->vector;
> > +	far_xfer_error_code = regs->error_code;
> > +	regs->rip = regs->rax;;
> 
> Double semi-colon.
> 
> > +}
> > +
> > +static void __test_far_xfer(enum far_xfer_insn insn, uint16_t seg,
> > +			    bool force_emulation)
> > +{
> > +	switch (insn) {
> > +	case FAR_XFER_RET:
> > +		test_far_ret_asm(seg, force_emulation);
> > +		break;
> > +	default:
> > +		report_fail("unknown instructions");
> 
> It's worth spitting out the insn, it might save someone a few seconds/minutes, e.g.
> 
> 		report_fail("Unexpected insn enum = %d\n", insn);
> 
> > +		break;
> > +	}
> > +}
> > +
> > +static void test_far_xfer(bool force_emulation, struct far_xfer_test *test)
> > +{
> > +	struct far_xfer_test_case *t;
> > +	uint16_t seg;
> > +	bool ign;
> > +	int i;
> > +
> > +	handle_exception(GP_VECTOR, far_xfer_exception_handler);
> > +	handle_exception(NP_VECTOR, far_xfer_exception_handler);
> > +
> > +	for (i = 0; i < test->nr_testcases; i++) {
> > +		t = &test->testcases[i];
> > +
> > +		seg = FIRST_SPARE_SEL | t->rpl;
> > +		gdt[seg / 8] = gdt[(t->usermode ? USER_CS64 : KERNEL_CS) / 8];
> > +		gdt[seg / 8].type = t->type;
> > +		gdt[seg / 8].dpl = t->dpl;
> > +		gdt[seg / 8].p = t->p;
> > +
> > +		far_xfer_vector = -1;
> > +		far_xfer_error_code = -1;
> > +
> > +		if (t->usermode)
> > +			run_in_user((usermode_func)__test_far_xfer, UD_VECTOR,
> > +				    test->insn, seg, force_emulation, 0, &ign);
> > +		else
> > +			__test_far_xfer(test->insn, seg, force_emulation);
> > +
> > +		report(far_xfer_vector == t->vector &&
> > +		       far_xfer_error_code == t->error_code, t->msg);
> 
> To avoid having to specify the error code, just do:
> 
> 		       (far_xfer_vector < 0 || far_xfer_error_code == FIRST_SPARE_SEL),
> 
> far_xfer_vector and t->vector need to be signed ints, but that's perfectly ok,
> vector fits in a u8.  Printing out the expecte vs. actual is also helpful for
> debug (pet peeve of mine in KUT...).  I doesn't have to be super fancy formatting,
> just enough so that the user doesn't have to modify the test to figure out what
> went wrong.
> 
> And with the insn_name + msg + target (see below) formatting:
> 
> 
> 		report(far_xfer_vector == t->vector &&
> 		       (far_xfer_vector < 0 || far_xfer_error_code == FIRST_SPARE_SEL),
> 		       "%s on %s, %s: wanted %d (%d), got %d (%d)",
> 		       test->insn_name, force_emulation ? "emuator" : "hardware", t->msg,
> 		       t->vector, t->vector < 0 ? -1 : FIRST_SPARE_SEL,
> 		       far_xfer_vector, far_xfer_error_code);
> 
> > +	}
> > +
> > +	handle_exception(GP_VECTOR, 0);
> > +	handle_exception(NP_VECTOR, 0);
> > +}
> > +
> > +static void test_lret(uint64_t *mem)
> 
> test_far_ret()
> 
> > +{
> > +	printf("test lret in hw\n");
> 
> Rather than print this out before, just spit out the "target" in the report.
> 
> > +	test_far_xfer(false, &far_ret_test);
> > +}
> 
> All of the above (I think) feeback:
> 
> ---
>  x86/emulator.c | 57 +++++++++++++++++++++++++-------------------------
>  1 file changed, 28 insertions(+), 29 deletions(-)
> 
> diff --git a/x86/emulator.c b/x86/emulator.c
> index 2a35caf..d28e36c 100644
> --- a/x86/emulator.c
> +++ b/x86/emulator.c
> @@ -21,7 +21,7 @@ static int exceptions;
>  #define KVM_FEP "ud2; .byte 'k', 'v', 'm';"
>  #define KVM_FEP_LENGTH 5
>  static int fep_available = 1;
> -static unsigned int far_xfer_vector = -1;
> +static int far_xfer_vector = -1;
>  static unsigned int far_xfer_error_code = -1;
> 
>  struct far_xfer_test_case {
> @@ -30,8 +30,7 @@ struct far_xfer_test_case {
>  	uint16_t dpl;
>  	uint16_t p;
>  	bool usermode;
> -	unsigned int vector;
> -	unsigned int error_code;
> +	int vector;
>  	const char *msg;
>  };
> 
> @@ -41,6 +40,7 @@ enum far_xfer_insn {
> 
>  struct far_xfer_test {
>  	enum far_xfer_insn insn;
> +	const char *insn_name;
>  	struct far_xfer_test_case *testcases;
>  	unsigned int nr_testcases;
>  };
> @@ -50,37 +50,31 @@ struct far_xfer_test {
>  #define DS_TYPE			0x3
> 
>  static struct far_xfer_test_case far_ret_testcases[] = {
> -	{0, DS_TYPE, 0, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "lret desc.type!=code && desc.p=0"},
> -	{0, NON_CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "lret non-conforming && dpl!=rpl && desc.p=0"},
> -	{0, CONFORM_CS_TYPE, 3, 0, false, GP_VECTOR, FIRST_SPARE_SEL, "lret conforming && dpl>rpl && desc.p=0"},
> -	{0, NON_CONFORM_CS_TYPE, 0, 0, false, NP_VECTOR, FIRST_SPARE_SEL, "lret desc.p=0"},
> -	{0, NON_CONFORM_CS_TYPE, 3, 1, true, GP_VECTOR, FIRST_SPARE_SEL, "lret rpl<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!=rpl && desc.p=0"},
> +	{0, CONFORM_CS_TYPE,     3, 0, false, GP_VECTOR, "conforming && dpl>rpl && desc.p=0"},
> +	{0, NON_CONFORM_CS_TYPE, 0, 0, false, NP_VECTOR, "desc.p=0"},
> +	{0, NON_CONFORM_CS_TYPE, 3, 1, true,  GP_VECTOR, "rpl<cpl"},
>  };
> 
>  static struct far_xfer_test far_ret_test = {
>  	.insn = FAR_XFER_RET,
> +	.insn_name = "far ret",
>  	.testcases = &far_ret_testcases[0],
>  	.nr_testcases = sizeof(far_ret_testcases) / sizeof(struct far_xfer_test_case),
>  };
> 
>  #define TEST_FAR_RET_ASM(seg, prefix)		\
> +({						\
>  	asm volatile("lea 1f(%%rip), %%rax\n\t" \
>  		     "pushq %[asm_seg]\n\t"	\
>  		     "pushq $2f\n\t"		\
>  		      prefix "lretq\n\t"	\
>  		     "1: addq $16, %%rsp\n\t"	\
>  		     "2:"			\
> -		     : : [asm_seg]"r"(seg)	\
> -		     : "eax", "memory");
> -
> -static inline void test_far_ret_asm(uint16_t seg, bool force_emulation)
> -{
> -	if (force_emulation) {
> -		TEST_FAR_RET_ASM(seg, KVM_FEP);
> -	} else {
> -		TEST_FAR_RET_ASM(seg, "");
> -	}
> -}
> +		     : : [asm_seg]"r"((u64)seg)	\
> +		     : "eax", "memory");	\
> +})
> 
>  struct regs {
>  	u64 rax, rbx, rcx, rdx;
> @@ -959,7 +953,7 @@ static void far_xfer_exception_handler(struct ex_regs *regs)
>  {
>  	far_xfer_vector = regs->vector;
>  	far_xfer_error_code = regs->error_code;
> -	regs->rip = regs->rax;;
> +	regs->rip = regs->rax;
>  }
> 
>  static void __test_far_xfer(enum far_xfer_insn insn, uint16_t seg,
> @@ -967,10 +961,13 @@ static void __test_far_xfer(enum far_xfer_insn insn, uint16_t seg,
>  {
>  	switch (insn) {
>  	case FAR_XFER_RET:
> -		test_far_ret_asm(seg, force_emulation);
> +		if (force_emulation)
> +			TEST_FAR_RET_ASM(seg, KVM_FEP);
> +		else
> +			TEST_FAR_RET_ASM(seg, "");
>  		break;
>  	default:
> -		report_fail("unknown instructions");
> +		report_fail("Unexpected insn enum = %d\n", insn);
>  		break;
>  	}
>  }
> @@ -1004,22 +1001,24 @@ static void test_far_xfer(bool force_emulation, struct far_xfer_test *test)
>  			__test_far_xfer(test->insn, seg, force_emulation);
> 
>  		report(far_xfer_vector == t->vector &&
> -		       far_xfer_error_code == t->error_code, "%s", t->msg);
> +		       (far_xfer_vector < 0 || far_xfer_error_code == FIRST_SPARE_SEL),
> +		       "%s on %s, %s: wanted %d (%d), got %d (%d)",
> +		       test->insn_name, force_emulation ? "emuator" : "hardware", t->msg,
> +		       t->vector, t->vector < 0 ? -1 : FIRST_SPARE_SEL,
> +		       far_xfer_vector, far_xfer_error_code);
>  	}
> 
>  	handle_exception(GP_VECTOR, 0);
>  	handle_exception(NP_VECTOR, 0);
>  }
> 
> -static void test_lret(uint64_t *mem)
> +static void test_far_ret(uint64_t *mem)
>  {
> -	printf("test lret in hw\n");
>  	test_far_xfer(false, &far_ret_test);
>  }
> 
> -static void test_em_lret(uint64_t *mem)
> +static void test_em_far_ret(uint64_t *mem)
>  {
> -	printf("test lret in emulator\n");
>  	test_far_xfer(true, &far_ret_test);
>  }
> 
> @@ -1297,7 +1296,7 @@ int main(void)
>  	test_smsw(mem);
>  	test_lmsw();
>  	test_ljmp(mem);
> -	test_lret(mem);
> +	test_far_ret(mem);
>  	test_stringio();
>  	test_incdecnotneg(mem);
>  	test_btc(mem);
> @@ -1322,7 +1321,7 @@ int main(void)
>  		test_smsw_reg(mem);
>  		test_nop(mem);
>  		test_mov_dr(mem);
> -		test_em_lret(mem);
> +		test_em_far_ret(mem);
>  	} else {
>  		report_skip("skipping register-only tests, "
>  			    "use kvm.force_emulation_prefix=1 to enable");
> 
> base-commit: b56a1a58abfcc6abc16e782f13505f4495cf59e8
> --



[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux