diff --git a/x86/emulator.c b/x86/emulator.c
index c5f584a9d8cc..480333a40eba 100644
--- a/x86/emulator.c
+++ b/x86/emulator.c
@@ -19,6 +19,88 @@ static int exceptions;
 #define KVM_FEP "ud2; .byte 'k', 'v', 'm';"
 #define KVM_FEP_LENGTH 5
 static int fep_available = 1;
+static unsigned int fep_vector = -1;
+static unsigned int fep_error_code = -1;
+
+struct fep_test_case {
+	uint16_t rpl;
+	uint16_t type;
+	uint16_t dpl;
+	uint16_t p;
+	unsigned int vector;
+	unsigned int error_code;
+	const char *msg;
+};
+
+enum fep_test_inst_type {
+	FEP_TEST_LRET,
+};
+
+struct fep_test {
+	enum fep_test_inst_type type;
+	unsigned long rip_advance;
+	struct fep_test_case *kernel_testcases;
+	unsigned int kernel_testcases_count;
+	struct fep_test_case *user_testcases;
+	unsigned int user_testcases_count;
+};
+
+#define NON_CONFORM_CS_TYPE	0xb
+#define CONFORM_CS_TYPE		0xf
+#define DS_TYPE			0x3
+
+static struct fep_test_case lret_kernel_testcases[] = {
+	{0, DS_TYPE, 0, 0, GP_VECTOR, FIRST_SPARE_SEL, "lret desc.type!=code && desc.p=0"},
+	{0, NON_CONFORM_CS_TYPE, 3, 0, GP_VECTOR, FIRST_SPARE_SEL, "lret non-conforming && dpl!=rpl && desc.p=0"},
+	{0, CONFORM_CS_TYPE, 3, 0, GP_VECTOR, FIRST_SPARE_SEL, "lret conforming && dpl>rpl && desc.p=0"},
+	{0, NON_CONFORM_CS_TYPE, 0, 0, NP_VECTOR, FIRST_SPARE_SEL, "lret desc.p=0"},
+};
+
+static struct fep_test_case lret_user_testcases[] = {
+	{0, NON_CONFORM_CS_TYPE, 3, 1, GP_VECTOR, FIRST_SPARE_SEL, "lret rpl<cpl"},
+};
+
+static struct fep_test fep_test_lret = {
+	.type = FEP_TEST_LRET,
+	.kernel_testcases = lret_kernel_testcases,
+	.kernel_testcases_count = sizeof(lret_kernel_testcases) / sizeof(struct fep_test_case),
+	.user_testcases = lret_user_testcases,
+	.user_testcases_count = sizeof(lret_user_testcases) / sizeof(struct fep_test_case),
+};
+
+static void test_in_user(bool emulate, uint16_t rpl, enum fep_test_inst_type type);
+
+#define TEST_LRET_ASM(seg, prefix)		\
+	asm volatile("pushq %[asm_seg]\n\t"	\
+		     "pushq $1f\n\t"		\
+		      prefix "lretq\n\t"	\
+		     "addq $16, %%rsp\n\t"	\
+		     "1:"			\
+		     : : [asm_seg]"r"(seg)	\
+		     : "memory");
+
+#define TEST_FEP_RESULT(vector, error_code, msg)	\
+	report(fep_vector == vector &&			\
+	       fep_error_code == error_code,msg);	\
+	fep_vector = -1;				\
+	fep_error_code = -1;
+
+#define TEST_FEP_INST(emulate, inst, seg, vector, error_code, msg) \
+	do {							   \
+		if (emulate) {					   \
+			TEST_##inst##_ASM(seg, KVM_FEP);	   \
+		} else {					   \
+			TEST_##inst##_ASM(seg, "");		   \
+		}						   \
+		TEST_FEP_RESULT(vector, error_code, msg);	   \
+	} while (0)
+
+#define TEST_FEP_INST_IN_USER(inst, emulate, rpl, dummy, vector, error_code, msg)\
+	do {								\
+		run_in_user((usermode_func)test_in_user, UD_VECTOR,	\
+			     emulate, rpl, FEP_TEST_##inst, 0, dummy);	\
+		TEST_FEP_RESULT(vector, error_code, msg);		\
+	} while (0)
 
 struct regs {
 	u64 rax, rbx, rcx, rdx;
@@ -890,6 +972,103 @@ static void test_mov_dr(uint64_t *mem)
 	report(rax == dr6_fixed_1, "mov_dr6");
 }
 
+static void fep_exception_handler(struct ex_regs *regs)
+{
+	fep_vector = regs->vector;
+	fep_error_code = regs->error_code;
+	regs->rip += rip_advance;
+}
+
+static void test_in_user(bool emulate, uint16_t rpl, enum fep_test_inst_type type)
+{
+	uint16_t seg = FIRST_SPARE_SEL | rpl;
+
+	switch (type) {
+	case FEP_TEST_LRET:
+		if (emulate) {
+			TEST_LRET_ASM(seg, KVM_FEP);
+		} else {
+			TEST_LRET_ASM(seg, "");
+		}
+		break;
+	}
+}
+
+static void test_fep_common(bool emulate, struct fep_test *test)
+{
+	int i;
+	bool dummy;
+	struct fep_test_case *t;
+	uint16_t seg = FIRST_SPARE_SEL;
+
+	handle_exception(GP_VECTOR, fep_exception_handler);
+	handle_exception(NP_VECTOR, fep_exception_handler);
+	rip_advance = test->rip_advance;
+
+	gdt[seg / 8] = gdt[KERNEL_CS / 8];
+	t = test->kernel_testcases;
+	for (i = 0; i < test->kernel_testcases_count; i++) {
+		seg = FIRST_SPARE_SEL | t[i].rpl;
+		gdt[seg / 8].type = t[i].type;
+		gdt[seg / 8].dpl = t[i].dpl;
+		gdt[seg / 8].p = t[i].p;
+
+		switch (test->type) {
+		case FEP_TEST_LRET:
+			TEST_FEP_INST(emulate, LRET, seg, t[i].vector,
+				      t[i].error_code, t[i].msg);
+			break;
+		}
+	}
+
+	gdt[seg / 8] = gdt[USER_CS64 / 8];
+	t = test->user_testcases;
+	for (i = 0; i < test->user_testcases_count; i++) {
+		gdt[seg / 8].type = t[i].type;
+		gdt[seg / 8].dpl = t[i].dpl;
+		gdt[seg / 8].p = t[i].p;
+
+		switch (test->type) {
+		case FEP_TEST_LRET:
+			TEST_FEP_INST_IN_USER(LRET, emulate, t[i].rpl,
+					      &dummy, t[i].vector,
+				              t[i].error_code, t[i].msg);
+			break;
+		}
+	}
+
+	handle_exception(GP_VECTOR, 0);
+	handle_exception(NP_VECTOR, 0);
+}
+
+static unsigned long get_lret_rip_advance(void)
+{
+	extern char lret_start, lret_end;
+	unsigned long lret_rip_advance = &lret_end - &lret_start;
+
+	asm volatile("data16 mov %%cs, %%rax\n\t"
+		     "pushq %%rax\n\t"
+		     "pushq $1f\n\t"
+		     "lret_start: lretq; lret_end:\n\t"
+		     "1:\n\t"
+		     : : : "ax", "memory");
+
+	return lret_rip_advance;
+}
+
+static void test_lret(uint64_t *mem)
+{
+	printf("test lret in hw\n");
+	fep_test_lret.rip_advance = get_lret_rip_advance();
+	test_fep_common(false, &fep_test_lret);
+}
+
+static void test_em_lret(uint64_t *mem)
+{
+	printf("test lret in emulator\n");
+	test_fep_common(true, &fep_test_lret);
+}
+
 static void test_push16(uint64_t *mem)
 {
 	uint64_t rsp1, rsp2;
@@ -1164,6 +1343,7 @@ int main(void)
 	test_smsw(mem);
 	test_lmsw();
 	test_ljmp(mem);
+	test_lret(mem);
 	test_stringio();
 	test_incdecnotneg(mem);
 	test_btc(mem);
@@ -1188,6 +1368,7 @@ int main(void)
 		test_smsw_reg(mem);
 		test_nop(mem);
 		test_mov_dr(mem);
+		test_em_lret(mem);
 	} else {
 		report_skip("skipping register-only tests, "
 			    "use kvm.force_emulation_prefix=1 to enable");
