Skip to contentSkip to conversation

x86/fpu: Restore and reinforce signal frame portability

PATCH Applied Andrei Vagin 3 participants 9 messages May 26 – Jun 3

Subsystems (4)

v1 of 8 Applied Latest is v8 →

lore
Share & tools
lore
#0 lore raw

cover x86/fpu: Restore and reinforce signal frame portability

The x86 signal frame is designed to be self-describing. The xstate_size field in the software-reserved bytes indicates the actual size of the xstate context and is used by the kernel to locate the FP_XSTATE_MAGIC2 marker during signal return.

This design is required to provide portability of signal frames across different machines. For example, a process checkpointed on a system with fewer xstate features and restored on a system with more features will have a signal frame on its stack that is smaller than the destination host's default. By relying on the frame's internal xstate_size, the kernel can correctly validate and restore such frames.

This series addresses a regression introduced in commit dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return").

Cc Thomas Gleixner <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Borislav Petkov <[email protected]>
Cc Dave Hansen <[email protected]>
Cc "H. Peter Anvin" <[email protected]>
Cc "Chang S. Bae" <[email protected]>

Andrei Vagin (5):
Revert "x86/fpu: Refine and simplify the magic number check during
signal return"
x86/fpu: Document signal frame portability selftests/x86: Add a test for signal frame portability x86/fpu: Add consistency check between xstate_size and xfeatures selftests/x86: Add a consistency test for signal frames

 Documentation/arch/x86/xstate.rst             |  14 +-
 arch/x86/include/uapi/asm/sigcontext.h        |  13 +-
 arch/x86/kernel/fpu/signal.c                  |  36 +++-
 arch/x86/kernel/fpu/xstate.c                  |   2 +-
 arch/x86/kernel/fpu/xstate.h                  |   2 +
 tools/testing/selftests/x86/Makefile          |   5 +-
 .../selftests/x86/sigframe_portability.c      | 189 ++++++++++++++++++
 tools/testing/selftests/x86/xstate.c          |   5 -
 tools/testing/selftests/x86/xstate.h          |  12 ++
 9 files changed, 260 insertions(+), 18 deletions(-)
 create mode 100644 tools/testing/selftests/x86/sigframe_portability.c
Signature · 1 line
2.54.0.746.g67dd491aae-goog
#1 lore raw

PATCH 1/5 Revert "x86/fpu: Refine and simplify the magic number check during signal return"

This reverts commit dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return").

The reverted commit broke applications that construct signal frames in userspace (such as CRIU and gVisor) if the frame's xstate size is smaller than the kernel's fpstate->user_size.

Furthermore, this introduces a critical issue for checkpoint/restore tools like CRIU. If a process is checkpointed while inside a signal handler, its stack contains a signal frame formatted according to the source host's xstate capabilities. If that process is later restored on a destination host with larger xstate capabilities (e.g., a newer CPU with more features enabled, resulting in a larger fpstate->user_size), the kernel will look for FP_XSTATE_MAGIC2 at the destination host's larger user_size offset instead of the offset encoded in the frame's fx_sw->xstate_size. This causes the magic2 check to fail, forcing sigreturn to silently fall back to "FX-only" mode. Upon return from the signal handler, the process's extended state is reset to initial values instead of being restored, leading to silent data corruption.

The original commit cited commit d877550eaf2d ("x86/fpu: Stop relying on userspace for info to fault in xsave buffer") as justification to stop relying on userspace for the magic number check. However, these two changes are fundamentally different. The last one only changed how much memory the kernel ensures is paged-in before running XRSTOR to prevent an infinite loop. It did not change the signal frame format or how the layout is validated.

Reverting this change restores the use of fx_sw->xstate_size for locating magic2 and restores the necessary sanity checks, ensuring that the signal frame remains self-describing and portable.

Fixes dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return")
---
 arch/x86/kernel/fpu/signal.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
arch/x86/kernel/fpu/signal.c +8 -3
index c3ec2512f2bb..20b638c507ca 100644
@@ -27,14 +27,19 @@
 static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 					    struct _fpx_sw_bytes *fx_sw)
 {
+	int min_xstate_size = sizeof(struct fxregs_state) +
+			      sizeof(struct xstate_header);
 	void __user *fpstate = fxbuf;
 	unsigned int magic2;
 
 	if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw)))
 		return false;
 
