// SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "helpers.h" #include "xstate.h" /* * The userspace xstate test suite is designed to be generic and operates * with randomized xstate data. However, some states require special handling: * * - PKRU and XTILECFG need specific adjustments, such as modifying * randomization behavior or using fixed values. * - But, PKRU already has a dedicated test suite in /tools/selftests/mm. * - Legacy states (FP and SSE) are excluded, as they are not considered * part of extended states (xstates) and their usage is already deeply * integrated into user-space libraries. */ #define XFEATURE_MASK_TEST_SUPPORTED \ ((1 << XFEATURE_YMM) | \ (1 << XFEATURE_OPMASK) | \ (1 << XFEATURE_ZMM_Hi256) | \ (1 << XFEATURE_Hi16_ZMM) | \ (1 << XFEATURE_XTILEDATA)) static inline uint64_t xgetbv(uint32_t index) { uint32_t eax, edx; asm volatile("xgetbv" : "=a" (eax), "=d" (edx) : "c" (index)); return eax + ((uint64_t)edx << 32); } static inline uint64_t get_xstatebv(struct xsave_buffer *xbuf) { return *(uint64_t *)(&xbuf->header); } static struct xstate_info xstate; struct futex_info { unsigned int iterations; struct futex_info *next; pthread_mutex_t mutex; pthread_t thread; bool valid; int nr; }; static inline void load_rand_xstate(struct xstate_info *xstate, struct xsave_buffer *xbuf) { clear_xstate_header(xbuf); set_xstatebv(xbuf, xstate->mask); set_rand_data(xstate, xbuf); xrstor(xbuf, xstate->mask); } static inline void load_init_xstate(struct xstate_info *xstate, struct xsave_buffer *xbuf) { clear_xstate_header(xbuf); xrstor(xbuf, xstate->mask); } static inline void copy_xstate(struct xsave_buffer *xbuf_dst, struct xsave_buffer *xbuf_src) { memcpy(&xbuf_dst->bytes[xstate.xbuf_offset], &xbuf_src->bytes[xstate.xbuf_offset], xstate.size); } static inline bool validate_xstate_same(struct xsave_buffer *xbuf1, struct xsave_buffer *xbuf2) { int ret; ret = memcmp(&xbuf1->bytes[xstate.xbuf_offset], &xbuf2->bytes[xstate.xbuf_offset], xstate.size); return ret == 0; } static inline bool validate_xregs_same(struct xsave_buffer *xbuf1) { struct xsave_buffer *xbuf2; bool ret; xbuf2 = alloc_xbuf(); if (!xbuf2) ksft_exit_fail_msg("failed to allocate XSAVE buffer\n"); xsave(xbuf2, xstate.mask); ret = validate_xstate_same(xbuf1, xbuf2); free(xbuf2); return ret; } /* Context switching test */ static void *check_xstate(void *info) { struct futex_info *finfo = (struct futex_info *)info; struct xsave_buffer *xbuf; int i; xbuf = alloc_xbuf(); if (!xbuf) ksft_exit_fail_msg("unable to allocate XSAVE buffer\n"); /* * Load random data into 'xbuf' and then restore it to the xstate * registers. */ load_rand_xstate(&xstate, xbuf); finfo->valid = true; for (i = 0; i < finfo->iterations; i++) { pthread_mutex_lock(&finfo->mutex); /* * Ensure the register values have not diverged from the * record. Then reload a new random value. If it failed * ever before, skip it. */ if (finfo->valid) { finfo->valid = validate_xregs_same(xbuf); load_rand_xstate(&xstate, xbuf); } /* * The last thread's last unlock will be for thread 0's * mutex. However, thread 0 will have already exited the * loop and the mutex will already be unlocked. * * Because this is not an ERRORCHECK mutex, that * inconsistency will be silently ignored. */ pthread_mutex_unlock(&finfo->next->mutex); } free(xbuf); return finfo; } static void create_threads(uint32_t num_threads, uint32_t iterations, struct futex_info *finfo) { int i; for (i = 0; i < num_threads; i++) { int next_nr; finfo[i].nr = i; finfo[i].iterations = iterations; /* * Thread 'i' will wait on this mutex to be unlocked. * Lock it immediately after initialization: */ pthread_mutex_init(&finfo[i].mutex, NULL); pthread_mutex_lock(&finfo[i].mutex); next_nr = (i + 1) % num_threads; finfo[i].next = &finfo[next_nr]; if (pthread_create(&finfo[i].thread, NULL, check_xstate, &finfo[i])) ksft_exit_fail_msg("pthread_create() failed\n"); } } static bool checkout_threads(uint32_t num_threads, struct futex_info *finfo) { void *thread_retval; bool valid = true; int err, i; for (i = 0; i < num_threads; i++) { err = pthread_join(finfo[i].thread, &thread_retval); if (err) ksft_exit_fail_msg("pthread_join() failed for thread %d err: %d\n", i, err); if (thread_retval != &finfo[i]) { ksft_exit_fail_msg("unexpected thread retval for thread %d: %p\n", i, thread_retval); } valid &= finfo[i].valid; } return valid; } static void affinitize_cpu0(void) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(0, &cpuset); if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) ksft_exit_fail_msg("sched_setaffinity to CPU 0 failed\n"); } static void test_context_switch(uint32_t num_threads, uint32_t iterations) { struct futex_info *finfo; /* Affinitize to one CPU to force context switches */ affinitize_cpu0(); printf("[RUN]\t%s: check context switches, %d iterations, %d threads.\n", xstate.name, iterations, num_threads); finfo = malloc(sizeof(*finfo) * num_threads); if (!finfo) ksft_exit_fail_msg("unable allocate memory\n"); create_threads(num_threads, iterations, finfo); /* * This thread wakes up thread 0 * Thread 0 will wake up 1 * Thread 1 will wake up 2 * ... * The last thread will wake up 0 * * This will repeat for the configured * number of iterations. */ pthread_mutex_unlock(&finfo[0].mutex); /* Wait for all the threads to finish: */ if (checkout_threads(num_threads, finfo)) printf("[OK]\tNo incorrect case was found.\n"); else printf("[FAIL]\tFailed with context switching test.\n"); free(finfo); } /* * Ptrace test for the ABI format as described in arch/x86/include/asm/user.h */ /* * Make sure the ptracee has the expanded kernel buffer on the first use. * Then, initialize the state before performing the state injection from * the ptracer. For non-dynamic states, this is benign. */ static inline void ptracee_touch_xstate(void) { struct xsave_buffer *xbuf; xbuf = alloc_xbuf(); load_rand_xstate(&xstate, xbuf); load_init_xstate(&xstate, xbuf); free(xbuf); } /* * Ptracer injects the randomized xstate data. It also reads before and * after that, which will execute the kernel's state copy functions. */ static void ptracer_inject_xstate(pid_t target) { uint32_t xbuf_size = get_xbuf_size(); struct xsave_buffer *xbuf1, *xbuf2; struct iovec iov; /* * Allocate buffers to keep data while ptracer can write the * other buffer */ xbuf1 = alloc_xbuf(); xbuf2 = alloc_xbuf(); if (!xbuf1 || !xbuf2) ksft_exit_fail_msg("unable to allocate XSAVE buffer\n"); iov.iov_base = xbuf1; iov.iov_len = xbuf_size; if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov)) ksft_exit_fail_msg("PTRACE_GETREGSET failed\n"); printf("[RUN]\t%s: inject xstate via ptrace().\n", xstate.name); load_rand_xstate(&xstate, xbuf1); copy_xstate(xbuf2, xbuf1); if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov)) ksft_exit_fail_msg("PTRACE_SETREGSET failed\n"); if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov)) ksft_exit_fail_msg("PTRACE_GETREGSET failed\n"); if (*(uint64_t *)get_fpx_sw_bytes(xbuf1) == xgetbv(0)) printf("[OK]\t'xfeatures' in SW reserved area was correctly written\n"); else printf("[FAIL]\t'xfeatures' in SW reserved area was not correctly written\n"); if (validate_xstate_same(xbuf2, xbuf1)) printf("[OK]\txstate was correctly updated.\n"); else printf("[FAIL]\txstate was not correctly updated.\n"); free(xbuf1); free(xbuf2); } static void test_ptrace(void) { pid_t child; int status; child = fork(); if (child < 0) { ksft_exit_fail_msg("fork() failed\n"); } else if (!child) { if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) ksft_exit_fail_msg("PTRACE_TRACEME failed\n"); ptracee_touch_xstate(); raise(SIGTRAP); _exit(0); } do { wait(&status); } while (WSTOPSIG(status) != SIGTRAP); ptracer_inject_xstate(child); ptrace(PTRACE_DETACH, child, NULL, NULL); wait(&status); if (!WIFEXITED(status) || WEXITSTATUS(status)) ksft_exit_fail_msg("ptracee exit error\n"); } /* * Test signal delivery for the ABI compatibility. * See the ABI format: arch/x86/include/uapi/asm/sigcontext.h */ /* * Avoid using printf() in signal handlers as it is not * async-signal-safe. */ #define SIGNAL_BUF_LEN 1000 static char signal_message_buffer[SIGNAL_BUF_LEN]; static void sig_print(char *msg) { int left = SIGNAL_BUF_LEN - strlen(signal_message_buffer) - 1; strncat(signal_message_buffer, msg, left); } static struct xsave_buffer *stashed_xbuf; static void validate_sigfpstate(int sig, siginfo_t *si, void *ctx_void) { ucontext_t *ctx = (ucontext_t *)ctx_void; void *xbuf = ctx->uc_mcontext.fpregs; struct _fpx_sw_bytes *sw_bytes; uint32_t magic2; /* Reset the signal message buffer: */ signal_message_buffer[0] = '\0'; sw_bytes = get_fpx_sw_bytes(xbuf); if (sw_bytes->magic1 == FP_XSTATE_MAGIC1) sig_print("[OK]\t'magic1' is valid\n"); else sig_print("[FAIL]\t'magic1' is not valid\n"); if (get_fpx_sw_bytes_features(xbuf) & xstate.mask) sig_print("[OK]\t'xfeatures' in SW reserved area is valid\n"); else sig_print("[FAIL]\t'xfeatures' in SW reserved area is not valid\n"); if (get_xstatebv(xbuf) & xstate.mask) sig_print("[OK]\t'xfeatures' in XSAVE header is valid\n"); else sig_print("[FAIL]\t'xfeatures' in XSAVE header is not valid\n"); if (validate_xstate_same(stashed_xbuf, xbuf)) sig_print("[OK]\txstate delivery was successful\n"); else sig_print("[FAIL]\txstate delivery was not successful\n"); magic2 = *(uint32_t *)(xbuf + sw_bytes->xstate_size); if (magic2 == FP_XSTATE_MAGIC2) sig_print("[OK]\t'magic2' is valid\n"); else sig_print("[FAIL]\t'magic2' is not valid\n"); set_rand_data(&xstate, xbuf); copy_xstate(stashed_xbuf, xbuf); } static void test_signal(void) { bool valid_xstate; /* * The signal handler will access this to verify xstate context * preservation. */ stashed_xbuf = alloc_xbuf(); if (!stashed_xbuf) ksft_exit_fail_msg("unable to allocate XSAVE buffer\n"); printf("[RUN]\t%s: load xstate and raise SIGUSR1\n", xstate.name); sethandler(SIGUSR1, validate_sigfpstate, 0); load_rand_xstate(&xstate, stashed_xbuf); raise(SIGUSR1); /* * Immediately record the test result, deferring printf() to * prevent unintended state contamination by that. */ valid_xstate = validate_xregs_same(stashed_xbuf); printf("%s", signal_message_buffer); printf("[RUN]\t%s: load new xstate from sighandler and check it after sigreturn\n", xstate.name); if (valid_xstate) printf("[OK]\txstate was restored correctly\n"); else printf("[FAIL]\txstate restoration failed\n"); clearhandler(SIGUSR1); free(stashed_xbuf); } void test_xstate(uint32_t feature_num) { const unsigned int ctxtsw_num_threads = 5, ctxtsw_iterations = 10; unsigned long features; long rc; if (!(XFEATURE_MASK_TEST_SUPPORTED & (1 << feature_num))) { ksft_print_msg("The xstate test does not fully support the component %u, yet.\n", feature_num); return; } rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &features); if (rc || !(features & (1 << feature_num))) { ksft_print_msg("The kernel does not support feature number: %u\n", feature_num); return; } xstate = get_xstate_info(feature_num); if (!xstate.size || !xstate.xbuf_offset) { ksft_exit_fail_msg("invalid state size/offset (%d/%d)\n", xstate.size, xstate.xbuf_offset); } test_context_switch(ctxtsw_num_threads, ctxtsw_iterations); test_ptrace(); test_signal(); }