The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability
@ 2026-06-15 19:37 Andrei Vagin
  2026-06-15 19:37 ` [PATCH 01/10] x86/fpu: Document " Andrei Vagin
                   ` (9 more replies)
  0 siblings, 10 replies; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

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 restores and improves signal frame portability. The goal is
to allow process migration across CPUs with heterogeneous FPU
capabilities, as long as the process only uses features supported by
both systems. This version addresses the original issues by pre-faulting
only the required size of the xstate buffer (rather than the default
task size), and includes cleanups requested by Ingo Molnar.

v3:
    - Include cleanups and refactoring of signal frame handling code
      as requested by Ingo Molnar.
    - Fix potential underflow in xstate_calculate_size()

v2:
    - Address sashiko comments.
    - 44eeff9bc467 ("Revert "x86/fpu: Refine and simplify the magic
      number check during signal return"") has been merged.

Cc: Thomas Gleixner <tglx@kernel.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: "Chang S. Bae" <chang.seok.bae@intel.com>

Andrei Vagin (10):
  x86/fpu: Document signal frame portability
  x86/fpu: Clean up and rename variables in signal frame handling
  x86/fpu: Split __fpu_restore_sig to extract compat path
  x86/fpu: Document reasoning of FX-only fallback
  x86/fpu: Fix potential underflow in xstate_calculate_size()
  selftests/x86: Add a test for signal frame FPU portability
  x86/fpu: Pre-fault only required size of xstate buffer
  selftests/x86: Add a sigframe insufficient xstate_size test
  x86/fpu: Allow restoring signal frames with larger xstate_size
  selftests/x86: Check restoring FPU state with larger xstate_size

 Documentation/arch/x86/xstate.rst             |  13 +
 arch/x86/include/uapi/asm/sigcontext.h        |  13 +
 arch/x86/kernel/fpu/signal.c                  | 141 +++++--
 arch/x86/kernel/fpu/xstate.c                  |   9 +-
 arch/x86/kernel/fpu/xstate.h                  |   2 +
 tools/testing/selftests/x86/Makefile          |   5 +-
 .../selftests/x86/sigframe_fpu_portability.c  | 345 ++++++++++++++++++
 tools/testing/selftests/x86/xstate.c          |   5 -
 tools/testing/selftests/x86/xstate.h          |  12 +
 9 files changed, 497 insertions(+), 48 deletions(-)
 create mode 100644 tools/testing/selftests/x86/sigframe_fpu_portability.c

-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 01/10] x86/fpu: Document signal frame portability
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 13:51   ` Alexander Mikhalitsyn
  2026-06-30 19:23   ` Chang S. Bae
  2026-06-15 19:37 ` [PATCH 02/10] x86/fpu: Clean up and rename variables in signal frame handling Andrei Vagin
                   ` (8 subsequent siblings)
  9 siblings, 2 replies; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

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.

Signed-off-by: Andrei Vagin <avagin@google.com>
---
 Documentation/arch/x86/xstate.rst      | 13 +++++++++++++
 arch/x86/include/uapi/asm/sigcontext.h | 13 +++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/Documentation/arch/x86/xstate.rst b/Documentation/arch/x86/xstate.rst
index cec05ac464c1..facd97b26bf1 100644
--- a/Documentation/arch/x86/xstate.rst
+++ b/Documentation/arch/x86/xstate.rst
@@ -172,3 +172,16 @@ are extended to control the guest permission:
 
 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.
diff --git a/arch/x86/include/uapi/asm/sigcontext.h b/arch/x86/include/uapi/asm/sigcontext.h
index d0d9b331d3a1..d52313345f21 100644
--- a/arch/x86/include/uapi/asm/sigcontext.h
+++ b/arch/x86/include/uapi/asm/sigcontext.h
@@ -34,6 +34,19 @@
  * fpstate+extended_size-FP_XSTATE_MAGIC2_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 in
+ * conjunction with the pointer to the xstate context to locate
+ * FP_XSTATE_MAGIC2. Note that on 32-bit systems, the fpstate pointer points
+ * to a legacy struct fregs_state (112 bytes) that precedes the xstate
+ * context, so the xstate context starts at fpstate + 112. 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.
  */
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 02/10] x86/fpu: Clean up and rename variables in signal frame handling
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
  2026-06-15 19:37 ` [PATCH 01/10] x86/fpu: Document " Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 17:05   ` Alexander Mikhalitsyn
  2026-06-15 19:37 ` [PATCH 03/10] x86/fpu: Split __fpu_restore_sig to extract compat path Andrei Vagin
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin, Ingo Molnar

Clean up signal frame handling code by renaming several variables for
clarity and consistency, and moving masking logic closer to its usage.

- Rename 'fxbuf' to 'buf_fx' in check_xstate_in_sigframe() for consistency.
- Rename label 'setfx' to 'err_setfx' in check_xstate_in_sigframe() to
  indicate it is an error path.
- In __restore_fpregs_from_user(), rename 'ufeatures' to 'task_xfeatures'
  and 'xrestore' to 'xrestore_mask'.
- Move the masking logic 'xrestore_mask &= task_xfeatures' from
  restore_fpregs_from_user() into __restore_fpregs_from_user().
- Rename 'xrestore' to 'xrestore_mask' in restore_fpregs_from_user() to
  match the name in __restore_fpregs_from_user() and __fpu_restore_sig().
- In __fpu_restore_sig(), rename 'buf' to 'buf_f' to distinguish it from
  'buf_fx', and 'user_xfeatures' to 'xrestore_mask'.

No functional changes.

Suggested-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Andrei Vagin <avagin@google.com>
---
 arch/x86/kernel/fpu/signal.c | 40 ++++++++++++++++++------------------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
index 20b638c507ca..42c3d78bd849 100644
--- a/arch/x86/kernel/fpu/signal.c
+++ b/arch/x86/kernel/fpu/signal.c
@@ -24,15 +24,15 @@
  * Check for the presence of extended state information in the
  * user fpstate pointer in the sigcontext.
  */
-static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
+static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
 					    struct _fpx_sw_bytes *fx_sw)
 {
 	int min_xstate_size = sizeof(struct fxregs_state) +
 			      sizeof(struct xstate_header);
-	void __user *fpstate = fxbuf;
+	void __user *fpstate = buf_fx;
 	unsigned int magic2;
 
-	if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw)))
+	if (__copy_from_user(fx_sw, &buf_fx->sw_reserved[0], sizeof(*fx_sw)))
 		return false;
 
 	/* Check for the first magic field and other error scenarios. */
@@ -40,7 +40,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 	    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;
+		goto err_setfx;
 
 	/*
 	 * Check for the presence of second magic word at the end of memory
@@ -53,7 +53,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 
 	if (likely(magic2 == FP_XSTATE_MAGIC2))
 		return true;
-setfx:
+err_setfx:
 	trace_x86_fpu_xstate_check_failed(x86_task_fpu(current));
 
 	/* Set the parameters for fx only state */
@@ -240,15 +240,17 @@ bool copy_fpstate_to_sigframe(void __user *buf, void __user *buf_fx, int size, u
 	return true;
 }
 
-static int __restore_fpregs_from_user(void __user *buf, u64 ufeatures,
-				      u64 xrestore, bool fx_only)
+static int __restore_fpregs_from_user(void __user *buf, u64 task_xfeatures,
+				      u64 xrestore_mask, bool fx_only)
 {
 	if (use_xsave()) {
-		u64 init_bv = ufeatures & ~xrestore;
+		u64 init_bv = task_xfeatures & ~xrestore_mask;
 		int ret;
 
+		/* Restore enabled features only. */
+		xrestore_mask &= task_xfeatures;
 		if (likely(!fx_only))
-			ret = xrstor_from_user_sigframe(buf, xrestore);
+			ret = xrstor_from_user_sigframe(buf, xrestore_mask);
 		else
 			ret = fxrstor_from_user_sigframe(buf);
 
@@ -266,20 +268,18 @@ 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_mask, bool fx_only)
 {
 	struct fpu *fpu = x86_task_fpu(current);
 	int ret;
 
-	/* Restore enabled features only. */
-	xrestore &= fpu->fpstate->user_xfeatures;
 retry:
 	fpregs_lock();
 	/* Ensure that XFD is up to date */
 	xfd_update_state(fpu->fpstate);
 	pagefault_disable();
 	ret = __restore_fpregs_from_user(buf, fpu->fpstate->user_xfeatures,
-					 xrestore, fx_only);
+					 xrestore_mask, fx_only);
 	pagefault_enable();
 
 	if (unlikely(ret)) {
@@ -324,7 +324,7 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore, bool fx_onl
 	return true;
 }
 
-static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
+static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx,
 			      bool ia32_fxstate)
 {
 	struct task_struct *tsk = current;
@@ -332,7 +332,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
 	struct user_i387_ia32_struct env;
 	bool success, fx_only = false;
 	union fpregs_state *fpregs;
-	u64 user_xfeatures = 0;
+	u64 xrestore_mask = 0;
 
 	if (use_xsave()) {
 		struct _fpx_sw_bytes fx_sw_user;
@@ -341,14 +341,14 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
 			return false;
 
 		fx_only = !fx_sw_user.magic1;
-		user_xfeatures = fx_sw_user.xfeatures;
+		xrestore_mask = fx_sw_user.xfeatures;
 	} else {
-		user_xfeatures = XFEATURE_MASK_FPSSE;
+		xrestore_mask = XFEATURE_MASK_FPSSE;
 	}
 
 	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, xrestore_mask, fx_only);
 	}
 
 	/*
@@ -356,7 +356,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
 	 * to be ignored for histerical raisins. The legacy state is folded
 	 * in once the larger state has been copied.
 	 */
-	if (__copy_from_user(&env, buf, sizeof(env)))
+	if (__copy_from_user(&env, buf_f, sizeof(env)))
 		return false;
 
 	/*
@@ -420,7 +420,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
 		 *
 		 * Preserve supervisor states!
 		 */
-		u64 mask = user_xfeatures | xfeatures_mask_supervisor();
+		u64 mask = xrestore_mask | xfeatures_mask_supervisor();
 
 		fpregs->xsave.header.xfeatures &= mask;
 		success = !os_xrstor_safe(fpu->fpstate,
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 03/10] x86/fpu: Split __fpu_restore_sig to extract compat path
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
  2026-06-15 19:37 ` [PATCH 01/10] x86/fpu: Document " Andrei Vagin
  2026-06-15 19:37 ` [PATCH 02/10] x86/fpu: Clean up and rename variables in signal frame handling Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 17:20   ` Alexander Mikhalitsyn
  2026-06-15 19:37 ` [PATCH 04/10] x86/fpu: Document reasoning of FX-only fallback Andrei Vagin
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

Split __fpu_restore_sig to move the restore part for the legacy/compat
FPU state (when buf_f is present) to a separate helper function.

The legacy 32-bit FP frame duplicates the FP state portion of the
FX/XSAVE frame. For backward compatibility, the legacy FP frame is
treated as the source of truth, and its state is folded into the
FX/XSAVE state before restoring the registers.

Signed-off-by: Andrei Vagin <avagin@google.com>
---
 arch/x86/kernel/fpu/signal.c | 50 +++++++++++++++++++++++++++---------
 1 file changed, 38 insertions(+), 12 deletions(-)

diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
index 42c3d78bd849..6a14b528ac7f 100644
--- a/arch/x86/kernel/fpu/signal.c
+++ b/arch/x86/kernel/fpu/signal.c
@@ -264,6 +264,9 @@ static int __restore_fpregs_from_user(void __user *buf, u64 task_xfeatures,
 	}
 }
 
+static bool restore_fpregs_from_user_compat(void __user *buf_f, void __user *buf_fx,
+					    u64 xrestore_mask, bool fx_only);
+
 /*
  * Attempt to restore the FPU registers directly from user memory.
  * Pagefaults are handled and any errors returned are fatal.
@@ -324,14 +327,9 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, bool f
 	return true;
 }
 
-static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx,
-			      bool ia32_fxstate)
+static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx)
 {
-	struct task_struct *tsk = current;
-	struct fpu *fpu = x86_task_fpu(tsk);
-	struct user_i387_ia32_struct env;
-	bool success, fx_only = false;
-	union fpregs_state *fpregs;
+	bool fx_only = false;
 	u64 xrestore_mask = 0;
 
 	if (use_xsave()) {
@@ -346,11 +344,33 @@ static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx,
 		xrestore_mask = XFEATURE_MASK_FPSSE;
 	}
 
-	if (likely(!ia32_fxstate)) {
+	if (likely(!buf_f)) {
 		/* Restore the FPU registers directly from user memory. */
 		return restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only);
 	}
 
+	return restore_fpregs_from_user_compat(buf_f, buf_fx, xrestore_mask, fx_only);
+}
+
+#if defined(CONFIG_X86_32) || defined(CONFIG_IA32_EMULATION)
+/*
+ * Restore FPU state from a signal frame when a legacy 32-bit FP frame
+ * (buf_f) is present.
+ *
+ * The legacy FP frame duplicates the FP state portion of the FX/XSAVE
+ * frame (buf_fx). For backward compatibility, the legacy FP frame is
+ * treated as the source of truth, and its state is folded into the
+ * FX/XSAVE state before restoring the registers.
+ */
+static bool restore_fpregs_from_user_compat(void __user *buf_f, void __user *buf_fx,
+					    u64 xrestore_mask, bool fx_only)
+{
+	struct task_struct *tsk = current;
+	struct fpu *fpu = x86_task_fpu(tsk);
+	struct user_i387_ia32_struct env;
+	union fpregs_state *fpregs;
+	bool success;
+
 	/*
 	 * Copy the legacy state because the FP portion of the FX frame has
 	 * to be ignored for histerical raisins. The legacy state is folded
@@ -435,6 +455,13 @@ static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx,
 	fpregs_unlock();
 	return success;
 }
+#else
+static bool restore_fpregs_from_user_compat(void __user *buf_f, void __user *buf_fx,
+						   u64 xrestore_mask, bool fx_only)
+{
+	return false;
+}
+#endif
 
 static inline unsigned int xstate_sigframe_size(struct fpstate *fpstate)
 {
@@ -449,8 +476,7 @@ static inline unsigned int xstate_sigframe_size(struct fpstate *fpstate)
 bool fpu__restore_sig(void __user *buf, int ia32_frame)
 {
 	struct fpu *fpu = x86_task_fpu(current);
-	void __user *buf_fx = buf;
-	bool ia32_fxstate = false;
+	void __user *buf_fx = buf, *buf_f = NULL;
 	bool success = false;
 	unsigned int size;
 
@@ -471,7 +497,7 @@ bool fpu__restore_sig(void __user *buf, int ia32_frame)
 	if (ia32_frame && use_fxsr()) {
 		buf_fx = buf + sizeof(struct fregs_state);
 		size += sizeof(struct fregs_state);
-		ia32_fxstate = true;
+		buf_f = buf;
 	}
 
 	if (!access_ok(buf, size))
@@ -482,7 +508,7 @@ bool fpu__restore_sig(void __user *buf, int ia32_frame)
 					   sizeof(struct user_i387_ia32_struct),
 					   NULL, buf);
 	} else {
-		success = __fpu_restore_sig(buf, buf_fx, ia32_fxstate);
+		success = __fpu_restore_sig(buf_f, buf_fx);
 	}
 
 out:
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 04/10] x86/fpu: Document reasoning of FX-only fallback
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (2 preceding siblings ...)
  2026-06-15 19:37 ` [PATCH 03/10] x86/fpu: Split __fpu_restore_sig to extract compat path Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 14:22   ` Alexander Mikhalitsyn
  2026-06-30 19:23   ` Chang S. Bae
  2026-06-15 19:37 ` [PATCH 05/10] x86/fpu: Fix potential underflow in xstate_calculate_size() Andrei Vagin
                   ` (5 subsequent siblings)
  9 siblings, 2 replies; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