-	/* Check for the first magic field */
-	if (fx_sw->magic1 != FP_XSTATE_MAGIC1)
+	/* Check for the first magic field and other error scenarios. */
+	if (fx_sw->magic1 != FP_XSTATE_MAGIC1 ||
+	    fx_sw->xstate_size < min_xstate_size ||
+	    fx_sw->xstate_size > x86_task_fpu(current)->fpstate->user_size ||
+	    fx_sw->xstate_size > fx_sw->extended_size)
 		goto setfx;
 
 	/*
@@ -43,7 +48,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 	 * fpstate layout with out copying the extended state information
 	 * in the memory layout.
 	 */
-	if (__get_user(magic2, (__u32 __user *)(fpstate + x86_task_fpu(current)->fpstate->user_size)))
+	if (__get_user(magic2, (__u32 __user *)(fpstate + fx_sw->xstate_size)))
 		return false;
 
 	if (likely(magic2 == FP_XSTATE_MAGIC2))
Signature · 1 line
2.54.0.746.g67dd491aae-goog
maintainer
#2 lore raw

Re: [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return"

Quoted 6 lines

This reverts commit dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return").

The reverted commit broke applications that construct signal frames in userspace (such as CRIU and gVisor) if the frame's xstate size is smaller than the kernel's fpstate->user_size.

Holding this off on the stable side until the revert (and the rest of the series) lands in mainline. Once it's upstream, please ping with the mainline SHAs and the list of trees you want it on, and I'll queue it.

--
Thanks,
Sasha

#3 lore raw

Re: [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return"

Quoted 11 lines · Sasha Levin
Quoted 7 lines

This reverts commit dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return").

The reverted commit broke applications that construct signal frames in userspace (such as CRIU and gVisor) if the frame's xstate size is smaller than the kernel's fpstate->user_size.

Holding this off on the stable side until the revert (and the rest of the series) lands in mainline. Once it's upstream, please ping with the mainline SHAs and the list of trees you want it on, and I'll queue it.

Will do. Thanks.

Thomas and Ingo, this revert is critical for CRIU. We've received reports of silent memory corruption caused by the original change. Could we please get this merged with high priority? This patch has been pending on the mailing list for a month now; it is identical to the one sent here:
https://lore.kernel.org/all/[email protected]/

Thanks,
Andrei

maintainer Applied
#4 lore raw

Re: [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return"

In reply to Andrei Vagin

Queued for 7.0.y and 6.18.y, thanks.

Signature · 2 lines
Thanks,
Sasha
#5 lore raw

PATCH 2/5 x86/fpu: Document signal frame portability

In reply to Andrei Vagin

The x86 signal frame is designed to be self-describing, with the 'xstate_size' field in the software-reserved bytes indicating the actual size of the context. This design is required for portability, allowing a signal frame created on a system with a specific set of xstate features to be restored on a machine with a different (larger) set of features.

Document this contract in the uabi headers and Documentation/. This requirement is critical for checkpoint/restore tools like CRIU, which should be able to migrate processes across machines with heterogeneous FPU capabilities. Note that portability is generally limited to CPUs from the same vendor (e.g., Intel vs. AMD) due to potential differences in xstate layouts.

---
 Documentation/arch/x86/xstate.rst      | 14 ++++++++++++--
 arch/x86/include/uapi/asm/sigcontext.h | 13 +++++++++++--
 2 files changed, 23 insertions(+), 4 deletions(-)
Documentation/arch/x86/xstate.rst +12 -2
index cec05ac464c1..e27e779bae44 100644
@@ -170,5 +170,15 @@ are extended to control the guest permission:
  is going to be rejected. So, the permission has to be requested before the
  first VCPU creation.
 
-Note that some VMMs may have already established a set of supported state
-components. These options are not presumed to support any particular VMM.
+Signal Frame Portability
+------------------------
+
+The signal frame is designed to be self-describing and portable. This is
+especially important for checkpoint/restore tools like CRIU, which may restore
+a process on a different host than where it was checkpointed. A signal frame
+created on a machine with fewer CPU features can be successfully restored on a
+machine with more CPU features.
+
+Note that signal frame portability is generally guaranteed only between CPUs
+from the same vendor. Different vendors may use different offsets for the same
+xstate features in the xsave area, making frames incompatible between them.
arch/x86/include/uapi/asm/sigcontext.h +11 -2
index d0d9b331d3a1..f3d10f804f42 100644
@@ -31,8 +31,17 @@
  * If sw_reserved.magic1 == FP_XSTATE_MAGIC1 then there's a
  * sw_reserved.extended_size bytes large extended context area present. (The
  * last 32-bit word of this extended area (at the
- * fpstate+extended_size-FP_XSTATE_MAGIC2_SIZE address) is set to
- * FP_XSTATE_MAGIC2 so that you can sanity check your size calculations.)
+ * fpstate+xstate_size address) is set to FP_XSTATE_MAGIC2 so that you
+ * can sanity check your size calculations.)
+ *
+ * The xstate_size field indicates the actual size of the xstate context
+ * (including the fxregs_state and xstate_header). This size is used to
+ * locate FP_XSTATE_MAGIC2. This makes the signal frame self-describing
+ * and portable: a signal frame created on a machine with a certain set
+ * of xstate features can be restored on a machine with a different (larger)
+ * set of features, as long as the latter supports all features present in
+ * the frame. Note that this portability is generally limited to CPUs of the
+ * same vendor, as different vendors may use different xstate layouts.
  *
  * This extended area typically grows with newer CPUs that have larger and
  * larger XSAVE areas.
Signature · 1 line
2.54.0.746.g67dd491aae-goog
#6 lore raw

PATCH 3/5 selftests/x86: Add a test for signal frame portability

In reply to Andrei Vagin

Add a new selftest tools/testing/selftests/x86/sigframe_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.

---
 tools/testing/selftests/x86/Makefile          |   5 +-
 .../selftests/x86/sigframe_portability.c      | 143 ++++++++++++++++++
 tools/testing/selftests/x86/xstate.c          |   5 -
 tools/testing/selftests/x86/xstate.h          |   7 +
 4 files changed, 154 insertions(+), 6 deletions(-)
 create mode 100644 tools/testing/selftests/x86/sigframe_portability.c
tools/testing/selftests/x86/Makefile +4 -1
index 434065215d12..bd59ee3df61d 100644
@@ -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_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_portability_64: CFLAGS += -mno-avx -mno-avx512f
tools/testing/selftests/x86/sigframe_portability.c new file +143 -0

143 lines not on this page · raw message

tools/testing/selftests/x86/xstate.c +0 -5
index 97fe4bd8bc77..40062b28c001 100644
@@ -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 {
tools/testing/selftests/x86/xstate.h +7 -0
index 6ee816e7625a..b24501b621f3 100644
@@ -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. */
Signature · 1 line
2.54.0.746.g67dd491aae-goog
#7 lore raw

PATCH 4/5 x86/fpu: Add consistency check between xstate_size and xfeatures

In reply to Andrei Vagin

The signal frame is designed to be self-describing, where xstate_size indicates the actual size of the xstate context. The kernel previously lacked a check to ensure that the provided xstate_size was sufficient for the features enabled in the xfeatures mask. Additionally, restore_fpregs_from_user() always used the default xstate_size to fault in the xstate user buffer.

These consistency checks have been added:
* Validate that xfeatures is a subset of the features enabled for the task.
* Calculate the required size for the validated xfeatures mask.
* Ensure the provided xstate_size is sufficient.

These checks prevent the kernel from attempting to fault in memory past the end of a frame.

---
 arch/x86/kernel/fpu/signal.c | 26 ++++++++++++++++++++------
 arch/x86/kernel/fpu/xstate.c |  2 +-
 arch/x86/kernel/fpu/xstate.h |  2 ++
 3 files changed, 23 insertions(+), 7 deletions(-)
arch/x86/kernel/fpu/signal.c +20 -6
index 20b638c507ca..5baba4101f1d 100644
@@ -29,7 +29,8 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 {
 	int min_xstate_size = sizeof(struct fxregs_state) +
 			      sizeof(struct xstate_header);
-	void __user *fpstate = fxbuf;
+	struct fpstate *fpstate = x86_task_fpu(current)->fpstate;
+	void __user *sig_fpstate = fxbuf;
 	unsigned int magic2;
 
 	if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw)))
