Re: [PATCH 06/10] selftests/x86: Add a test for signal frame FPU portability
Alexander Mikhalitsyn <[email protected]> Fri, 26 Jun 2026 16:39:10 +0200
| Newsgroups | dev.linux.lists.criu,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CAJqdLronSB4rWmxaaeEFgU4EaGu8HzoxW1UOp6bX31axuijHdw@mail.gmail.com> |
Am Mo., 15. Juni 2026 um 21:40 Uhr schrieb Andrei Vagin <[email protected]>: > > Add a new selftest tools/testing/selftests/x86/sigframe_fpu_portability.c > that verifies that the kernel correctly restores the xstate context even > if the frame size has been manually reduced, as long as the > FP_XSTATE_MAGIC2 marker is correctly placed at the end of the specified > xstate_size. > > This test simulates a scenario where a signal frame is created on a > system with fewer xstate features and restored on a system with more > features. > > Signed-off-by: Andrei Vagin <[email protected]> Reviewed-by: Alexander Mikhalitsyn <[email protected]> > --- > tools/testing/selftests/x86/Makefile | 5 +- > .../selftests/x86/sigframe_fpu_portability.c | 162 ++++++++++++++++++ > tools/testing/selftests/x86/xstate.c | 5 - > tools/testing/selftests/x86/xstate.h | 12 ++ > 4 files changed, 178 insertions(+), 6 deletions(-) > create mode 100644 tools/testing/selftests/x86/sigframe_fpu_portability.c > > diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile > index 434065215d12..72071deda978 100644 > --- a/tools/testing/selftests/x86/Makefile > +++ b/tools/testing/selftests/x86/Makefile > @@ -19,7 +19,8 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \ > test_FCMOV test_FCOMI test_FISTTP \ > vdso_restorer > TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \ > - corrupt_xstate_header amx lam test_shadow_stack avx apx > + corrupt_xstate_header amx lam test_shadow_stack avx apx \ > + sigframe_fpu_portability > # Some selftests require 32bit support enabled also on 64bit systems > TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall > > @@ -138,3 +139,5 @@ $(OUTPUT)/avx_64: CFLAGS += -mno-avx -mno-avx512f > $(OUTPUT)/amx_64: EXTRA_FILES += xstate.c > $(OUTPUT)/avx_64: EXTRA_FILES += xstate.c > $(OUTPUT)/apx_64: EXTRA_FILES += xstate.c > + > +$(OUTPUT)/sigframe_fpu_portability_64: CFLAGS += -mno-avx -mno-avx512f > diff --git a/tools/testing/selftests/x86/sigframe_fpu_portability.c b/tools/testing/selftests/x86/sigframe_fpu_portability.c > new file mode 100644 > index 000000000000..169548892f92 > --- /dev/null > +++ b/tools/testing/selftests/x86/sigframe_fpu_portability.c > @@ -0,0 +1,162 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +#define _GNU_SOURCE > +#include <stdio.h> > +#include <signal.h> > +#include <string.h> > +#include <sys/ucontext.h> > +#include <stdlib.h> > +#include <stdint.h> > +#include <stdbool.h> > +#include <cpuid.h> > +#include <unistd.h> > +#include <sys/syscall.h> > +#include <asm/prctl.h> > +#include <stddef.h> > + > +#include "helpers.h" > +#include "xstate.h" > + > +/* > + * This test verifies the FPU portability of the signal frame. > + * It verifies that the kernel correctly restores the xstate context even > + * if the frame size has been manually reduced (shrunk), as long as the > + * FP_XSTATE_MAGIC2 marker is correctly placed. > + */ > + > +#define SIGFRAME_XSTATE_HDR_OFFSET 512 > + > +#define XSTATE_SSE_ONLY_SIZE (SIGFRAME_XSTATE_HDR_OFFSET + XSAVE_HDR_SIZE) > +#define XFEATURE_MASK_FPSSE ((1 << XFEATURE_FP) | (1 << XFEATURE_SSE)) > + > +static uint32_t ymm_offset; > +static uint32_t xstate_size_ymm; > + > +/* > + * Avoid using printf() in signal handlers as it is not > + * async-signal-safe. > + */ > +#define SIGNAL_BUF_LEN 1024 > +static char sig_err_buf[SIGNAL_BUF_LEN]; > + > +static void sig_print(const char *msg) > +{ > + int left = SIGNAL_BUF_LEN - strlen(sig_err_buf) - 1; > + > + strncat(sig_err_buf, msg, left); > +} > + > +static void check_avx_support(void) > +{ > + struct xstate_info xstate; > + unsigned long features; > + long rc; > + > + /* > + * Check if the kernel supports AVX (XFEATURE_YMM). > + * This also confirms that the OS has enabled XSAVE. > + */ > + rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &features); > + if (rc != 0) > + ksft_exit_skip("ARCH_GET_XCOMP_SUPP not supported\n"); > + > + if (!(features & (1 << XFEATURE_YMM))) > + ksft_exit_skip("AVX not supported by kernel/hardware\n"); > + > + xstate = get_xstate_info(XFEATURE_YMM); > + if (!xstate.size) > + ksft_exit_skip("AVX not supported by hardware\n"); > + > + ymm_offset = xstate.xbuf_offset; > + xstate_size_ymm = xstate.xbuf_offset + xstate.size; > +} > + > +#define TEST_YMMH_VAL (0x5656565656565656UL) > + > +__attribute__((target("avx"))) > +static void read_ymm0(uint64_t *v) > +{ > + asm volatile ("vmovdqu %%ymm0, %0" : "=m" (*(char (*)[32])v)); > +} > + > +__attribute__((target("avx"))) > +static void write_ymm0(uint64_t *v) > +{ > + asm volatile ("vmovdqu %0, %%ymm0" : : "m" (*(char (*)[32])v)); > +} > + > +static void handle_shrunk_xstate_size(int sig, siginfo_t *si, void *ucp) > +{ > + ucontext_t *uc = ucp; > + void *fp = uc->uc_mcontext.fpregs; > + struct _fpx_sw_bytes *sw = get_fpx_sw_bytes(fp); > + struct xsave_buffer *xbuf; > + uint64_t xfeatures, *ymmh_p; > + > + if (sw->magic1 != FP_XSTATE_MAGIC1) { > + sig_print("magic1 is not valid\n"); > + return; > + } > + > + xbuf = (struct xsave_buffer *)fp; > + > + /* Shrink the frame to just YMM size */ > + sw->xstate_size = xstate_size_ymm; > + > + xfeatures = get_xstatebv(xbuf); > + xfeatures &= XFEATURE_MASK_FPSSE | (1 << XFEATURE_YMM); > + set_xstatebv(xbuf, xfeatures); > + /* Also update sw->xfeatures as the kernel relies on it */ > + set_fpx_sw_bytes_features(fp, xfeatures); > + > + *(uint32_t *)(fp + sw->xstate_size) = FP_XSTATE_MAGIC2; > + > + ymmh_p = (uint64_t *)(fp + ymm_offset); > + ymmh_p[0] = TEST_YMMH_VAL; > + ymmh_p[1] = TEST_YMMH_VAL+1; > + > + /* clear everything after MAGIC2. */ > + if (sw->xstate_size + 4 < sw->extended_size) > + memset(fp + sw->xstate_size + 4, 0, sw->extended_size - sw->xstate_size - 4); > +} > + > +static void test_shrunk_xstate_size(void) > +{ > + uint64_t v[4] = {0, 0, 0, 0}; > + > + sig_err_buf[0] = 0; > + sethandler(SIGUSR1, handle_shrunk_xstate_size, 0); > + > + v[0] = 0x1111111111111111ULL; > + v[1] = 0x2222222222222222ULL; > + v[2] = 0x3333333333333333ULL; > + v[3] = 0x4444444444444444ULL; > + write_ymm0(v); > + > + raise(SIGUSR1); > + v[0] = v[1] = v[2] = v[3] = 0; > + read_ymm0(v); > + > + if (sig_err_buf[0]) > + ksft_test_result_fail("%s\n", sig_err_buf); > + else if (v[2] == TEST_YMMH_VAL && v[3] == (TEST_YMMH_VAL + 1)) > + ksft_test_result_pass("YMM state restored correctly from shrunk frame\n"); > + else > + ksft_test_result_fail( > + "Got upper bits: 0x%lx 0x%lx (expected %lx %lx)\n", > + v[2], v[3], TEST_YMMH_VAL, TEST_YMMH_VAL + 1); > + > + clearhandler(SIGUSR1); > +} > + > + > +int main(void) > +{ > + ksft_print_header(); > + ksft_set_plan(1); > + > + check_avx_support(); > + > + test_shrunk_xstate_size(); > + ksft_finished(); > + return 0; > +} > diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c > index 97fe4bd8bc77..40062b28c001 100644 > --- a/tools/testing/selftests/x86/xstate.c > +++ b/tools/testing/selftests/x86/xstate.c > @@ -42,11 +42,6 @@ static inline uint64_t xgetbv(uint32_t 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 { > diff --git a/tools/testing/selftests/x86/xstate.h b/tools/testing/selftests/x86/xstate.h > index 6ee816e7625a..c531667b66ad 100644 > --- a/tools/testing/selftests/x86/xstate.h > +++ b/tools/testing/selftests/x86/xstate.h > @@ -3,6 +3,8 @@ > #define __SELFTESTS_X86_XSTATE_H > > #include <stdint.h> > +#include <stdlib.h> > +#include <string.h> > > #include "kselftest.h" > > @@ -160,6 +162,11 @@ static inline void set_xstatebv(struct xsave_buffer *xbuf, uint64_t bv) > *(uint64_t *)(&xbuf->header) = bv; > } > > +static inline uint64_t get_xstatebv(struct xsave_buffer *xbuf) > +{ > + return *(uint64_t *)(&xbuf->header); > +} > + > /* See 'struct _fpx_sw_bytes' at sigcontext.h */ > #define SW_BYTES_OFFSET 464 > /* N.B. The struct's field name varies so read from the offset. */ > @@ -175,6 +182,11 @@ static inline uint64_t get_fpx_sw_bytes_features(void *buffer) > return *(uint64_t *)(buffer + SW_BYTES_BV_OFFSET); > } > > +static inline void set_fpx_sw_bytes_features(void *buffer, uint64_t features) > +{ > + *(uint64_t *)(buffer + SW_BYTES_BV_OFFSET) = features; > +} > + > static inline void set_rand_data(struct xstate_info *xstate, struct xsave_buffer *xbuf) > { > int *ptr = (int *)&xbuf->bytes[xstate->xbuf_offset]; > -- > 2.54.0.1189.g8c84645362-goog > >