xref: /linux/tools/testing/selftests/x86/sigframe_fpu_portability.c (revision dd8845db6d05e81532110c271d5f1cef3975916a)
1 // SPDX-License-Identifier: GPL-2.0-only
2 #define _GNU_SOURCE
3 #include <stdio.h>
4 #include <signal.h>
5 #include <string.h>
6 #include <sys/ucontext.h>
7 #include <stdlib.h>
8 #include <stdint.h>
9 #include <stdbool.h>
10 #include <cpuid.h>
11 #include <unistd.h>
12 #include <sys/syscall.h>
13 #include <stddef.h>
14 #include <setjmp.h>
15 
16 #include "helpers.h"
17 #include "xstate.h"
18 
19 #ifndef FP_XSTATE_MAGIC2_SIZE
20 #define FP_XSTATE_MAGIC2_SIZE	sizeof(FP_XSTATE_MAGIC2)
21 #endif
22 
23 /*
24  * This test verifies the FPU portability and consistency of the signal frame.
25  *
26  * - test_valid_shrunk_xstate_size:
27  *   Verifies that the kernel restores state from a frame with xstate_size
28  *   shrunk to only include active features.
29  *
30  * - test_invalid_shrunk_xstate_size:
31  *   Verifies that the kernel rejects a frame if xstate_size is too small for
32  *   the features enabled in xfeatures.
33  */
34 
35 #define SIGFRAME_XSTATE_HDR_OFFSET	512
36 #define XSTATE_SSE_ONLY_SIZE	(SIGFRAME_XSTATE_HDR_OFFSET + XSAVE_HDR_SIZE)
37 #define XFEATURE_MASK_FPSSE	((1 << XFEATURE_FP) | (1 << XFEATURE_SSE))
38 
39 static uint32_t ymm_offset;
40 static uint32_t xstate_size_ymm;
41 static pid_t self_pid;
42 
43 /*
44  * Load %ymm0 from @v, invoke SYS_kill to deliver @sig, and store the
45  * restored %ymm0 state back into @v within a single inline assembly
46  * block so the compiler cannot clobber %xmm0/%ymm0 between steps.
47  */
48 __attribute__((target("avx")))
raise_with_ymm0(int sig,uint64_t * v)49 static void raise_with_ymm0(int sig, uint64_t *v)
50 {
51 	register long rax asm("rax") = SYS_kill;
52 	register long rdi asm("rdi") = self_pid;
53 	register long rsi asm("rsi") = sig;
54 
55 	asm volatile ("vmovdqu %0, %%ymm0\n\t"
56 		      "syscall\n\t"
57 		      "vmovdqu %%ymm0, %0"
58 		      : "+m" (*(char (*)[32])v), "+r" (rax)
59 		      : "r" (rdi), "r" (rsi)
60 		      : "rcx", "r11", "ymm0", "memory");
61 }
62 
63 /*
64  * Avoid using printf() in signal handlers as it is not
65  * async-signal-safe.
66  */
67 #define SIGNAL_BUF_LEN 1024
68 static char sig_err_buf[SIGNAL_BUF_LEN];
69 
sig_print(const char * msg)70 static void sig_print(const char *msg)
71 {
72 	int left = SIGNAL_BUF_LEN - strlen(sig_err_buf) - 1;
73 
74 	strncat(sig_err_buf, msg, left);
75 }
76 
check_avx_support(void)77 static void check_avx_support(void)
78 {
79 	uint32_t eax, ebx, ecx, edx;
80 	struct xstate_info xstate;
81 
82 	/* Check CPUID.01H:ECX.OSXSAVE[bit 27] before calling xgetbv to avoid #UD */
83 	__cpuid(1, eax, ebx, ecx, edx);
84 	if (!(ecx & (1 << 27)))
85 		ksft_exit_skip("OSXSAVE not enabled by OS\n");
86 
87 	/* Check XCR0[2] (YMM) is enabled by OS */
88 	if (!(xgetbv(0) & (1 << XFEATURE_YMM)))
89 		ksft_exit_skip("AVX (YMM) not enabled in XCR0\n");
90 
91 	xstate = get_xstate_info(XFEATURE_YMM);
92 	if (!xstate.size)
93 		ksft_exit_skip("AVX not supported by hardware\n");
94 
95 	ymm_offset = xstate.xbuf_offset;
96 	xstate_size_ymm = xstate.xbuf_offset + xstate.size;
97 }
98 
99 #define TEST_YMMH_VAL (0x5656565656565656UL)
100 
__handle_shrunk_xstate_size(int sig,siginfo_t * si,void * ucp,bool valid_size)101 static void __handle_shrunk_xstate_size(int sig, siginfo_t *si, void *ucp, bool valid_size)
102 {
103 	uint64_t xfeatures, *ymmh_p;
104 	struct xsave_buffer *xbuf;
105 	struct _fpx_sw_bytes *sw;
106 	ucontext_t *uc = ucp;
107 	void *fp;
108 
109 	fp = uc->uc_mcontext.fpregs;
110 	if (!fp) {
111 		sig_print("fpregs is NULL\n");
112 		return;
113 	}
114 
115 	sw = get_fpx_sw_bytes(fp);
116 	if (sw->magic1 != FP_XSTATE_MAGIC1) {
117 		sig_print("magic1 is not valid\n");
118 		return;
119 	}
120 
121 	xbuf = (struct xsave_buffer *)fp;
122 
123 	/*
124 	 * Both test cases shrink the frame to contain only AVX (FP + SSE + YMM).
125 	 * If valid_size is true, set xstate_size to match the enabled features.
126 	 * If valid_size is false, set xstate_size too small (SSE only), which
127 	 * the kernel must reject.
128 	 */
129 	if (valid_size)
130 		sw->xstate_size = xstate_size_ymm;
131 	else
132 		sw->xstate_size = XSTATE_SSE_ONLY_SIZE;
133 
134 	xfeatures = get_xstatebv(xbuf);
135 	xfeatures &= XFEATURE_MASK_FPSSE | (1 << XFEATURE_YMM);
136 	set_xstatebv(xbuf, xfeatures);
137 	set_fpx_sw_bytes_features(fp, xfeatures);
138 
139 	*(uint32_t *)(fp + sw->xstate_size) = FP_XSTATE_MAGIC2;
140 
141 	if (valid_size) {
142 		ymmh_p = (uint64_t *)(fp + ymm_offset);
143 		ymmh_p[0] = TEST_YMMH_VAL;
144 		ymmh_p[1] = TEST_YMMH_VAL + 1;
145 	}
146 
147 	/* clear everything after MAGIC2. */
148 	if (sw->xstate_size + FP_XSTATE_MAGIC2_SIZE < sw->extended_size)
149 		memset(fp + sw->xstate_size + FP_XSTATE_MAGIC2_SIZE, 0,
150 		       sw->extended_size - sw->xstate_size - FP_XSTATE_MAGIC2_SIZE);
151 }
152 
handle_valid_shrunk_xstate_size(int sig,siginfo_t * si,void * ucp)153 static void handle_valid_shrunk_xstate_size(int sig, siginfo_t *si, void *ucp)
154 {
155 	__handle_shrunk_xstate_size(sig, si, ucp, true);
156 }
157 
handle_invalid_shrunk_xstate_size(int sig,siginfo_t * si,void * ucp)158 static void handle_invalid_shrunk_xstate_size(int sig, siginfo_t *si, void *ucp)
159 {
160 	__handle_shrunk_xstate_size(sig, si, ucp, false);
161 }
162 
test_valid_shrunk_xstate_size(void)163 static void test_valid_shrunk_xstate_size(void)
164 {
165 	uint64_t v[4];
166 
167 	sig_err_buf[0] = 0;
168 	sethandler(SIGUSR1, handle_valid_shrunk_xstate_size, 0);
169 
170 	v[0] = 0x1111111111111111ULL;
171 	v[1] = 0x2222222222222222ULL;
172 	v[2] = 0x3333333333333333ULL;
173 	v[3] = 0x4444444444444444ULL;
174 	raise_with_ymm0(SIGUSR1, v);
175 
176 	if (sig_err_buf[0])
177 		ksft_test_result_fail("%s\n", sig_err_buf);
178 	else if (v[2] == TEST_YMMH_VAL && v[3] == (TEST_YMMH_VAL + 1))
179 		ksft_test_result_pass("YMM state restored correctly from shrunk frame\n");
180 	else
181 		ksft_test_result_fail(
182 				"Got upper bits: 0x%lx 0x%lx (expected %lx %lx)\n",
183 			       v[2], v[3], TEST_YMMH_VAL, TEST_YMMH_VAL + 1);
184 
185 	clearhandler(SIGUSR1);
186 }
187 
188 static sigjmp_buf segv_jmpbuf;
189 
handle_segv(int sig,siginfo_t * si,void * ucp)190 static void handle_segv(int sig, siginfo_t *si, void *ucp)
191 {
192 	siglongjmp(segv_jmpbuf, 1);
193 }
194 
test_invalid_shrunk_xstate_size(void)195 static void test_invalid_shrunk_xstate_size(void)
196 {
197 	uint64_t v[4];
198 
199 	sig_err_buf[0] = 0;
200 	sethandler(SIGUSR1, handle_invalid_shrunk_xstate_size, 0);
201 	sethandler(SIGSEGV, handle_segv, 0);
202 
203 	if (sigsetjmp(segv_jmpbuf, 1) == 0) {
204 		v[0] = 0x1111111111111111ULL;
205 		v[1] = 0x2222222222222222ULL;
206 		v[2] = 0x3333333333333333ULL;
207 		v[3] = 0x4444444444444444ULL;
208 		raise_with_ymm0(SIGUSR1, v);
209 		sig_print("Inconsistent size was NOT rejected\n");
210 	}
211 
212 	clearhandler(SIGUSR1);
213 	clearhandler(SIGSEGV);
214 
215 	if (sig_err_buf[0])
216 		ksft_test_result_fail("%s\n", sig_err_buf);
217 	else
218 		ksft_test_result_pass("Inconsistent size correctly rejected\n");
219 }
220 
main(void)221 int main(void)
222 {
223 	ksft_print_header();
224 	ksft_set_plan(2);
225 
226 	self_pid = getpid();
227 
228 	check_avx_support();
229 
230 	test_valid_shrunk_xstate_size();
231 	test_invalid_shrunk_xstate_size();
232 
233 	ksft_finished();
234 	return 0;
235 }
236