@@ -37,8 +38,9 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 
 	/* Check for the first magic field and other error scenarios. */
 	if (fx_sw->magic1 != FP_XSTATE_MAGIC1 ||
+	    (fx_sw->xfeatures & ~fpstate->user_xfeatures) ||
 	    fx_sw->xstate_size < min_xstate_size ||
-	    fx_sw->xstate_size > x86_task_fpu(current)->fpstate->user_size ||
+	    fx_sw->xstate_size > fpstate->user_size ||
 	    fx_sw->xstate_size > fx_sw->extended_size)
 		goto setfx;
 
@@ -48,9 +50,17 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 	 * fpstate layout with out copying the extended state information
 	 * in the memory layout.
 	 */
-	if (__get_user(magic2, (__u32 __user *)(fpstate + fx_sw->xstate_size)))
+	if (__get_user(magic2, (__u32 __user *)(sig_fpstate + fx_sw->xstate_size)))
 		return false;
 
+	if (fx_sw->xstate_size != fpstate->user_size) {
+		unsigned int calculated_size;
+
+		calculated_size = xstate_calculate_size(fx_sw->xfeatures, false);
+		if (fx_sw->xstate_size < calculated_size)
+			goto setfx;
+	}
+
 	if (likely(magic2 == FP_XSTATE_MAGIC2))
 		return true;
 setfx:
