xref: /kvm-unit-tests/x86/cet.c (revision 4c8a99ca02252d4a2bee43f4558fe47ce5ab7ec0)
1 
2 #include "libcflat.h"
3 #include "x86/desc.h"
4 #include "x86/processor.h"
5 #include "x86/vm.h"
6 #include "x86/msr.h"
7 #include "vmalloc.h"
8 #include "alloc_page.h"
9 #include "fault_test.h"
10 
11 static int cp_count;
12 static unsigned long invalid_offset = 0xffffffffffffff;
13 
14 static u64 cet_shstk_func(void)
15 {
16 	unsigned long *ret_addr, *ssp;
17 
18 	/* rdsspq %rax */
19 	asm volatile (".byte 0xf3, 0x48, 0x0f, 0x1e, 0xc8" : "=a"(ssp));
20 
21 	asm("movq %%rbp,%0" : "=r"(ret_addr));
22 	printf("The return-address in shadow-stack = 0x%lx, in normal stack = 0x%lx\n",
23 	       *ssp, *(ret_addr + 1));
24 
25 	/*
26 	 * In below line, it modifies the return address, it'll trigger #CP
27 	 * while function is returning. The error-code is 0x1, meaning it's
28 	 * caused by a near RET instruction, and the execution is terminated
29 	 * when HW detects the violation.
30 	 */
31 	printf("Try to temper the return-address, this causes #CP on returning...\n");
32 	*(ret_addr + 1) = 0xdeaddead;
33 
34 	return 0;
35 }
36 
37 static u64 cet_ibt_func(void)
38 {
39 	/*
40 	 * In below assembly code, the first instruction at label 2 is not
41 	 * endbr64, it'll trigger #CP with error code 0x3, and the execution
42 	 * is terminated when HW detects the violation.
43 	 */
44 	printf("No endbr64 instruction at jmp target, this triggers #CP...\n");
45 	asm volatile ("movq $2, %rcx\n"
46 		      "dec %rcx\n"
47 		      "leaq 2f(%rip), %rax\n"
48 		      "jmp *%rax \n"
49 		      "2:\n"
50 		      "dec %rcx\n");
51 	return 0;
52 }
53 
54 #define ENABLE_SHSTK_BIT 0x1
55 #define ENABLE_IBT_BIT   0x4
56 
57 static void handle_cp(struct ex_regs *regs)
58 {
59 	cp_count++;
60 	printf("In #CP exception handler, error_code = 0x%lx\n",
61 		regs->error_code);
62 	/* Below jmp is expected to trigger #GP */
63 	asm("jmpq *%0": :"m"(invalid_offset));
64 }
65 
66 int main(int ac, char **av)
67 {
68 	char *shstk_virt;
69 	unsigned long shstk_phys;
70 	unsigned long *ptep;
71 	pteval_t pte = 0;
72 	bool rvc;
73 
74 	cp_count = 0;
75 	if (!this_cpu_has(X86_FEATURE_SHSTK)) {
76 		printf("SHSTK not enabled\n");
77 		return report_summary();
78 	}
79 
80 	if (!this_cpu_has(X86_FEATURE_IBT)) {
81 		printf("IBT not enabled\n");
82 		return report_summary();
83 	}
84 
85 	setup_vm();
86 	handle_exception(21, handle_cp);
87 
88 	/* Allocate one page for shadow-stack. */
89 	shstk_virt = alloc_vpage();
90 	shstk_phys = (unsigned long)virt_to_phys(alloc_page());
91 
92 	/* Install the new page. */
93 	pte = shstk_phys | PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK;
94 	install_pte(current_page_table(), 1, shstk_virt, pte, 0);
95 	memset(shstk_virt, 0x0, PAGE_SIZE);
96 
97 	/* Mark it as shadow-stack page. */
98 	ptep = get_pte_level(current_page_table(), shstk_virt, 1);
99 	*ptep &= ~PT_WRITABLE_MASK;
100 	*ptep |= PT_DIRTY_MASK;
101 
102 	/* Flush the paging cache. */
103 	invlpg((void *)shstk_phys);
104 
105 	/* Enable shadow-stack protection */
106 	wrmsr(MSR_IA32_U_CET, ENABLE_SHSTK_BIT);
107 
108 	/* Store shadow-stack pointer. */
109 	wrmsr(MSR_IA32_PL3_SSP, (u64)(shstk_virt + 0x1000));
110 
111 	/* Enable CET master control bit in CR4. */
112 	write_cr4(read_cr4() | X86_CR4_CET);
113 
114 	printf("Unit test for CET user mode...\n");
115 	run_in_user((usermode_func)cet_shstk_func, GP_VECTOR, 0, 0, 0, 0, &rvc);
116 	report(cp_count == 1, "Completed shadow-stack protection test successfully.");
117 	cp_count = 0;
118 
119 	/* Enable indirect-branch tracking */
120 	wrmsr(MSR_IA32_U_CET, ENABLE_IBT_BIT);
121 
122 	run_in_user((usermode_func)cet_ibt_func, GP_VECTOR, 0, 0, 0, 0, &rvc);
123 	report(cp_count == 1, "Completed Indirect-branch tracking test successfully.");
124 
125 	write_cr4(read_cr4() & ~X86_CR4_CET);
126 	wrmsr(MSR_IA32_U_CET, 0);
127 
128 	return report_summary();
129 }
130