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
>
>