[PATCH 5/5] selftests/x86: Add a consistency test for signal frames

From: Andrei Vagin

Date: Tue May 26 2026 - 16:53:39 EST


Extend sigframe_portability test to include a consistency check. Verify
that the kernel correctly rejects signal frames where the xstate_size is
too small for the enabled features in the xfeatures mask.

Refactor the test to use separate helpers for portability and
consistency test cases, each with its own signal handler.

Signed-off-by: Andrei Vagin <avagin@xxxxxxxxxx>
---
.../selftests/x86/sigframe_portability.c | 106 +++++++++++++-----
tools/testing/selftests/x86/xstate.h | 5 +
2 files changed, 81 insertions(+), 30 deletions(-)

diff --git a/tools/testing/selftests/x86/sigframe_portability.c b/tools/testing/selftests/x86/sigframe_portability.c
index 8888079a153a..a60aa4d20dd1 100644
--- a/tools/testing/selftests/x86/sigframe_portability.c
+++ b/tools/testing/selftests/x86/sigframe_portability.c
@@ -17,13 +17,14 @@
#include "xstate.h"

/*
- * This test verifies the portability of the signal frame.
- * It simulates a scenario where a signal frame is created on a system with
- * fewer xstate features and restored on a system with more features.
+ * This test verifies the portability and consistency of the signal frame.
+ * Portability: A frame created on a system with fewer features can be
+ * restored on a system with more features.
+ * Consistency: The kernel rejects frames where xstate_size is insufficient
+ * for the features enabled in xfeatures.
*/

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

@@ -55,7 +56,17 @@ static void check_avx_support(void)

#define TEST_YMMH_VAL (0x5656565656565656UL)

-static void handle_signal(int sig, siginfo_t *si, void *ucp)
+static void read_ymm0(uint64_t *v)
+{
+ asm volatile ("vmovdqu %%ymm0, %0" : "=m" (*(char (*)[32])v));
+}
+
+static void write_ymm0(uint64_t *v)
+{
+ asm volatile ("vmovdqu %0, %%ymm0" : : "m" (*(char (*)[32])v));
+}
+
+static void handle_portability(int sig, siginfo_t *si, void *ucp)
{
ucontext_t *uc = ucp;
void *fp = uc->uc_mcontext.fpregs;
@@ -72,18 +83,14 @@ static void handle_signal(int sig, siginfo_t *si, void *ucp)

xbuf = (struct xsave_buffer *)fp;

- if (sw->xstate_size < xstate_size_ymm) {
- snprintf(sig_err_buf, SIGNAL_BUF_LEN,
- "xstate size is too small: %d (expected %d or greater)",
- sw->xstate_size, xstate_size_ymm);
- return;
- }
-
+ /* 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;

@@ -96,26 +103,12 @@ static void handle_signal(int sig, siginfo_t *si, void *ucp)
memset(fp + sw->xstate_size + 4, 0, sw->extended_size - sw->xstate_size - 4);
}

-static void read_ymm0(uint64_t *v)
-{
- asm volatile ("vmovdqu %%ymm0, %0" : "=m" (*(char (*)[32])v));
-}
-
-static void write_ymm0(uint64_t *v)
-{
- asm volatile ("vmovdqu %0, %%ymm0" : : "m" (*(char (*)[32])v));
-}
-
-int main(void)
+static void test_portability(void)
{
uint64_t v[4] = {0, 0, 0, 0};

- ksft_print_header();
- ksft_set_plan(1);
-
- check_avx_support();
-
- sethandler(SIGUSR1, handle_signal, 0);
+ sig_err_buf[0] = 0;
+ sethandler(SIGUSR1, handle_portability, 0);

v[0] = 0x1111111111111111ULL;
v[1] = 0x2222222222222222ULL;
@@ -130,13 +123,66 @@ int main(void)
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\n");
+ 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);
+}
+
+static void handle_consistency(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);
+
+ /* The origin frame contains an AVX state. */
+ sw->xstate_size = XSTATE_SSE_ONLY_SIZE;
+
+ *(uint32_t *)(fp + sw->xstate_size) = FP_XSTATE_MAGIC2;
+}
+
+static void test_consistency(void)
+{
+ uint64_t v[4] = {0, 0, 0, 0};
+
+ sig_err_buf[0] = 0;
+ sethandler(SIGUSR2, handle_consistency, 0);
+
+ v[0] = 0x1111111111111111ULL;
+ v[1] = 0x2222222222222222ULL;
+ v[2] = 0x3333333333333333ULL;
+ v[3] = 0x4444444444444444ULL;
+ write_ymm0(v);
+
+ raise(SIGUSR2);
+ v[0] = v[1] = v[2] = v[3] = 0;
+ read_ymm0(v);
+
+ /*
+ * When inconsistent, the kernel should have fallen back to
+ * FX-only mode, so YMM upper bits should be zero (init state).
+ */
+ if (v[2] == 0 && v[3] == 0)
+ ksft_test_result_pass("Inconsistent size correctly rejected\n");
+ else
+ ksft_test_result_fail("Inconsistent size was NOT rejected: 0x%lx 0x%lx\n",
+ v[2], v[3]);
+
+ clearhandler(SIGUSR2);
+}
+
+int main(void)
+{
+ ksft_print_header();
+ ksft_set_plan(2);
+
+ check_avx_support();
+
+ test_portability();
+ test_consistency();

ksft_finished();
return 0;
diff --git a/tools/testing/selftests/x86/xstate.h b/tools/testing/selftests/x86/xstate.h
index b24501b621f3..c531667b66ad 100644
--- a/tools/testing/selftests/x86/xstate.h
+++ b/tools/testing/selftests/x86/xstate.h
@@ -182,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.746.g67dd491aae-goog