@@ -266,7 +276,8 @@ static int __restore_fpregs_from_user(void __user *buf, u64 ufeatures,
  * Attempt to restore the FPU registers directly from user memory.
  * Pagefaults are handled and any errors returned are fatal.
  */
-static bool restore_fpregs_from_user(void __user *buf, u64 xrestore, bool fx_only)
+static bool restore_fpregs_from_user(void __user *buf, u64 xrestore,
+				     bool fx_only, size_t xstate_size)
 {
 	struct fpu *fpu = x86_task_fpu(current);
 	int ret;
@@ -302,7 +313,7 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore, bool fx_onl
 		if (ret != X86_TRAP_PF)
 			return false;
 
-		if (!fault_in_readable(buf, fpu->fpstate->user_size))
+		if (!fault_in_readable(buf, xstate_size))
 			goto retry;
 		return false;
 	}
@@ -333,6 +344,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
 	bool success, fx_only = false;
 	union fpregs_state *fpregs;
 	u64 user_xfeatures = 0;
+	size_t xstate_size;
 
 	if (use_xsave()) {
 		struct _fpx_sw_bytes fx_sw_user;
@@ -342,13 +354,15 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
 
 		fx_only = !fx_sw_user.magic1;
 		user_xfeatures = fx_sw_user.xfeatures;
+		xstate_size = fx_sw_user.xstate_size;
 	} else {
 		user_xfeatures = XFEATURE_MASK_FPSSE;
+		xstate_size = sizeof(struct fxregs_state);
 	}
 
 	if (likely(!ia32_fxstate)) {
 		/* Restore the FPU registers directly from user memory. */
-		return restore_fpregs_from_user(buf_fx, user_xfeatures, fx_only);
+		return restore_fpregs_from_user(buf_fx, user_xfeatures, fx_only, xstate_size);
 	}
 
 	/*
arch/x86/kernel/fpu/xstate.c +1 -1
index a7b6524a9dea..371a3a4a4b4e 100644
@@ -587,7 +587,7 @@ static bool __init check_xstate_against_struct(int nr)
 	return true;
 }
 
-static unsigned int xstate_calculate_size(u64 xfeatures, bool compacted)
+unsigned int xstate_calculate_size(u64 xfeatures, bool compacted)
 {
 	unsigned int topmost = fls64(xfeatures) -  1;
 	unsigned int offset, i;
arch/x86/kernel/fpu/xstate.h +2 -0
index 38a2862f09d3..c73cf2444de6 100644
@@ -55,6 +55,8 @@ extern int copy_sigframe_from_user_to_xstate(struct task_struct *tsk, const void
 extern void fpu__init_cpu_xstate(void);
 extern void fpu__init_system_xstate(unsigned int legacy_size);
 
+extern unsigned int xstate_calculate_size(u64 xfeatures, bool compacted);
+
 extern void __user *get_xsave_addr_user(struct xregs_state __user *xsave, int xfeature_nr);
 
 static inline u64 xfeatures_mask_supervisor(void)
Signature · 1 line
2.54.0.746.g67dd491aae-goog
#8 lore raw

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

In reply to Andrei Vagin

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.

---
 .../selftests/x86/sigframe_portability.c      | 106 +++++++++++++-----
 tools/testing/selftests/x86/xstate.h          |   5 +
 2 files changed, 81 insertions(+), 30 deletions(-)
tools/testing/selftests/x86/sigframe_portability.c +76 -30
index 8888079a153a..a60aa4d20dd1 100644
@@ -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;
tools/testing/selftests/x86/xstate.h +5 -0
index b24501b621f3..c531667b66ad 100644
@@ -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];
Signature · 1 line
2.54.0.746.g67dd491aae-goog