Add a comment to check_xstate_in_sigframe() to explain reasoning behind
falling back to the FX-only state when signal frame metadata is
inconsistent.

The fallback is intended to preserve backward compatibility with legacy
user-space processes that are not aware of XSAVE states and might only
fill or copy just the legacy FP state.

This fallback is dangerous as it can trigger silent corruptions of
user-space state by resetting extended registers if the process was
using them but the frame metadata was malformed.

XSAVE was introduced 15 years ago, we may need to consider removing this
fallback entirely or introducing a sysctl to enable/disable it.

Signed-off-by: Andrei Vagin <avagin@google.com>
---
 arch/x86/kernel/fpu/signal.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
index 6a14b528ac7f..85021c5ea649 100644
--- a/arch/x86/kernel/fpu/signal.c
+++ b/arch/x86/kernel/fpu/signal.c
@@ -54,6 +54,14 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
 	if (likely(magic2 == FP_XSTATE_MAGIC2))
 		return true;
 err_setfx:
+	/*
+	 * The fallback to FX-only state is used to preserve backward
+	 * compatibility with user-space processes that are not aware of xsave
+	 * states.
+	 *
+	 * In all other cases, returning false (to trigger SIGSEGV) is
+	 * preferred to avoid silent user-space state corruption.
+	 */
 	trace_x86_fpu_xstate_check_failed(x86_task_fpu(current));
 
 	/* Set the parameters for fx only state */
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 05/10] x86/fpu: Fix potential underflow in xstate_calculate_size()
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (3 preceding siblings ...)
  2026-06-15 19:37 ` [PATCH 04/10] x86/fpu: Document reasoning of FX-only fallback Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 14:32   ` Alexander Mikhalitsyn
  2026-06-30 19:23   ` Chang S. Bae
  2026-06-15 19:37 ` [PATCH 06/10] selftests/x86: Add a test for signal frame FPU portability Andrei Vagin
                   ` (4 subsequent siblings)
  9 siblings, 2 replies; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

xstate_calculate_size() calculates the size required for a given set of
xfeatures. It determines the topmost feature by finding the most
significant bit in xfeatures using fls64(xfeatures) - 1.

If xfeatures is 0, fls64(0) returns 0, and topmost becomes -1.
Previously, topmost was unsigned int, so -1 underflowed to UINT_MAX.
This caused the subsequent check `topmost <= XFEATURE_SSE` to fail, and
the code proceeded to access xstate arrays using topmost (UINT_MAX) as
an index, leading to an out-of-bounds access.

Fix this by checking if xfeatures only contains legacy features (FP/SSE)
or is empty (xfeatures <= XFEATURE_MASK_FPSSE) before calculating
topmost.

Fixes: d6d6d50f1e80 ("x86/fpu/xstate: Consolidate size calculations")
Signed-off-by: Andrei Vagin <avagin@google.com>
---
 arch/x86/kernel/fpu/xstate.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c
index a7b6524a9dea..ed39d7051d0d 100644
--- a/arch/x86/kernel/fpu/xstate.c
+++ b/arch/x86/kernel/fpu/xstate.c
@@ -589,12 +589,13 @@ static bool __init check_xstate_against_struct(int nr)
 
 static unsigned int xstate_calculate_size(u64 xfeatures, bool compacted)
 {
-	unsigned int topmost = fls64(xfeatures) -  1;
-	unsigned int offset, i;
+	unsigned int topmost, offset, i;
 
-	if (topmost <= XFEATURE_SSE)
+	if (xfeatures <= XFEATURE_MASK_FPSSE)
 		return sizeof(struct xregs_state);
 
+	topmost = fls64(xfeatures) -  1;
+
 	if (compacted) {
 		offset = xfeature_get_offset(xfeatures, topmost);
 	} else {
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 06/10] selftests/x86: Add a test for signal frame FPU portability
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (4 preceding siblings ...)
  2026-06-15 19:37 ` [PATCH 05/10] x86/fpu: Fix potential underflow in xstate_calculate_size() Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 14:39   ` Alexander Mikhalitsyn
  2026-06-15 19:37 ` [PATCH 07/10] x86/fpu: Pre-fault only required size of xstate buffer Andrei Vagin
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

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 <avagin@google.com>
---
 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


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 07/10] x86/fpu: Pre-fault only required size of xstate buffer
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (5 preceding siblings ...)
  2026-06-15 19:37 ` [PATCH 06/10] selftests/x86: Add a test for signal frame FPU portability Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 17:28   ` Alexander Mikhalitsyn
  2026-06-15 19:37 ` [PATCH 08/10] selftests/x86: Add a sigframe insufficient xstate_size test Andrei Vagin
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

The kernel previously used the default task FPU state size (user_size)
to fault in the user buffer when restoring FPU registers from a signal
frame. This can lead to attempting to fault in memory past the end of
the actual frame if the frame was smaller than the default size.

Introduce consistency checks to calculate the actual required size for the
features enabled in the xfeatures mask, ensure that the provided
xstate_size is sufficient, and shrink it to the actual required size. Use
this validated size to fault in the user buffer.

Keep the strict check that the provided xstate_size does not exceed the
default user_size for now.

Signed-off-by: Andrei Vagin <avagin@google.com>
---
 arch/x86/kernel/fpu/signal.c | 40 ++++++++++++++++++++++++++++--------
 arch/x86/kernel/fpu/xstate.c |  2 +-
 arch/x86/kernel/fpu/xstate.h |  2 ++
 3 files changed, 34 insertions(+), 10 deletions(-)

diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
index 85021c5ea649..1e7cc114c186 100644
--- a/arch/x86/kernel/fpu/signal.c
+++ b/arch/x86/kernel/fpu/signal.c
@@ -29,7 +29,8 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
 {
 	int min_xstate_size = sizeof(struct fxregs_state) +
 			      sizeof(struct xstate_header);
-	void __user *fpstate = buf_fx;
+	struct fpstate *fpstate = x86_task_fpu(current)->fpstate;
+	void __user *buf = buf_fx;
 	unsigned int magic2;
 
 	if (__copy_from_user(fx_sw, &buf_fx->sw_reserved[0], sizeof(*fx_sw)))
@@ -38,8 +39,9 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
 	/* 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)
+	    fx_sw->xstate_size > fpstate->user_size ||
+	    fx_sw->xstate_size > fx_sw->extended_size ||
+	    fx_sw->extended_size - fx_sw->xstate_size < FP_XSTATE_MAGIC2_SIZE)
 		goto err_setfx;
 
 	/*
@@ -48,11 +50,27 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
 	 * 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 *)(buf + fx_sw->xstate_size)))
 		return false;
+	if (unlikely(magic2 != FP_XSTATE_MAGIC2))
+		goto err_setfx;
 
-	if (likely(magic2 == FP_XSTATE_MAGIC2))
-		return true;
+	if (fx_sw->xstate_size != fpstate->user_size ||
+	    fx_sw->xfeatures != fpstate->user_xfeatures) {
+		unsigned int xsize;
+		u64 xfeatures;
+
+		/* Calculate size of enabled features only. */
+		xfeatures = fx_sw->xfeatures & fpstate->user_xfeatures;
+
+		xsize = xstate_calculate_size(xfeatures, false);
+		if (fx_sw->xstate_size < xsize)
+			return false;
+
+		fx_sw->xstate_size = xsize;
+	}
+
+	return true;
 err_setfx:
 	/*
 	 * The fallback to FX-only state is used to preserve backward
@@ -279,7 +297,8 @@ static bool restore_fpregs_from_user_compat(void __user *buf_f, void __user *buf
  * 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_mask, bool fx_only)
+static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask,
+				     bool fx_only, size_t xstate_size)
 {
 	struct fpu *fpu = x86_task_fpu(current);
 	int ret;
@@ -313,7 +332,7 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, bool f
 		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;
 	}
@@ -339,6 +358,7 @@ static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx)
 {
 	bool fx_only = false;
 	u64 xrestore_mask = 0;
+	size_t xstate_size;
 
 	if (use_xsave()) {
 		struct _fpx_sw_bytes fx_sw_user;
@@ -348,13 +368,15 @@ static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx)
 
 		fx_only = !fx_sw_user.magic1;
 		xrestore_mask = fx_sw_user.xfeatures;
+		xstate_size = fx_sw_user.xstate_size;
 	} else {
 		xrestore_mask = XFEATURE_MASK_FPSSE;
+		xstate_size = sizeof(struct fxregs_state);
 	}
 
 	if (likely(!buf_f)) {
 		/* Restore the FPU registers directly from user memory. */
-		return restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only);
+		return restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only, xstate_size);
 	}
 
 	return restore_fpregs_from_user_compat(buf_f, buf_fx, xrestore_mask, fx_only);
diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c
index ed39d7051d0d..b7d0d78d2081 100644
--- a/arch/x86/kernel/fpu/xstate.c
+++ b/arch/x86/kernel/fpu/xstate.c
@@ -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, offset, i;
 
diff --git a/arch/x86/kernel/fpu/xstate.h b/arch/x86/kernel/fpu/xstate.h
index 38a2862f09d3..c73cf2444de6 100644
--- a/arch/x86/kernel/fpu/xstate.h
+++ b/arch/x86/kernel/fpu/xstate.h
@@ -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)
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 08/10] selftests/x86: Add a sigframe insufficient xstate_size test
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (6 preceding siblings ...)
  2026-06-15 19:37 ` [PATCH 07/10] x86/fpu: Pre-fault only required size of xstate buffer Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 15:02   ` Alexander Mikhalitsyn
  2026-06-15 19:37 ` [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size Andrei Vagin
  2026-06-15 19:37 ` [PATCH 10/10] selftests/x86: Check restoring FPU state " Andrei Vagin
  9 siblings, 1 reply; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

Extend sigframe_fpu_portability to include an insufficient xstate_size
check (test_insufficient_xstate_size). Verify that the kernel correctly
rejects signal frames where the xstate_size is too small for the enabled
features in the xfeatures mask.

Signed-off-by: Andrei Vagin <avagin@google.com>
---
 .../selftests/x86/sigframe_fpu_portability.c  | 68 +++++++++++++++++--
 1 file changed, 62 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/x86/sigframe_fpu_portability.c b/tools/testing/selftests/x86/sigframe_fpu_portability.c
index 169548892f92..462219905303 100644
--- a/tools/testing/selftests/x86/sigframe_fpu_portability.c
+++ b/tools/testing/selftests/x86/sigframe_fpu_portability.c
@@ -12,19 +12,24 @@
 #include <sys/syscall.h>
 #include <asm/prctl.h>
 #include <stddef.h>
+#include <setjmp.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.
+ * This test verifies the FPU portability and consistency of the signal frame.
+ *
+ * - test_shrunk_xstate_size:
+ *   Verifies that the kernel restores state from a frame with xstate_size
+ *   shrunk to only include active features.
+ *
+ * - test_insufficient_xstate_size:
+ *   Verifies that the kernel rejects a frame if xstate_size is too small 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))
 
@@ -148,15 +153,66 @@ static void test_shrunk_xstate_size(void)
 	clearhandler(SIGUSR1);
 }
 
+static sigjmp_buf segv_jmpbuf;
+
+static void handle_segv(int sig, siginfo_t *si, void *ucp)
+{
+	siglongjmp(segv_jmpbuf, 1);
+}
+
+static void handle_insufficient_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);
+
+	/* 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_insufficient_xstate_size(void)
+{
+	uint64_t v[4] = {0, 0, 0, 0};
+
+	sig_err_buf[0] = 0;
+	sethandler(SIGUSR1, handle_insufficient_xstate_size, 0);
+	sethandler(SIGSEGV, handle_segv, 0);
+
+	v[0] = 0x1111111111111111ULL;
+	v[1] = 0x2222222222222222ULL;
+	v[2] = 0x3333333333333333ULL;
+	v[3] = 0x4444444444444444ULL;
+	write_ymm0(v);
+
+	if (sigsetjmp(segv_jmpbuf, 1) == 0) {
+		raise(SIGUSR1);
+		sig_print("Inconsistent size was NOT rejected\n");
+	}
+
+	clearhandler(SIGUSR1);
+	clearhandler(SIGSEGV);
+
+	if (sig_err_buf[0])
+		ksft_test_result_fail("%s\n", sig_err_buf);
+	else
+		ksft_test_result_pass("Inconsistent size correctly rejected\n");
+
+	clearhandler(SIGUSR1);
+	clearhandler(SIGSEGV);
+}
 
 int main(void)
 {
 	ksft_print_header();
-	ksft_set_plan(1);
+	ksft_set_plan(2);
 
 	check_avx_support();
 
 	test_shrunk_xstate_size();
+	test_insufficient_xstate_size();
+
 	ksft_finished();
 	return 0;
 }
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (7 preceding siblings ...)
  2026-06-15 19:37 ` [PATCH 08/10] selftests/x86: Add a sigframe insufficient xstate_size test Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 17:32   ` Alexander Mikhalitsyn
  2026-06-30 19:23   ` Chang S. Bae
  2026-06-15 19:37 ` [PATCH 10/10] selftests/x86: Check restoring FPU state " Andrei Vagin
  9 siblings, 2 replies; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

The kernel previously enforced that the xstate_size in the signal frame
must not exceed the current task's fpstate->user_size. This prevents
restoring signal frames that were saved on another CPU (in case of
container/process migration) with a different (larger) set of enabled
xstate features, even if the features to be restored are compatible.

Relax this restriction by removing the strict check against user_size.
The previous commit introduced infrastructure to calculate the actual
required size based on the intersection of requested and supported
features.  We now rely on that validation and only require that the
provided xstate_size is sufficient for the active features.

Signed-off-by: Andrei Vagin <avagin@google.com>
---
 arch/x86/kernel/fpu/signal.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
index 1e7cc114c186..083f03d2d002 100644
--- a/arch/x86/kernel/fpu/signal.c
+++ b/arch/x86/kernel/fpu/signal.c
@@ -36,14 +36,23 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
 	if (__copy_from_user(fx_sw, &buf_fx->sw_reserved[0], sizeof(*fx_sw)))
 		return false;
 
-	/* Check for the first magic field and other error scenarios. */
+	/* Check for the first magic field and other error scenarios.
+	 *
+	 * Do not enforce that fx_sw->xstate_size matches the task's
+	 * fpstate->user_size. The frame could be saved on another CPU with a
+	 * different set of xtate features. The actual set of used features is
+	 * defined in the xsave header. If the buffer contains any unsupported
+	 * feature states, it will be rejected.
+	 */
 	if (fx_sw->magic1 != FP_XSTATE_MAGIC1 ||
 	    fx_sw->xstate_size < min_xstate_size ||
-	    fx_sw->xstate_size > fpstate->user_size ||
 	    fx_sw->xstate_size > fx_sw->extended_size ||
 	    fx_sw->extended_size - fx_sw->xstate_size < FP_XSTATE_MAGIC2_SIZE)
 		goto err_setfx;
 
+	if (!access_ok(buf_fx, fx_sw->extended_size))
+		goto err_setfx;
+
 	/*
 	 * Check for the presence of second magic word at the end of memory
 	 * layout. This detects the case where the user just copied the legacy
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 10/10] selftests/x86: Check restoring FPU state with larger xstate_size
  2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (8 preceding siblings ...)
  2026-06-15 19:37 ` [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size Andrei Vagin
@ 2026-06-15 19:37 ` Andrei Vagin
  2026-06-26 15:12   ` Alexander Mikhalitsyn
  9 siblings, 1 reply; 26+ messages in thread
From: Andrei Vagin @ 2026-06-15 19:37 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
  Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
	H. Peter Anvin

Add two new test cases to sigframe_fpu_portability.c:

The first test case (test_larger_xstate_size) verifies that the kernel
can restore FPU state from a signal frame that has xstate_size larger
than the current task's fpstate->user_size, but the buffer doesn't
contain states of any unsupported features. This test case emulates a
case when a process is migrated from a newer cpu to an older cpu, but
the process doesn't use any unsupported features.

The second test case (test_unsupported_xfeatures) verifies that the
kernel correctly rejects restoring FPU state from a signal frame if it
contains states of any unsupported features.

Signed-off-by: Andrei Vagin <avagin@google.com>
---
 .../selftests/x86/sigframe_fpu_portability.c  | 129 +++++++++++++++++-
 1 file changed, 128 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/x86/sigframe_fpu_portability.c b/tools/testing/selftests/x86/sigframe_fpu_portability.c
index 462219905303..b349efcf05c3 100644
--- a/tools/testing/selftests/x86/sigframe_fpu_portability.c
+++ b/tools/testing/selftests/x86/sigframe_fpu_portability.c
@@ -27,6 +27,14 @@
  * - test_insufficient_xstate_size:
  *   Verifies that the kernel rejects a frame if xstate_size is too small for
  *   the features enabled in xfeatures.
+ *
+ * - test_larger_xstate_size:
+ *   Verifies that the kernel restores state from a frame with xstate_size
+ *   larger than the current task's size, if no unsupported features are active.
+ *
+ * - test_unsupported_xfeatures:
+ *   Verifies that the kernel rejects a frame if it contains unsupported
+ *   features in the xsave header.
  */
 
 #define SIGFRAME_XSTATE_HDR_OFFSET	512
@@ -203,15 +211,134 @@ static void test_insufficient_xstate_size(void)
 	clearhandler(SIGSEGV);
 }
 
+static char fpu_buffer[8192] __attribute__((aligned(64)));
+#define UNSUPPORTED_XFEATURE (1ULL<<62)
+
+static void __handle_larger_xstate_size(int sig, siginfo_t *si, void *ucp, bool mod_xhdr)
+{
+	ucontext_t *uc = ucp;
+	void *fp = uc->uc_mcontext.fpregs;
+	struct _fpx_sw_bytes *sw = get_fpx_sw_bytes(fp);
+	size_t copy_size;
+	uint64_t *ymmh_p, xfeatures;
+	struct xsave_buffer *xbuf;
+
+	if (sw->magic1 != FP_XSTATE_MAGIC1) {
+		sig_print("magic1 is not valid\n");
+		return;
+	}
+
+	copy_size = sw->xstate_size;
+	if (copy_size > sizeof(fpu_buffer)) {
+		sig_print("fpu_buffer is too small\n");
+		return;
+	}
+
+	memset(fpu_buffer, 0, sizeof(fpu_buffer));
+	memcpy(fpu_buffer, fp, copy_size);
+
+	xbuf = (struct xsave_buffer *)fpu_buffer;
+	sw = get_fpx_sw_bytes(fpu_buffer);
+
+	sw->xstate_size += 64;
+	sw->extended_size += 64;
+	xfeatures = get_fpx_sw_bytes_features(xbuf);
+	set_fpx_sw_bytes_features(fpu_buffer, xfeatures | UNSUPPORTED_XFEATURE);
+
+	*(uint32_t *)((char *)fpu_buffer + sw->xstate_size) = FP_XSTATE_MAGIC2;
+
+	if (mod_xhdr) {
+		xfeatures = get_xstatebv(xbuf);
+		set_xstatebv(xbuf, xfeatures | UNSUPPORTED_XFEATURE);
+	}
+
+	ymmh_p = (uint64_t *)(fpu_buffer + ymm_offset);
+	ymmh_p[0] = TEST_YMMH_VAL;
+	ymmh_p[1] = TEST_YMMH_VAL + 1;
+
+	/* Update fpregs to point to the new buffer */
+	uc->uc_mcontext.fpregs = (fpregset_t)fpu_buffer;
+}
+
+static void handle_larger_xstate_size(int sig, siginfo_t *si, void *ucp)
+{
+	__handle_larger_xstate_size(sig, si, ucp, false);
+}
+
+static void test_larger_xstate_size(void)
+{
+	uint64_t v[4] = {0, 0, 0, 0};
+
+	sig_err_buf[0] = 0;
+	sethandler(SIGUSR1, handle_larger_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 with larger xstate_size\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_unsupported_xfeatures(int sig, siginfo_t *si, void *ucp)
+{
+	__handle_larger_xstate_size(sig, si, ucp, true);
+}
+
+static void test_unsupported_xfeatures(void)
+{
+	uint64_t v[4] = {0, 0, 0, 0};
+
+	sig_err_buf[0] = 0;
+
+	sethandler(SIGUSR1, handle_unsupported_xfeatures, 0);
+	sethandler(SIGSEGV, handle_segv, 0);
+
+	v[0] = 0x1111111111111111ULL;
+	v[1] = 0x2222222222222222ULL;
+	v[2] = 0x3333333333333333ULL;
+	v[3] = 0x4444444444444444ULL;
+	write_ymm0(v);
+
+	if (sigsetjmp(segv_jmpbuf, 1) == 0) {
+		raise(SIGUSR1);
+		sig_print("raise(SIGUSR1) returned (expected SIGSEGV)\n");
+	}
+
+	clearhandler(SIGUSR1);
+	clearhandler(SIGSEGV);
+
+	if (sig_err_buf[0])
+		ksft_test_result_fail("%s\n", sig_err_buf);
+	else
+		ksft_test_result_pass("Unsupported feature in xsave header triggered SIGSEGV\n");
+}
+
 int main(void)
 {
 	ksft_print_header();
-	ksft_set_plan(2);
+	ksft_set_plan(4);
 
 	check_avx_support();
 
 	test_shrunk_xstate_size();
 	test_insufficient_xstate_size();
+	test_larger_xstate_size();
+	test_unsupported_xfeatures();
 
 	ksft_finished();
 	return 0;
-- 
2.54.0.1189.g8c84645362-goog


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH 01/10] x86/fpu: Document signal frame portability
  2026-06-15 19:37 ` [PATCH 01/10] x86/fpu: Document " Andrei Vagin
@ 2026-06-26 13:51   ` Alexander Mikhalitsyn
  2026-06-30 19:23   ` Chang S. Bae
  1 sibling, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 13:51 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:37 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> 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.
>
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  Documentation/arch/x86/xstate.rst      | 13 +++++++++++++
>  arch/x86/include/uapi/asm/sigcontext.h | 13 +++++++++++++
>  2 files changed, 26 insertions(+)
>
> diff --git a/Documentation/arch/x86/xstate.rst b/Documentation/arch/x86/xstate.rst
> index cec05ac464c1..facd97b26bf1 100644
> --- a/Documentation/arch/x86/xstate.rst
> +++ b/Documentation/arch/x86/xstate.rst
> @@ -172,3 +172,16 @@ are extended to control the guest permission:
>
>  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.
> diff --git a/arch/x86/include/uapi/asm/sigcontext.h b/arch/x86/include/uapi/asm/sigcontext.h
> index d0d9b331d3a1..d52313345f21 100644
> --- a/arch/x86/include/uapi/asm/sigcontext.h
> +++ b/arch/x86/include/uapi/asm/sigcontext.h
> @@ -34,6 +34,19 @@
>   * fpstate+extended_size-FP_XSTATE_MAGIC2_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 in
> + * conjunction with the pointer to the xstate context to locate
> + * FP_XSTATE_MAGIC2. Note that on 32-bit systems, the fpstate pointer points
> + * to a legacy struct fregs_state (112 bytes) that precedes the xstate
> + * context, so the xstate context starts at fpstate + 112. 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.
>   */
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 04/10] x86/fpu: Document reasoning of FX-only fallback
  2026-06-15 19:37 ` [PATCH 04/10] x86/fpu: Document reasoning of FX-only fallback Andrei Vagin
@ 2026-06-26 14:22   ` Alexander Mikhalitsyn
  2026-06-30 19:23   ` Chang S. Bae
  1 sibling, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 14:22 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:39 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> Add a comment to check_xstate_in_sigframe() to explain reasoning behind
> falling back to the FX-only state when signal frame metadata is
> inconsistent.
>
> The fallback is intended to preserve backward compatibility with legacy
> user-space processes that are not aware of XSAVE states and might only
> fill or copy just the legacy FP state.
>
> This fallback is dangerous as it can trigger silent corruptions of
> user-space state by resetting extended registers if the process was
> using them but the frame metadata was malformed.
>
> XSAVE was introduced 15 years ago, we may need to consider removing this
> fallback entirely or introducing a sysctl to enable/disable it.
>
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  arch/x86/kernel/fpu/signal.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
>
> diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
> index 6a14b528ac7f..85021c5ea649 100644
> --- a/arch/x86/kernel/fpu/signal.c
> +++ b/arch/x86/kernel/fpu/signal.c
> @@ -54,6 +54,14 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
>         if (likely(magic2 == FP_XSTATE_MAGIC2))
>                 return true;
>  err_setfx:
> +       /*
> +        * The fallback to FX-only state is used to preserve backward
> +        * compatibility with user-space processes that are not aware of xsave
> +        * states.
> +        *
> +        * In all other cases, returning false (to trigger SIGSEGV) is
> +        * preferred to avoid silent user-space state corruption.
> +        */
>         trace_x86_fpu_xstate_check_failed(x86_task_fpu(current));
>
>         /* Set the parameters for fx only state */
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 05/10] x86/fpu: Fix potential underflow in xstate_calculate_size()
  2026-06-15 19:37 ` [PATCH 05/10] x86/fpu: Fix potential underflow in xstate_calculate_size() Andrei Vagin
@ 2026-06-26 14:32   ` Alexander Mikhalitsyn
  2026-06-30 19:23   ` Chang S. Bae
  1 sibling, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 14:32 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:39 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> xstate_calculate_size() calculates the size required for a given set of
> xfeatures. It determines the topmost feature by finding the most
> significant bit in xfeatures using fls64(xfeatures) - 1.
>
> If xfeatures is 0, fls64(0) returns 0, and topmost becomes -1.
> Previously, topmost was unsigned int, so -1 underflowed to UINT_MAX.
> This caused the subsequent check `topmost <= XFEATURE_SSE` to fail, and
> the code proceeded to access xstate arrays using topmost (UINT_MAX) as
> an index, leading to an out-of-bounds access.
>
> Fix this by checking if xfeatures only contains legacy features (FP/SSE)
> or is empty (xfeatures <= XFEATURE_MASK_FPSSE) before calculating
> topmost.
>
> Fixes: d6d6d50f1e80 ("x86/fpu/xstate: Consolidate size calculations")
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  arch/x86/kernel/fpu/xstate.c | 7 ++++---
>  1 file changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c
> index a7b6524a9dea..ed39d7051d0d 100644
> --- a/arch/x86/kernel/fpu/xstate.c
> +++ b/arch/x86/kernel/fpu/xstate.c
> @@ -589,12 +589,13 @@ static bool __init check_xstate_against_struct(int nr)
>
>  static unsigned int xstate_calculate_size(u64 xfeatures, bool compacted)
>  {
> -       unsigned int topmost = fls64(xfeatures) -  1;
> -       unsigned int offset, i;
> +       unsigned int topmost, offset, i;
>
> -       if (topmost <= XFEATURE_SSE)
> +       if (xfeatures <= XFEATURE_MASK_FPSSE)
>                 return sizeof(struct xregs_state);
>
> +       topmost = fls64(xfeatures) -  1;
> +
>         if (compacted) {
>                 offset = xfeature_get_offset(xfeatures, topmost);
>         } else {
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 06/10] selftests/x86: Add a test for signal frame FPU portability
  2026-06-15 19:37 ` [PATCH 06/10] selftests/x86: Add a test for signal frame FPU portability Andrei Vagin
@ 2026-06-26 14:39   ` Alexander Mikhalitsyn
  0 siblings, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 14:39 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:40 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> 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 <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

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

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 08/10] selftests/x86: Add a sigframe insufficient xstate_size test
  2026-06-15 19:37 ` [PATCH 08/10] selftests/x86: Add a sigframe insufficient xstate_size test Andrei Vagin
@ 2026-06-26 15:02   ` Alexander Mikhalitsyn
  0 siblings, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 15:02 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:40 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> Extend sigframe_fpu_portability to include an insufficient xstate_size
> check (test_insufficient_xstate_size). Verify that the kernel correctly
> rejects signal frames where the xstate_size is too small for the enabled
> features in the xfeatures mask.
>
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  .../selftests/x86/sigframe_fpu_portability.c  | 68 +++++++++++++++++--
>  1 file changed, 62 insertions(+), 6 deletions(-)
>
> diff --git a/tools/testing/selftests/x86/sigframe_fpu_portability.c b/tools/testing/selftests/x86/sigframe_fpu_portability.c
> index 169548892f92..462219905303 100644
> --- a/tools/testing/selftests/x86/sigframe_fpu_portability.c
> +++ b/tools/testing/selftests/x86/sigframe_fpu_portability.c
> @@ -12,19 +12,24 @@
>  #include <sys/syscall.h>
>  #include <asm/prctl.h>
>  #include <stddef.h>
> +#include <setjmp.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.
> + * This test verifies the FPU portability and consistency of the signal frame.
> + *
> + * - test_shrunk_xstate_size:
> + *   Verifies that the kernel restores state from a frame with xstate_size
> + *   shrunk to only include active features.
> + *
> + * - test_insufficient_xstate_size:
> + *   Verifies that the kernel rejects a frame if xstate_size is too small 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))
>
> @@ -148,15 +153,66 @@ static void test_shrunk_xstate_size(void)
>         clearhandler(SIGUSR1);
>  }
>
> +static sigjmp_buf segv_jmpbuf;
> +
> +static void handle_segv(int sig, siginfo_t *si, void *ucp)
> +{
> +       siglongjmp(segv_jmpbuf, 1);
> +}
> +
> +static void handle_insufficient_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);
> +
> +       /* 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_insufficient_xstate_size(void)
> +{
> +       uint64_t v[4] = {0, 0, 0, 0};
> +
> +       sig_err_buf[0] = 0;
> +       sethandler(SIGUSR1, handle_insufficient_xstate_size, 0);
> +       sethandler(SIGSEGV, handle_segv, 0);
> +
> +       v[0] = 0x1111111111111111ULL;
> +       v[1] = 0x2222222222222222ULL;
> +       v[2] = 0x3333333333333333ULL;
> +       v[3] = 0x4444444444444444ULL;
> +       write_ymm0(v);
> +
> +       if (sigsetjmp(segv_jmpbuf, 1) == 0) {
> +               raise(SIGUSR1);
> +               sig_print("Inconsistent size was NOT rejected\n");
> +       }
> +
> +       clearhandler(SIGUSR1);
> +       clearhandler(SIGSEGV);
> +
> +       if (sig_err_buf[0])
> +               ksft_test_result_fail("%s\n", sig_err_buf);
> +       else
> +               ksft_test_result_pass("Inconsistent size correctly rejected\n");
> +
> +       clearhandler(SIGUSR1);
> +       clearhandler(SIGSEGV);
> +}
>
>  int main(void)
>  {
>         ksft_print_header();
> -       ksft_set_plan(1);
> +       ksft_set_plan(2);
>
>         check_avx_support();
>
>         test_shrunk_xstate_size();
> +       test_insufficient_xstate_size();
> +
>         ksft_finished();
>         return 0;
>  }
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 10/10] selftests/x86: Check restoring FPU state with larger xstate_size
  2026-06-15 19:37 ` [PATCH 10/10] selftests/x86: Check restoring FPU state " Andrei Vagin
@ 2026-06-26 15:12   ` Alexander Mikhalitsyn
  0 siblings, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 15:12 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:42 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> Add two new test cases to sigframe_fpu_portability.c:
>
> The first test case (test_larger_xstate_size) verifies that the kernel
> can restore FPU state from a signal frame that has xstate_size larger
> than the current task's fpstate->user_size, but the buffer doesn't
> contain states of any unsupported features. This test case emulates a
> case when a process is migrated from a newer cpu to an older cpu, but
> the process doesn't use any unsupported features.
>
> The second test case (test_unsupported_xfeatures) verifies that the
> kernel correctly rejects restoring FPU state from a signal frame if it
> contains states of any unsupported features.
>
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  .../selftests/x86/sigframe_fpu_portability.c  | 129 +++++++++++++++++-
>  1 file changed, 128 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/x86/sigframe_fpu_portability.c b/tools/testing/selftests/x86/sigframe_fpu_portability.c
> index 462219905303..b349efcf05c3 100644
> --- a/tools/testing/selftests/x86/sigframe_fpu_portability.c
> +++ b/tools/testing/selftests/x86/sigframe_fpu_portability.c
> @@ -27,6 +27,14 @@
>   * - test_insufficient_xstate_size:
>   *   Verifies that the kernel rejects a frame if xstate_size is too small for
>   *   the features enabled in xfeatures.
> + *
> + * - test_larger_xstate_size:
> + *   Verifies that the kernel restores state from a frame with xstate_size
> + *   larger than the current task's size, if no unsupported features are active.
> + *
> + * - test_unsupported_xfeatures:
> + *   Verifies that the kernel rejects a frame if it contains unsupported
> + *   features in the xsave header.
>   */
>
>  #define SIGFRAME_XSTATE_HDR_OFFSET     512
> @@ -203,15 +211,134 @@ static void test_insufficient_xstate_size(void)
>         clearhandler(SIGSEGV);
>  }
>
> +static char fpu_buffer[8192] __attribute__((aligned(64)));
> +#define UNSUPPORTED_XFEATURE (1ULL<<62)
> +
> +static void __handle_larger_xstate_size(int sig, siginfo_t *si, void *ucp, bool mod_xhdr)
> +{
> +       ucontext_t *uc = ucp;
> +       void *fp = uc->uc_mcontext.fpregs;
> +       struct _fpx_sw_bytes *sw = get_fpx_sw_bytes(fp);
> +       size_t copy_size;
> +       uint64_t *ymmh_p, xfeatures;
> +       struct xsave_buffer *xbuf;
> +
> +       if (sw->magic1 != FP_XSTATE_MAGIC1) {
> +               sig_print("magic1 is not valid\n");
> +               return;
> +       }
> +
> +       copy_size = sw->xstate_size;
> +       if (copy_size > sizeof(fpu_buffer)) {
> +               sig_print("fpu_buffer is too small\n");
> +               return;
> +       }
> +
> +       memset(fpu_buffer, 0, sizeof(fpu_buffer));
> +       memcpy(fpu_buffer, fp, copy_size);
> +
> +       xbuf = (struct xsave_buffer *)fpu_buffer;
> +       sw = get_fpx_sw_bytes(fpu_buffer);
> +
> +       sw->xstate_size += 64;
> +       sw->extended_size += 64;
> +       xfeatures = get_fpx_sw_bytes_features(xbuf);
> +       set_fpx_sw_bytes_features(fpu_buffer, xfeatures | UNSUPPORTED_XFEATURE);
> +
> +       *(uint32_t *)((char *)fpu_buffer + sw->xstate_size) = FP_XSTATE_MAGIC2;
> +
> +       if (mod_xhdr) {
> +               xfeatures = get_xstatebv(xbuf);
> +               set_xstatebv(xbuf, xfeatures | UNSUPPORTED_XFEATURE);
> +       }
> +
> +       ymmh_p = (uint64_t *)(fpu_buffer + ymm_offset);
> +       ymmh_p[0] = TEST_YMMH_VAL;
> +       ymmh_p[1] = TEST_YMMH_VAL + 1;
> +
> +       /* Update fpregs to point to the new buffer */
> +       uc->uc_mcontext.fpregs = (fpregset_t)fpu_buffer;
> +}
> +
> +static void handle_larger_xstate_size(int sig, siginfo_t *si, void *ucp)
> +{
> +       __handle_larger_xstate_size(sig, si, ucp, false);
> +}
> +
> +static void test_larger_xstate_size(void)
> +{
> +       uint64_t v[4] = {0, 0, 0, 0};
> +
> +       sig_err_buf[0] = 0;
> +       sethandler(SIGUSR1, handle_larger_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 with larger xstate_size\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_unsupported_xfeatures(int sig, siginfo_t *si, void *ucp)
> +{
> +       __handle_larger_xstate_size(sig, si, ucp, true);
> +}
> +
> +static void test_unsupported_xfeatures(void)
> +{
> +       uint64_t v[4] = {0, 0, 0, 0};
> +
> +       sig_err_buf[0] = 0;
> +
> +       sethandler(SIGUSR1, handle_unsupported_xfeatures, 0);
> +       sethandler(SIGSEGV, handle_segv, 0);
> +
> +       v[0] = 0x1111111111111111ULL;
> +       v[1] = 0x2222222222222222ULL;
> +       v[2] = 0x3333333333333333ULL;
> +       v[3] = 0x4444444444444444ULL;
> +       write_ymm0(v);
> +
> +       if (sigsetjmp(segv_jmpbuf, 1) == 0) {
> +               raise(SIGUSR1);
> +               sig_print("raise(SIGUSR1) returned (expected SIGSEGV)\n");
> +       }
> +
> +       clearhandler(SIGUSR1);
> +       clearhandler(SIGSEGV);
> +
> +       if (sig_err_buf[0])
> +               ksft_test_result_fail("%s\n", sig_err_buf);
> +       else
> +               ksft_test_result_pass("Unsupported feature in xsave header triggered SIGSEGV\n");
> +}
> +
>  int main(void)
>  {
>         ksft_print_header();
> -       ksft_set_plan(2);
> +       ksft_set_plan(4);
>
>         check_avx_support();
>
>         test_shrunk_xstate_size();
>         test_insufficient_xstate_size();
> +       test_larger_xstate_size();
> +       test_unsupported_xfeatures();
>
>         ksft_finished();
>         return 0;
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 02/10] x86/fpu: Clean up and rename variables in signal frame handling
  2026-06-15 19:37 ` [PATCH 02/10] x86/fpu: Clean up and rename variables in signal frame handling Andrei Vagin
@ 2026-06-26 17:05   ` Alexander Mikhalitsyn
  0 siblings, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 17:05 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin, Ingo Molnar

Am Mo., 15. Juni 2026 um 21:38 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> Clean up signal frame handling code by renaming several variables for
> clarity and consistency, and moving masking logic closer to its usage.
>
> - Rename 'fxbuf' to 'buf_fx' in check_xstate_in_sigframe() for consistency.
> - Rename label 'setfx' to 'err_setfx' in check_xstate_in_sigframe() to
>   indicate it is an error path.
> - In __restore_fpregs_from_user(), rename 'ufeatures' to 'task_xfeatures'
>   and 'xrestore' to 'xrestore_mask'.
> - Move the masking logic 'xrestore_mask &= task_xfeatures' from
>   restore_fpregs_from_user() into __restore_fpregs_from_user().
> - Rename 'xrestore' to 'xrestore_mask' in restore_fpregs_from_user() to
>   match the name in __restore_fpregs_from_user() and __fpu_restore_sig().
> - In __fpu_restore_sig(), rename 'buf' to 'buf_f' to distinguish it from
>   'buf_fx', and 'user_xfeatures' to 'xrestore_mask'.
>
> No functional changes.
>
> Suggested-by: Ingo Molnar <mingo@kernel.org>
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  arch/x86/kernel/fpu/signal.c | 40 ++++++++++++++++++------------------
>  1 file changed, 20 insertions(+), 20 deletions(-)
>
> diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
> index 20b638c507ca..42c3d78bd849 100644
> --- a/arch/x86/kernel/fpu/signal.c
> +++ b/arch/x86/kernel/fpu/signal.c
> @@ -24,15 +24,15 @@
>   * Check for the presence of extended state information in the
>   * user fpstate pointer in the sigcontext.
>   */
> -static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
> +static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
>                                             struct _fpx_sw_bytes *fx_sw)
>  {
>         int min_xstate_size = sizeof(struct fxregs_state) +
>                               sizeof(struct xstate_header);
> -       void __user *fpstate = fxbuf;
> +       void __user *fpstate = buf_fx;
>         unsigned int magic2;
>
> -       if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw)))
> +       if (__copy_from_user(fx_sw, &buf_fx->sw_reserved[0], sizeof(*fx_sw)))
>                 return false;
>
>         /* Check for the first magic field and other error scenarios. */
> @@ -40,7 +40,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
>             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;
> +               goto err_setfx;
>
>         /*
>          * Check for the presence of second magic word at the end of memory
> @@ -53,7 +53,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
>
>         if (likely(magic2 == FP_XSTATE_MAGIC2))
>                 return true;
> -setfx:
> +err_setfx:
>         trace_x86_fpu_xstate_check_failed(x86_task_fpu(current));
>
>         /* Set the parameters for fx only state */
> @@ -240,15 +240,17 @@ bool copy_fpstate_to_sigframe(void __user *buf, void __user *buf_fx, int size, u
>         return true;
>  }
>
> -static int __restore_fpregs_from_user(void __user *buf, u64 ufeatures,
> -                                     u64 xrestore, bool fx_only)
> +static int __restore_fpregs_from_user(void __user *buf, u64 task_xfeatures,
> +                                     u64 xrestore_mask, bool fx_only)
>  {
>         if (use_xsave()) {
> -               u64 init_bv = ufeatures & ~xrestore;
> +               u64 init_bv = task_xfeatures & ~xrestore_mask;
>                 int ret;

We had an off-list discussion about this place with Andrei. Here we
implicitly assume that:

[new code]
init_bv = task_xfeatures & ~xrestore_mask
xrestore_mask &= task_xfeatures

is equivalent to:

[old code]
xrestore_mask &= task_xfeatures
init_bv = task_xfeatures & ~xrestore_mask

For xrestore_mask is is obvious.
For init_bv, it may be not that obvious.

If we consider a function bv(x, y) = x & ~y, then we want to ensure
that the following properly holds:
bv(x, y) = bv(x, (y & x)).

bv(x, (y & x)) = x & ~(y & x) = x & (~y | ~x) = (x & ~y) | (x & ~x) =
bv(x, y) | 0 = bv(x, y).

>
> +               /* Restore enabled features only. */
> +               xrestore_mask &= task_xfeatures;
>                 if (likely(!fx_only))
> -                       ret = xrstor_from_user_sigframe(buf, xrestore);
> +                       ret = xrstor_from_user_sigframe(buf, xrestore_mask);
>                 else
>                         ret = fxrstor_from_user_sigframe(buf);
>
> @@ -266,20 +268,18 @@ 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_mask, bool fx_only)
>  {
>         struct fpu *fpu = x86_task_fpu(current);
>         int ret;
>
> -       /* Restore enabled features only. */
> -       xrestore &= fpu->fpstate->user_xfeatures;
>  retry:
>         fpregs_lock();
>         /* Ensure that XFD is up to date */
>         xfd_update_state(fpu->fpstate);
>         pagefault_disable();
>         ret = __restore_fpregs_from_user(buf, fpu->fpstate->user_xfeatures,
> -                                        xrestore, fx_only);
> +                                        xrestore_mask, fx_only);
>         pagefault_enable();
>
>         if (unlikely(ret)) {
> @@ -324,7 +324,7 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore, bool fx_onl
>         return true;
>  }
>
> -static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
> +static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx,
>                               bool ia32_fxstate)
>  {
>         struct task_struct *tsk = current;
> @@ -332,7 +332,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
>         struct user_i387_ia32_struct env;
>         bool success, fx_only = false;
>         union fpregs_state *fpregs;
> -       u64 user_xfeatures = 0;
> +       u64 xrestore_mask = 0;
>
>         if (use_xsave()) {
>                 struct _fpx_sw_bytes fx_sw_user;
> @@ -341,14 +341,14 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
>                         return false;
>
>                 fx_only = !fx_sw_user.magic1;
> -               user_xfeatures = fx_sw_user.xfeatures;
> +               xrestore_mask = fx_sw_user.xfeatures;
>         } else {
> -               user_xfeatures = XFEATURE_MASK_FPSSE;
> +               xrestore_mask = XFEATURE_MASK_FPSSE;
>         }
>
>         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, xrestore_mask, fx_only);
>         }
>
>         /*
> @@ -356,7 +356,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
>          * to be ignored for histerical raisins. The legacy state is folded
>          * in once the larger state has been copied.
>          */
> -       if (__copy_from_user(&env, buf, sizeof(env)))
> +       if (__copy_from_user(&env, buf_f, sizeof(env)))
>                 return false;
>
>         /*
> @@ -420,7 +420,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
>                  *
>                  * Preserve supervisor states!
>                  */
> -               u64 mask = user_xfeatures | xfeatures_mask_supervisor();
> +               u64 mask = xrestore_mask | xfeatures_mask_supervisor();
>
>                 fpregs->xsave.header.xfeatures &= mask;
>                 success = !os_xrstor_safe(fpu->fpstate,
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 03/10] x86/fpu: Split __fpu_restore_sig to extract compat path
  2026-06-15 19:37 ` [PATCH 03/10] x86/fpu: Split __fpu_restore_sig to extract compat path Andrei Vagin
@ 2026-06-26 17:20   ` Alexander Mikhalitsyn
  0 siblings, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 17:20 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:38 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> Split __fpu_restore_sig to move the restore part for the legacy/compat
> FPU state (when buf_f is present) to a separate helper function.
>
> The legacy 32-bit FP frame duplicates the FP state portion of the
> FX/XSAVE frame. For backward compatibility, the legacy FP frame is
> treated as the source of truth, and its state is folded into the
> FX/XSAVE state before restoring the registers.
>
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  arch/x86/kernel/fpu/signal.c | 50 +++++++++++++++++++++++++++---------
>  1 file changed, 38 insertions(+), 12 deletions(-)
>
> diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
> index 42c3d78bd849..6a14b528ac7f 100644
> --- a/arch/x86/kernel/fpu/signal.c
> +++ b/arch/x86/kernel/fpu/signal.c
> @@ -264,6 +264,9 @@ static int __restore_fpregs_from_user(void __user *buf, u64 task_xfeatures,
>         }
>  }
>
> +static bool restore_fpregs_from_user_compat(void __user *buf_f, void __user *buf_fx,
> +                                           u64 xrestore_mask, bool fx_only);
> +
>  /*
>   * Attempt to restore the FPU registers directly from user memory.
>   * Pagefaults are handled and any errors returned are fatal.
> @@ -324,14 +327,9 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, bool f
>         return true;
>  }
>
> -static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx,
> -                             bool ia32_fxstate)
> +static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx)
>  {
> -       struct task_struct *tsk = current;
> -       struct fpu *fpu = x86_task_fpu(tsk);
> -       struct user_i387_ia32_struct env;
> -       bool success, fx_only = false;
> -       union fpregs_state *fpregs;
> +       bool fx_only = false;
>         u64 xrestore_mask = 0;
>
>         if (use_xsave()) {
> @@ -346,11 +344,33 @@ static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx,
>                 xrestore_mask = XFEATURE_MASK_FPSSE;
>         }
>
> -       if (likely(!ia32_fxstate)) {
> +       if (likely(!buf_f)) {
>                 /* Restore the FPU registers directly from user memory. */
>                 return restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only);
>         }
>
> +       return restore_fpregs_from_user_compat(buf_f, buf_fx, xrestore_mask, fx_only);
> +}
> +
> +#if defined(CONFIG_X86_32) || defined(CONFIG_IA32_EMULATION)
> +/*
> + * Restore FPU state from a signal frame when a legacy 32-bit FP frame
> + * (buf_f) is present.
> + *
> + * The legacy FP frame duplicates the FP state portion of the FX/XSAVE
> + * frame (buf_fx). For backward compatibility, the legacy FP frame is
> + * treated as the source of truth, and its state is folded into the
> + * FX/XSAVE state before restoring the registers.
> + */
> +static bool restore_fpregs_from_user_compat(void __user *buf_f, void __user *buf_fx,
> +                                           u64 xrestore_mask, bool fx_only)
> +{
> +       struct task_struct *tsk = current;
> +       struct fpu *fpu = x86_task_fpu(tsk);
> +       struct user_i387_ia32_struct env;
> +       union fpregs_state *fpregs;
> +       bool success;
> +
>         /*
>          * Copy the legacy state because the FP portion of the FX frame has
>          * to be ignored for histerical raisins. The legacy state is folded
> @@ -435,6 +455,13 @@ static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx,
>         fpregs_unlock();
>         return success;
>  }
> +#else
> +static bool restore_fpregs_from_user_compat(void __user *buf_f, void __user *buf_fx,
> +                                                  u64 xrestore_mask, bool fx_only)
> +{
> +       return false;
> +}
> +#endif
>
>  static inline unsigned int xstate_sigframe_size(struct fpstate *fpstate)
>  {
> @@ -449,8 +476,7 @@ static inline unsigned int xstate_sigframe_size(struct fpstate *fpstate)
>  bool fpu__restore_sig(void __user *buf, int ia32_frame)
>  {
>         struct fpu *fpu = x86_task_fpu(current);
> -       void __user *buf_fx = buf;
> -       bool ia32_fxstate = false;
> +       void __user *buf_fx = buf, *buf_f = NULL;
>         bool success = false;
>         unsigned int size;
>
> @@ -471,7 +497,7 @@ bool fpu__restore_sig(void __user *buf, int ia32_frame)
>         if (ia32_frame && use_fxsr()) {
>                 buf_fx = buf + sizeof(struct fregs_state);
>                 size += sizeof(struct fregs_state);
> -               ia32_fxstate = true;
> +               buf_f = buf;
>         }
>
>         if (!access_ok(buf, size))
> @@ -482,7 +508,7 @@ bool fpu__restore_sig(void __user *buf, int ia32_frame)
>                                            sizeof(struct user_i387_ia32_struct),
>                                            NULL, buf);
>         } else {
> -               success = __fpu_restore_sig(buf, buf_fx, ia32_fxstate);
> +               success = __fpu_restore_sig(buf_f, buf_fx);
>         }
>
>  out:
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 07/10] x86/fpu: Pre-fault only required size of xstate buffer
  2026-06-15 19:37 ` [PATCH 07/10] x86/fpu: Pre-fault only required size of xstate buffer Andrei Vagin
@ 2026-06-26 17:28   ` Alexander Mikhalitsyn
  0 siblings, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 17:28 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:40 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> The kernel previously used the default task FPU state size (user_size)
> to fault in the user buffer when restoring FPU registers from a signal
> frame. This can lead to attempting to fault in memory past the end of
> the actual frame if the frame was smaller than the default size.
>
> Introduce consistency checks to calculate the actual required size for the
> features enabled in the xfeatures mask, ensure that the provided
> xstate_size is sufficient, and shrink it to the actual required size. Use
> this validated size to fault in the user buffer.
>
> Keep the strict check that the provided xstate_size does not exceed the
> default user_size for now.
>
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  arch/x86/kernel/fpu/signal.c | 40 ++++++++++++++++++++++++++++--------
>  arch/x86/kernel/fpu/xstate.c |  2 +-
>  arch/x86/kernel/fpu/xstate.h |  2 ++
>  3 files changed, 34 insertions(+), 10 deletions(-)
>
> diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
> index 85021c5ea649..1e7cc114c186 100644
> --- a/arch/x86/kernel/fpu/signal.c
> +++ b/arch/x86/kernel/fpu/signal.c
> @@ -29,7 +29,8 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
>  {
>         int min_xstate_size = sizeof(struct fxregs_state) +
>                               sizeof(struct xstate_header);
> -       void __user *fpstate = buf_fx;
> +       struct fpstate *fpstate = x86_task_fpu(current)->fpstate;
> +       void __user *buf = buf_fx;
>         unsigned int magic2;
>
>         if (__copy_from_user(fx_sw, &buf_fx->sw_reserved[0], sizeof(*fx_sw)))
> @@ -38,8 +39,9 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
>         /* 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)
> +           fx_sw->xstate_size > fpstate->user_size ||
> +           fx_sw->xstate_size > fx_sw->extended_size ||
> +           fx_sw->extended_size - fx_sw->xstate_size < FP_XSTATE_MAGIC2_SIZE)
>                 goto err_setfx;
>
>         /*
> @@ -48,11 +50,27 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
>          * 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 *)(buf + fx_sw->xstate_size)))
>                 return false;
> +       if (unlikely(magic2 != FP_XSTATE_MAGIC2))
> +               goto err_setfx;
>
> -       if (likely(magic2 == FP_XSTATE_MAGIC2))
> -               return true;
> +       if (fx_sw->xstate_size != fpstate->user_size ||
> +           fx_sw->xfeatures != fpstate->user_xfeatures) {
> +               unsigned int xsize;
> +               u64 xfeatures;
> +
> +               /* Calculate size of enabled features only. */
> +               xfeatures = fx_sw->xfeatures & fpstate->user_xfeatures;
> +
> +               xsize = xstate_calculate_size(xfeatures, false);
> +               if (fx_sw->xstate_size < xsize)
> +                       return false;
> +
> +               fx_sw->xstate_size = xsize;
> +       }
> +
> +       return true;
>  err_setfx:
>         /*
>          * The fallback to FX-only state is used to preserve backward
> @@ -279,7 +297,8 @@ static bool restore_fpregs_from_user_compat(void __user *buf_f, void __user *buf
>   * 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_mask, bool fx_only)
> +static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask,
> +                                    bool fx_only, size_t xstate_size)
>  {
>         struct fpu *fpu = x86_task_fpu(current);
>         int ret;
> @@ -313,7 +332,7 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, bool f
>                 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;
>         }
> @@ -339,6 +358,7 @@ static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx)
>  {
>         bool fx_only = false;
>         u64 xrestore_mask = 0;
> +       size_t xstate_size;
>
>         if (use_xsave()) {
>                 struct _fpx_sw_bytes fx_sw_user;
> @@ -348,13 +368,15 @@ static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx)
>
>                 fx_only = !fx_sw_user.magic1;
>                 xrestore_mask = fx_sw_user.xfeatures;
> +               xstate_size = fx_sw_user.xstate_size;
>         } else {
>                 xrestore_mask = XFEATURE_MASK_FPSSE;
> +               xstate_size = sizeof(struct fxregs_state);
>         }
>
>         if (likely(!buf_f)) {
>                 /* Restore the FPU registers directly from user memory. */
> -               return restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only);
> +               return restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only, xstate_size);
>         }
>
>         return restore_fpregs_from_user_compat(buf_f, buf_fx, xrestore_mask, fx_only);
> diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c
> index ed39d7051d0d..b7d0d78d2081 100644
> --- a/arch/x86/kernel/fpu/xstate.c
> +++ b/arch/x86/kernel/fpu/xstate.c
> @@ -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, offset, i;
>
> diff --git a/arch/x86/kernel/fpu/xstate.h b/arch/x86/kernel/fpu/xstate.h
> index 38a2862f09d3..c73cf2444de6 100644
> --- a/arch/x86/kernel/fpu/xstate.h
> +++ b/arch/x86/kernel/fpu/xstate.h
> @@ -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)
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size
  2026-06-15 19:37 ` [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size Andrei Vagin
@ 2026-06-26 17:32   ` Alexander Mikhalitsyn
  2026-06-30 19:23   ` Chang S. Bae
  1 sibling, 0 replies; 26+ messages in thread
From: Alexander Mikhalitsyn @ 2026-06-26 17:32 UTC (permalink / raw)
  To: Andrei Vagin
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae,
	linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

Am Mo., 15. Juni 2026 um 21:41 Uhr schrieb Andrei Vagin <avagin@google.com>:
>
> The kernel previously enforced that the xstate_size in the signal frame
> must not exceed the current task's fpstate->user_size. This prevents
> restoring signal frames that were saved on another CPU (in case of
> container/process migration) with a different (larger) set of enabled
> xstate features, even if the features to be restored are compatible.
>
> Relax this restriction by removing the strict check against user_size.
> The previous commit introduced infrastructure to calculate the actual
> required size based on the intersection of requested and supported
> features.  We now rely on that validation and only require that the
> provided xstate_size is sufficient for the active features.
>
> Signed-off-by: Andrei Vagin <avagin@google.com>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>

> ---
>  arch/x86/kernel/fpu/signal.c | 13 +++++++++++--
>  1 file changed, 11 insertions(+), 2 deletions(-)
>
> diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
> index 1e7cc114c186..083f03d2d002 100644
> --- a/arch/x86/kernel/fpu/signal.c
> +++ b/arch/x86/kernel/fpu/signal.c
> @@ -36,14 +36,23 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx,
>         if (__copy_from_user(fx_sw, &buf_fx->sw_reserved[0], sizeof(*fx_sw)))
>                 return false;
>
> -       /* Check for the first magic field and other error scenarios. */
> +       /* Check for the first magic field and other error scenarios.
> +        *
> +        * Do not enforce that fx_sw->xstate_size matches the task's
> +        * fpstate->user_size. The frame could be saved on another CPU with a
> +        * different set of xtate features. The actual set of used features is
> +        * defined in the xsave header. If the buffer contains any unsupported
> +        * feature states, it will be rejected.
> +        */
>         if (fx_sw->magic1 != FP_XSTATE_MAGIC1 ||
>             fx_sw->xstate_size < min_xstate_size ||
> -           fx_sw->xstate_size > fpstate->user_size ||
>             fx_sw->xstate_size > fx_sw->extended_size ||
>             fx_sw->extended_size - fx_sw->xstate_size < FP_XSTATE_MAGIC2_SIZE)
>                 goto err_setfx;
>
> +       if (!access_ok(buf_fx, fx_sw->extended_size))
> +               goto err_setfx;
> +
>         /*
>          * Check for the presence of second magic word at the end of memory
>          * layout. This detects the case where the user just copied the legacy
> --
> 2.54.0.1189.g8c84645362-goog
>
>

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 01/10] x86/fpu: Document signal frame portability
  2026-06-15 19:37 ` [PATCH 01/10] x86/fpu: Document " Andrei Vagin
  2026-06-26 13:51   ` Alexander Mikhalitsyn
@ 2026-06-30 19:23   ` Chang S. Bae
  1 sibling, 0 replies; 26+ messages in thread
From: Chang S. Bae @ 2026-06-30 19:23 UTC (permalink / raw)
  To: Andrei Vagin, Thomas Gleixner, Ingo Molnar, Borislav Petkov
  Cc: linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

On 6/15/2026 12:37 PM, Andrei Vagin wrote:
> +
> +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.


For XSTATE, I think the kernel is constrainted by the XSAVE architecture 
because it adopts the hardware-defined format directly.

This statement was once true before APX. With APX, however, an XSAVE 
image from older Intel MPX systems cannot be migrated directly to a 
newer Intel system where that region has been repurposed for APX.

Historically, newer XSTATE components happened to be introduced at 
higher offsets, and the kernel once had that assumption. However, that 
isn't an architectural guarantee ever. Commit 031b33ef1a6a 
("x86/fpu/xstate: Remove xstate offset check") clarified that fact 
before APX.

Similarly, although AMX significantly increased the XSAVE size, it is 
also impractical to assume the XSAVE image can continue growing 
indefinitely given the context-switching overheads.

If higher offsets were a strict architectural requirement, every future 
state would effectively need to become another XFD-backed dynamic state, 
which would also mandate larger frame size regardless of its actual use.

Given that all, repurposing deprecated region for new states seems to be 
an option in the architecture evolution. If so, the doc may want to 
clarify that the portability is constrained by the architectural XSAVE 
layout (even within same vendor).

Thanks,
Chang

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 04/10] x86/fpu: Document reasoning of FX-only fallback
  2026-06-15 19:37 ` [PATCH 04/10] x86/fpu: Document reasoning of FX-only fallback Andrei Vagin
  2026-06-26 14:22   ` Alexander Mikhalitsyn
@ 2026-06-30 19:23   ` Chang S. Bae
  1 sibling, 0 replies; 26+ messages in thread
From: Chang S. Bae @ 2026-06-30 19:23 UTC (permalink / raw)
  To: Andrei Vagin, Thomas Gleixner, Ingo Molnar, Borislav Petkov
  Cc: linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

On 6/15/2026 12:37 PM, Andrei Vagin wrote:
> 
> This fallback is dangerous as it can trigger silent corruptions of
> user-space state by resetting extended registers if the process was
> using them but the frame metadata was malformed.
> 
> XSAVE was introduced 15 years ago, we may need to consider removing this
> fallback entirely or introducing a sysctl to enable/disable it.

This reads more like a separate problem statement. I don't see any 
follow-up implementing either option here.

Without additional context, it isn't entirely clear to me why this 
should be in this changelog at least.

Thanks,
Chang

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 05/10] x86/fpu: Fix potential underflow in xstate_calculate_size()
  2026-06-15 19:37 ` [PATCH 05/10] x86/fpu: Fix potential underflow in xstate_calculate_size() Andrei Vagin
  2026-06-26 14:32   ` Alexander Mikhalitsyn
@ 2026-06-30 19:23   ` Chang S. Bae
  1 sibling, 0 replies; 26+ messages in thread
From: Chang S. Bae @ 2026-06-30 19:23 UTC (permalink / raw)
  To: Andrei Vagin, Thomas Gleixner, Ingo Molnar, Borislav Petkov
  Cc: linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

On 6/15/2026 12:37 PM, Andrei Vagin wrote:
> xstate_calculate_size() calculates the size required for a given set of
> xfeatures. It determines the topmost feature by finding the most
> significant bit in xfeatures using fls64(xfeatures) - 1.
> 
> If xfeatures is 0, fls64(0) returns 0, and topmost becomes -1.
> Previously, topmost was unsigned int, so -1 underflowed to UINT_MAX.
> This caused the subsequent check `topmost <= XFEATURE_SSE` to fail, and
> the code proceeded to access xstate arrays using topmost (UINT_MAX) as
> an index, leading to an out-of-bounds access.
> 
> Fix this by checking if xfeatures only contains legacy features (FP/SSE)
> or is empty (xfeatures <= XFEATURE_MASK_FPSSE) before calculating
> topmost.

Just a nit: this appears to prepare for the changes in patch7. If so, it 
may make sense to move it just before that?

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size
  2026-06-15 19:37 ` [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size Andrei Vagin
  2026-06-26 17:32   ` Alexander Mikhalitsyn
@ 2026-06-30 19:23   ` Chang S. Bae
  2026-07-01  0:48     ` Andrei Vagin
  1 sibling, 1 reply; 26+ messages in thread
From: Chang S. Bae @ 2026-06-30 19:23 UTC (permalink / raw)
  To: Andrei Vagin, Thomas Gleixner, Ingo Molnar, Borislav Petkov
  Cc: linux-kernel, criu, Dave Hansen, x86, H. Peter Anvin

On 6/15/2026 12:37 PM, Andrei Vagin wrote:
> The kernel previously enforced that the xstate_size in the signal frame
> must not exceed the current task's fpstate->user_size. This prevents
> restoring signal frames that were saved on another CPU (in case of
> container/process migration) with a different (larger) set of enabled
> xstate features, even if the features to be restored are compatible.
> 
> Relax this restriction by removing the strict check against user_size.
> The previous commit introduced infrastructure to calculate the actual
> required size based on the intersection of requested and supported
> features.  We now rely on that validation and only require that the
> provided xstate_size is sufficient for the active features.

I appreciate the effort to document the contract, add regression tests, 
and tighten the validation logic along with the revert fix so far in 
this series.

But I'm wondering this bit of relaxing the checker is really necessary 
at this point.

With APX, userspace can no longer assume that a higher XSTATE component 
number implies a higher offset within the XSAVE image. Going forward, 
migration software will likely need a more robust approach that 
interprets the layout and transforms the image when moving between 
machines with different layouts. With such translation, maybe further 
relaxing the kernel-side checker isn't that needed.

Thanks,
Chang

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size
  2026-06-30 19:23   ` Chang S. Bae
@ 2026-07-01  0:48     ` Andrei Vagin
  0 siblings, 0 replies; 26+ messages in thread
From: Andrei Vagin @ 2026-07-01  0:48 UTC (permalink / raw)
  To: Chang S. Bae
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, linux-kernel, criu,
	Dave Hansen, x86, H. Peter Anvin

On Tue, Jun 30, 2026 at 12:24 PM Chang S. Bae <chang.seok.bae@intel.com> wrote:
>
> On 6/15/2026 12:37 PM, Andrei Vagin wrote:
> > The kernel previously enforced that the xstate_size in the signal frame
> > must not exceed the current task's fpstate->user_size. This prevents
> > restoring signal frames that were saved on another CPU (in case of
> > container/process migration) with a different (larger) set of enabled
> > xstate features, even if the features to be restored are compatible.
> >
> > Relax this restriction by removing the strict check against user_size.
> > The previous commit introduced infrastructure to calculate the actual
> > required size based on the intersection of requested and supported
> > features.  We now rely on that validation and only require that the
> > provided xstate_size is sufficient for the active features.
>
> I appreciate the effort to document the contract, add regression tests,
> and tighten the validation logic along with the revert fix so far in
> this series.
>
> But I'm wondering this bit of relaxing the checker is really necessary
> at this point.

Hi Chang,

Thanks for reviewing this.

In our previous discussion, I explained why the state translation
approach doesn't solve the problem: in-flight signal frames on user
stacks cannot be translated. When a process is checkpointed while
handling a signal, there is an in-flight signal frame residing directly
on the thread's user stack. There is no way for userspace tools to
reliably discover arbitrary in-flight signal frames embedded in stack
memory.

We want to support cases where processes using only cluster-wide
available features can be migrated from newer to older CPUs. If we
migrate from a newer CPU (larger default `user_size`) to an older CPU
(smaller `user_size`), enforcing `xstate_size <= fpstate->user_size` in
the kernel unconditionally rejects valid signal frames.

An fpu translation mechanism already exists in CRIU to restore current
per-thread FPU states, making it possible to migrate workloads between
different CPUs even today. But there is always a risk that a process is
migrated at the wrong moment (while running inside a signal handler).
Without this change, failing the size check on that in-flight stack
frame can trigger a state corruption.  This patch eliminates that risk.

>
> With APX, userspace can no longer assume that a higher XSTATE component
> number implies a higher offset within the XSAVE image. Going forward,
> migration software will likely need a more robust approach that
> interprets the layout and transforms the image when moving between
> machines with different layouts. With such translation, maybe further
> relaxing the kernel-side checker isn't that needed.

I think APX was designed to preserve backward compatibility cleanly. And
I don't think that we rely on the assumption that a higher XSTATE component
number implies a higher offset within the XSAVE image.
`xstate_calculate_size()` already finds the topmost feature by offset. The
only reason the kernel needs to know the required xstate size is to
correctly pre-fault the user memory buffer.

While APX reuses the MPX space in the xsave state, it introduces a new
feature bit to indicate the presence of its state, which is really what
matters. The actual register state that gets restored depends on
`task_xfeatures` and the header's `xstate_bv`. Any attempt by XRSTOR to
restore a header containing unsupported feature bits in xstate_bv
generates a GP fault. This cleanly traps in restore_fpregs_from_user()
and fails out, triggering a SIGSEGV.

I completely understand that some features may be deprecated in the
future, but I still believe that for non-deprecated features, component
offsets should be fixed across all CPUs within a vendor's family. If
this assumption is ever broken, even standard KVM live migration of
guest vCPUs would break.

Sorry if I missed something. Maybe you can give an example of when
this change would work against us?

Thanks,
Andrei

^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2026-07-01  0:48 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-15 19:37 [PATCH v3 0/10] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
2026-06-15 19:37 ` [PATCH 01/10] x86/fpu: Document " Andrei Vagin
2026-06-26 13:51   ` Alexander Mikhalitsyn
2026-06-30 19:23   ` Chang S. Bae
2026-06-15 19:37 ` [PATCH 02/10] x86/fpu: Clean up and rename variables in signal frame handling Andrei Vagin
2026-06-26 17:05   ` Alexander Mikhalitsyn
2026-06-15 19:37 ` [PATCH 03/10] x86/fpu: Split __fpu_restore_sig to extract compat path Andrei Vagin
2026-06-26 17:20   ` Alexander Mikhalitsyn
2026-06-15 19:37 ` [PATCH 04/10] x86/fpu: Document reasoning of FX-only fallback Andrei Vagin
2026-06-26 14:22   ` Alexander Mikhalitsyn
2026-06-30 19:23   ` Chang S. Bae
2026-06-15 19:37 ` [PATCH 05/10] x86/fpu: Fix potential underflow in xstate_calculate_size() Andrei Vagin
2026-06-26 14:32   ` Alexander Mikhalitsyn
2026-06-30 19:23   ` Chang S. Bae
2026-06-15 19:37 ` [PATCH 06/10] selftests/x86: Add a test for signal frame FPU portability Andrei Vagin
2026-06-26 14:39   ` Alexander Mikhalitsyn
2026-06-15 19:37 ` [PATCH 07/10] x86/fpu: Pre-fault only required size of xstate buffer Andrei Vagin
2026-06-26 17:28   ` Alexander Mikhalitsyn
2026-06-15 19:37 ` [PATCH 08/10] selftests/x86: Add a sigframe insufficient xstate_size test Andrei Vagin
2026-06-26 15:02   ` Alexander Mikhalitsyn
2026-06-15 19:37 ` [PATCH 09/10] x86/fpu: Allow restoring signal frames with larger xstate_size Andrei Vagin
2026-06-26 17:32   ` Alexander Mikhalitsyn
2026-06-30 19:23   ` Chang S. Bae
2026-07-01  0:48     ` Andrei Vagin
2026-06-15 19:37 ` [PATCH 10/10] selftests/x86: Check restoring FPU state " Andrei Vagin
2026-06-26 15:12   ` Alexander Mikhalitsyn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox