All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/5] x86/fpu: Restore and reinforce signal frame portability
@ 2026-05-26 20:50 Andrei Vagin
  2026-05-26 20:50 ` [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return" Andrei Vagin
                   ` (4 more replies)
  0 siblings, 5 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-05-26 20:50 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: linux-kernel, criu, Borislav Petkov, Dave Hansen, x86,
	Andrei Vagin, H. Peter Anvin, Chang S. Bae

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

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

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

Cc: Thomas Gleixner <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 (5):
  Revert "x86/fpu: Refine and simplify the magic number check during
    signal return"
  x86/fpu: Document signal frame portability
  selftests/x86: Add a test for signal frame portability
  x86/fpu: Add consistency check between xstate_size and xfeatures
  selftests/x86: Add a consistency test for signal frames

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

-- 
2.54.0.746.g67dd491aae-goog


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

* [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return"
  2026-05-26 20:50 [PATCH 0/5] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
@ 2026-05-26 20:50 ` Andrei Vagin
  2026-05-27 19:48   ` Sasha Levin
  2026-06-03 15:13   ` Sasha Levin
  2026-05-26 20:50 ` [PATCH 2/5] x86/fpu: Document signal frame portability Andrei Vagin
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-05-26 20:50 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: linux-kernel, criu, Borislav Petkov, Dave Hansen, x86,
	Andrei Vagin, H. Peter Anvin, Chang S. Bae, stable

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

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

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

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

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

Cc: stable@vger.kernel.org
Acked-by: Chang S. Bae <chang.seok.bae@intel.com>
Fixes: dc8aa31a7ac2 ("x86/fpu: Refine and simplify the magic number check during signal return")
Signed-off-by: Andrei Vagin <avagin@google.com>
---
 arch/x86/kernel/fpu/signal.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
index c3ec2512f2bb..20b638c507ca 100644
--- a/arch/x86/kernel/fpu/signal.c
+++ b/arch/x86/kernel/fpu/signal.c
@@ -27,14 +27,19 @@
 static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 					    struct _fpx_sw_bytes *fx_sw)
 {
+	int min_xstate_size = sizeof(struct fxregs_state) +
+			      sizeof(struct xstate_header);
 	void __user *fpstate = fxbuf;
 	unsigned int magic2;
 
 	if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw)))
 		return false;
 
-	/* Check for the first magic field */
-	if (fx_sw->magic1 != FP_XSTATE_MAGIC1)
+	/* Check for the first magic field and other error scenarios. */
+	if (fx_sw->magic1 != FP_XSTATE_MAGIC1 ||
+	    fx_sw->xstate_size < min_xstate_size ||
+	    fx_sw->xstate_size > x86_task_fpu(current)->fpstate->user_size ||
+	    fx_sw->xstate_size > fx_sw->extended_size)
 		goto setfx;
 
 	/*
@@ -43,7 +48,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 	 * fpstate layout with out copying the extended state information
 	 * in the memory layout.
 	 */
-	if (__get_user(magic2, (__u32 __user *)(fpstate + x86_task_fpu(current)->fpstate->user_size)))
+	if (__get_user(magic2, (__u32 __user *)(fpstate + fx_sw->xstate_size)))
 		return false;
 
 	if (likely(magic2 == FP_XSTATE_MAGIC2))
-- 
2.54.0.746.g67dd491aae-goog


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

* [PATCH 2/5] x86/fpu: Document signal frame portability
  2026-05-26 20:50 [PATCH 0/5] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
  2026-05-26 20:50 ` [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return" Andrei Vagin
@ 2026-05-26 20:50 ` Andrei Vagin
  2026-05-26 20:50 ` [PATCH 3/5] selftests/x86: Add a test for " Andrei Vagin
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-05-26 20:50 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: linux-kernel, criu, Borislav Petkov, Dave Hansen, x86,
	Andrei Vagin, H. Peter Anvin, Chang S. Bae

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      | 14 ++++++++++++--
 arch/x86/include/uapi/asm/sigcontext.h | 13 +++++++++++--
 2 files changed, 23 insertions(+), 4 deletions(-)

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


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

* [PATCH 3/5] selftests/x86: Add a test for signal frame portability
  2026-05-26 20:50 [PATCH 0/5] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
  2026-05-26 20:50 ` [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return" Andrei Vagin
  2026-05-26 20:50 ` [PATCH 2/5] x86/fpu: Document signal frame portability Andrei Vagin
@ 2026-05-26 20:50 ` Andrei Vagin
  2026-05-26 20:50 ` [PATCH 4/5] x86/fpu: Add consistency check between xstate_size and xfeatures Andrei Vagin
  2026-05-26 20:50 ` [PATCH 5/5] selftests/x86: Add a consistency test for signal frames Andrei Vagin
  4 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-05-26 20:50 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: linux-kernel, criu, Borislav Petkov, Dave Hansen, x86,
	Andrei Vagin, H. Peter Anvin, Chang S. Bae

Add a new selftest tools/testing/selftests/x86/sigframe_portability.c
that verifies that the kernel correctly restores the xstate context
even if the frame size has been manually reduced, as long as the
FP_XSTATE_MAGIC2 marker is correctly placed at the end of the
specified xstate_size.

This test simulates a scenario where a signal frame is created on a
system with fewer xstate features and restored on a system with more
features.

Signed-off-by: Andrei Vagin <avagin@google.com>
---
 tools/testing/selftests/x86/Makefile          |   5 +-
 .../selftests/x86/sigframe_portability.c      | 143 ++++++++++++++++++
 tools/testing/selftests/x86/xstate.c          |   5 -
 tools/testing/selftests/x86/xstate.h          |   7 +
 4 files changed, 154 insertions(+), 6 deletions(-)
 create mode 100644 tools/testing/selftests/x86/sigframe_portability.c

diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 434065215d12..bd59ee3df61d 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_portability
 # Some selftests require 32bit support enabled also on 64bit systems
 TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
 
@@ -138,3 +139,5 @@ $(OUTPUT)/avx_64: CFLAGS += -mno-avx -mno-avx512f
 $(OUTPUT)/amx_64: EXTRA_FILES += xstate.c
 $(OUTPUT)/avx_64: EXTRA_FILES += xstate.c
 $(OUTPUT)/apx_64: EXTRA_FILES += xstate.c
+
+$(OUTPUT)/sigframe_portability_64: CFLAGS += -mno-avx -mno-avx512f
diff --git a/tools/testing/selftests/x86/sigframe_portability.c b/tools/testing/selftests/x86/sigframe_portability.c
new file mode 100644
index 000000000000..8888079a153a
--- /dev/null
+++ b/tools/testing/selftests/x86/sigframe_portability.c
@@ -0,0 +1,143 @@
+// 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 portability of the signal frame.
+ * It simulates a scenario where a signal frame is created on a system with
+ * fewer xstate features and restored on a system with more features.
+ */
+
+#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 check_avx_support(void)
+{
+	struct xstate_info xstate;
+
+	/* Check maximum supported CPUID leaf; we need 0xd for XSAVE */
+	if (get_xbuf_size() == 0)
+		ksft_exit_skip("XSAVE not supported\n");
+
+	xstate = get_xstate_info(XFEATURE_YMM);
+	if (!xstate.mask)
+		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)
+
+static void handle_signal(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) {
+		snprintf(sig_err_buf, SIGNAL_BUF_LEN,
+			 "magic1 is not valid: %x expected %x",
+			 sw->magic1, FP_XSTATE_MAGIC1);
+		return;
+	}
+
+	xbuf = (struct xsave_buffer *)fp;
+
+	if (sw->xstate_size < xstate_size_ymm) {
+		snprintf(sig_err_buf, SIGNAL_BUF_LEN,
+			 "xstate size is too small: %d (expected %d or greater)",
+			 sw->xstate_size, xstate_size_ymm);
+		return;
+	}
+
+	sw->xstate_size = xstate_size_ymm;
+
+	xfeatures = get_xstatebv(xbuf);
+	xfeatures &= XFEATURE_MASK_FPSSE | (1 << XFEATURE_YMM);
+	set_xstatebv(xbuf, 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 read_ymm0(uint64_t *v)
+{
+	asm volatile ("vmovdqu %%ymm0, %0" : "=m"  (*(char (*)[32])v));
+}
+
+static void write_ymm0(uint64_t *v)
+{
+	asm volatile ("vmovdqu %0, %%ymm0" : : "m"  (*(char (*)[32])v));
+}
+
+int main(void)
+{
+	uint64_t v[4] = {0, 0, 0, 0};
+
+	ksft_print_header();
+	ksft_set_plan(1);
+
+	check_avx_support();
+
+	sethandler(SIGUSR1, handle_signal, 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\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);
+
+	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..b24501b621f3 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. */
-- 
2.54.0.746.g67dd491aae-goog


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

* [PATCH 4/5] x86/fpu: Add consistency check between xstate_size and xfeatures
  2026-05-26 20:50 [PATCH 0/5] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (2 preceding siblings ...)
  2026-05-26 20:50 ` [PATCH 3/5] selftests/x86: Add a test for " Andrei Vagin
@ 2026-05-26 20:50 ` Andrei Vagin
  2026-05-26 20:50 ` [PATCH 5/5] selftests/x86: Add a consistency test for signal frames Andrei Vagin
  4 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-05-26 20:50 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: linux-kernel, criu, Borislav Petkov, Dave Hansen, x86,
	Andrei Vagin, H. Peter Anvin, Chang S. Bae

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

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

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

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

diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
index 20b638c507ca..5baba4101f1d 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 *fxbuf,
 {
 	int min_xstate_size = sizeof(struct fxregs_state) +
 			      sizeof(struct xstate_header);
-	void __user *fpstate = fxbuf;
+	struct fpstate *fpstate = x86_task_fpu(current)->fpstate;
+	void __user *sig_fpstate = fxbuf;
 	unsigned int magic2;
 
 	if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw)))
@@ -37,8 +38,9 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 
 	/* Check for the first magic field and other error scenarios. */
 	if (fx_sw->magic1 != FP_XSTATE_MAGIC1 ||
+	    (fx_sw->xfeatures & ~fpstate->user_xfeatures) ||
 	    fx_sw->xstate_size < min_xstate_size ||
-	    fx_sw->xstate_size > x86_task_fpu(current)->fpstate->user_size ||
+	    fx_sw->xstate_size > fpstate->user_size ||
 	    fx_sw->xstate_size > fx_sw->extended_size)
 		goto setfx;
 
@@ -48,9 +50,17 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf,
 	 * fpstate layout with out copying the extended state information
 	 * in the memory layout.
 	 */
-	if (__get_user(magic2, (__u32 __user *)(fpstate + fx_sw->xstate_size)))
+	if (__get_user(magic2, (__u32 __user *)(sig_fpstate + fx_sw->xstate_size)))
 		return false;
 
+	if (fx_sw->xstate_size != fpstate->user_size) {
+		unsigned int calculated_size;
+
+		calculated_size = xstate_calculate_size(fx_sw->xfeatures, false);
+		if (fx_sw->xstate_size < calculated_size)
+			goto setfx;
+	}
+
 	if (likely(magic2 == FP_XSTATE_MAGIC2))
 		return true;
 setfx:
@@ -266,7 +276,8 @@ static int __restore_fpregs_from_user(void __user *buf, u64 ufeatures,
  * Attempt to restore the FPU registers directly from user memory.
  * Pagefaults are handled and any errors returned are fatal.
  */
-static bool restore_fpregs_from_user(void __user *buf, u64 xrestore, bool fx_only)
+static bool restore_fpregs_from_user(void __user *buf, u64 xrestore,
+				     bool fx_only, size_t xstate_size)
 {
 	struct fpu *fpu = x86_task_fpu(current);
 	int ret;
@@ -302,7 +313,7 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore, bool fx_onl
 		if (ret != X86_TRAP_PF)
 			return false;
 
-		if (!fault_in_readable(buf, fpu->fpstate->user_size))
+		if (!fault_in_readable(buf, xstate_size))
 			goto retry;
 		return false;
 	}
@@ -333,6 +344,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
 	bool success, fx_only = false;
 	union fpregs_state *fpregs;
 	u64 user_xfeatures = 0;
+	size_t xstate_size;
 
 	if (use_xsave()) {
 		struct _fpx_sw_bytes fx_sw_user;
@@ -342,13 +354,15 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx,
 
 		fx_only = !fx_sw_user.magic1;
 		user_xfeatures = fx_sw_user.xfeatures;
+		xstate_size = fx_sw_user.xstate_size;
 	} else {
 		user_xfeatures = XFEATURE_MASK_FPSSE;
+		xstate_size = sizeof(struct fxregs_state);
 	}
 
 	if (likely(!ia32_fxstate)) {
 		/* Restore the FPU registers directly from user memory. */
-		return restore_fpregs_from_user(buf_fx, user_xfeatures, fx_only);
+		return restore_fpregs_from_user(buf_fx, user_xfeatures, fx_only, xstate_size);
 	}
 
 	/*
diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c
index a7b6524a9dea..371a3a4a4b4e 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 = fls64(xfeatures) -  1;
 	unsigned int 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.746.g67dd491aae-goog


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

* [PATCH 5/5] selftests/x86: Add a consistency test for signal frames
  2026-05-26 20:50 [PATCH 0/5] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
                   ` (3 preceding siblings ...)
  2026-05-26 20:50 ` [PATCH 4/5] x86/fpu: Add consistency check between xstate_size and xfeatures Andrei Vagin
@ 2026-05-26 20:50 ` Andrei Vagin
  4 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-05-26 20:50 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: linux-kernel, criu, Borislav Petkov, Dave Hansen, x86,
	Andrei Vagin, H. Peter Anvin, Chang S. Bae

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

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

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

diff --git a/tools/testing/selftests/x86/sigframe_portability.c b/tools/testing/selftests/x86/sigframe_portability.c
index 8888079a153a..a60aa4d20dd1 100644
--- a/tools/testing/selftests/x86/sigframe_portability.c
+++ b/tools/testing/selftests/x86/sigframe_portability.c
@@ -17,13 +17,14 @@
 #include "xstate.h"
 
 /*
- * This test verifies the portability of the signal frame.
- * It simulates a scenario where a signal frame is created on a system with
- * fewer xstate features and restored on a system with more features.
+ * This test verifies the portability and consistency of the signal frame.
+ * Portability: A frame created on a system with fewer features can be
+ *              restored on a system with more features.
+ * Consistency: The kernel rejects frames where xstate_size is insufficient
+ *              for the features enabled in xfeatures.
  */
 
 #define SIGFRAME_XSTATE_HDR_OFFSET	512
-
 #define XSTATE_SSE_ONLY_SIZE	(SIGFRAME_XSTATE_HDR_OFFSET + XSAVE_HDR_SIZE)
 #define XFEATURE_MASK_FPSSE	((1 << XFEATURE_FP) | (1 << XFEATURE_SSE))
 
@@ -55,7 +56,17 @@ static void check_avx_support(void)
 
 #define TEST_YMMH_VAL (0x5656565656565656UL)
 
-static void handle_signal(int sig, siginfo_t *si, void *ucp)
+static void read_ymm0(uint64_t *v)
+{
+	asm volatile ("vmovdqu %%ymm0, %0" : "=m"  (*(char (*)[32])v));
+}
+
+static void write_ymm0(uint64_t *v)
+{
+	asm volatile ("vmovdqu %0, %%ymm0" : : "m"  (*(char (*)[32])v));
+}
+
+static void handle_portability(int sig, siginfo_t *si, void *ucp)
 {
 	ucontext_t *uc = ucp;
 	void *fp = uc->uc_mcontext.fpregs;
@@ -72,18 +83,14 @@ static void handle_signal(int sig, siginfo_t *si, void *ucp)
 
 	xbuf = (struct xsave_buffer *)fp;
 
-	if (sw->xstate_size < xstate_size_ymm) {
-		snprintf(sig_err_buf, SIGNAL_BUF_LEN,
-			 "xstate size is too small: %d (expected %d or greater)",
-			 sw->xstate_size, xstate_size_ymm);
-		return;
-	}
-
+	/* Shrink the frame to just YMM size */
 	sw->xstate_size = xstate_size_ymm;
 
 	xfeatures = get_xstatebv(xbuf);
 	xfeatures &= XFEATURE_MASK_FPSSE | (1 << XFEATURE_YMM);
 	set_xstatebv(xbuf, xfeatures);
+	/* Also update sw->xfeatures as the kernel relies on it */
+	set_fpx_sw_bytes_features(fp, xfeatures);
 
 	*(uint32_t *)(fp + sw->xstate_size) = FP_XSTATE_MAGIC2;
 
@@ -96,26 +103,12 @@ static void handle_signal(int sig, siginfo_t *si, void *ucp)
 		memset(fp + sw->xstate_size + 4, 0, sw->extended_size - sw->xstate_size - 4);
 }
 
-static void read_ymm0(uint64_t *v)
-{
-	asm volatile ("vmovdqu %%ymm0, %0" : "=m"  (*(char (*)[32])v));
-}
-
-static void write_ymm0(uint64_t *v)
-{
-	asm volatile ("vmovdqu %0, %%ymm0" : : "m"  (*(char (*)[32])v));
-}
-
-int main(void)
+static void test_portability(void)
 {
 	uint64_t v[4] = {0, 0, 0, 0};
 
-	ksft_print_header();
-	ksft_set_plan(1);
-
-	check_avx_support();
-
-	sethandler(SIGUSR1, handle_signal, 0);
+	sig_err_buf[0] = 0;
+	sethandler(SIGUSR1, handle_portability, 0);
 
 	v[0] = 0x1111111111111111ULL;
 	v[1] = 0x2222222222222222ULL;
@@ -130,13 +123,66 @@ int main(void)
 	if (sig_err_buf[0])
 		ksft_test_result_fail("%s\n", sig_err_buf);
 	else if (v[2] == TEST_YMMH_VAL && v[3] == (TEST_YMMH_VAL + 1))
-		ksft_test_result_pass("YMM state restored correctly\n");
+		ksft_test_result_pass("YMM state restored correctly from shrunk frame\n");
 	else
 		ksft_test_result_fail(
 				"Got upper bits: 0x%lx 0x%lx (expected %lx %lx)\n",
 			       v[2], v[3], TEST_YMMH_VAL, TEST_YMMH_VAL + 1);
 
 	clearhandler(SIGUSR1);
+}
+
+static void handle_consistency(int sig, siginfo_t *si, void *ucp)
+{
+	ucontext_t *uc = ucp;
+	void *fp = uc->uc_mcontext.fpregs;
+	struct _fpx_sw_bytes *sw = get_fpx_sw_bytes(fp);
+
+	/* The origin frame contains an AVX state. */
+	sw->xstate_size = XSTATE_SSE_ONLY_SIZE;
+
+	*(uint32_t *)(fp + sw->xstate_size) = FP_XSTATE_MAGIC2;
+}
+
+static void test_consistency(void)
+{
+	uint64_t v[4] = {0, 0, 0, 0};
+
+	sig_err_buf[0] = 0;
+	sethandler(SIGUSR2, handle_consistency, 0);
+
+	v[0] = 0x1111111111111111ULL;
+	v[1] = 0x2222222222222222ULL;
+	v[2] = 0x3333333333333333ULL;
+	v[3] = 0x4444444444444444ULL;
+	write_ymm0(v);
+
+	raise(SIGUSR2);
+	v[0] = v[1] = v[2] = v[3] = 0;
+	read_ymm0(v);
+
+	/*
+	 * When inconsistent, the kernel should have fallen back to
+	 * FX-only mode, so YMM upper bits should be zero (init state).
+	 */
+	if (v[2] == 0 && v[3] == 0)
+		ksft_test_result_pass("Inconsistent size correctly rejected\n");
+	else
+		ksft_test_result_fail("Inconsistent size was NOT rejected: 0x%lx 0x%lx\n",
+				      v[2], v[3]);
+
+	clearhandler(SIGUSR2);
+}
+
+int main(void)
+{
+	ksft_print_header();
+	ksft_set_plan(2);
+
+	check_avx_support();
+
+	test_portability();
+	test_consistency();
 
 	ksft_finished();
 	return 0;
diff --git a/tools/testing/selftests/x86/xstate.h b/tools/testing/selftests/x86/xstate.h
index b24501b621f3..c531667b66ad 100644
--- a/tools/testing/selftests/x86/xstate.h
+++ b/tools/testing/selftests/x86/xstate.h
@@ -182,6 +182,11 @@ static inline uint64_t get_fpx_sw_bytes_features(void *buffer)
 	return *(uint64_t *)(buffer + SW_BYTES_BV_OFFSET);
 }
 
+static inline void set_fpx_sw_bytes_features(void *buffer, uint64_t features)
+{
+	*(uint64_t *)(buffer + SW_BYTES_BV_OFFSET) = features;
+}
+
 static inline void set_rand_data(struct xstate_info *xstate, struct xsave_buffer *xbuf)
 {
 	int *ptr = (int *)&xbuf->bytes[xstate->xbuf_offset];
-- 
2.54.0.746.g67dd491aae-goog


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

* Re: [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return"
  2026-05-26 20:50 ` [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return" Andrei Vagin
@ 2026-05-27 19:48   ` Sasha Levin
  2026-05-28 17:08     ` Andrei Vagin
  2026-06-03 15:13   ` Sasha Levin
  1 sibling, 1 reply; 9+ messages in thread
From: Sasha Levin @ 2026-05-27 19:48 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: Sasha Levin, linux-kernel, criu, Borislav Petkov, Dave Hansen,
	x86, Andrei Vagin, H. Peter Anvin, Chang S. Bae, stable

> This reverts commit dc8aa31a7ac2 ("x86/fpu: Refine and simplify the
> magic number check during signal return").
>
> The reverted commit broke applications that construct signal frames in
> userspace (such as CRIU and gVisor) if the frame's xstate size is
> smaller than the kernel's fpstate->user_size.

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

--
Thanks,
Sasha

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

* Re: [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return"
  2026-05-27 19:48   ` Sasha Levin
@ 2026-05-28 17:08     ` Andrei Vagin
  0 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-05-28 17:08 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: linux-kernel, criu, Sasha Levin, Borislav Petkov, Dave Hansen,
	x86, Andrei Vagin, H. Peter Anvin, Chang S. Bae, stable

On Wed, May 27, 2026 at 12:49 PM Sasha Levin <sashal@kernel.org> wrote:
>
> > This reverts commit dc8aa31a7ac2 ("x86/fpu: Refine and simplify the
> > magic number check during signal return").
> >
> > The reverted commit broke applications that construct signal frames in
> > userspace (such as CRIU and gVisor) if the frame's xstate size is
> > smaller than the kernel's fpstate->user_size.
>
> Holding this off on the stable side until the revert (and the rest of
> the series) lands in mainline. Once it's upstream, please ping with the
> mainline SHAs and the list of trees you want it on, and I'll queue it.

Will do. Thanks.

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

Thanks,
Andrei

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

* Re: [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return"
  2026-05-26 20:50 ` [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return" Andrei Vagin
  2026-05-27 19:48   ` Sasha Levin
@ 2026-06-03 15:13   ` Sasha Levin
  1 sibling, 0 replies; 9+ messages in thread
From: Sasha Levin @ 2026-06-03 15:13 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar
  Cc: Sasha Levin, linux-kernel, criu, Borislav Petkov, Dave Hansen,
	x86, Andrei Vagin, H. Peter Anvin, Chang S. Bae, stable

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

-- 
Thanks,
Sasha

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

end of thread, other threads:[~2026-06-03 15:14 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-26 20:50 [PATCH 0/5] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
2026-05-26 20:50 ` [PATCH 1/5] Revert "x86/fpu: Refine and simplify the magic number check during signal return" Andrei Vagin
2026-05-27 19:48   ` Sasha Levin
2026-05-28 17:08     ` Andrei Vagin
2026-06-03 15:13   ` Sasha Levin
2026-05-26 20:50 ` [PATCH 2/5] x86/fpu: Document signal frame portability Andrei Vagin
2026-05-26 20:50 ` [PATCH 3/5] selftests/x86: Add a test for " Andrei Vagin
2026-05-26 20:50 ` [PATCH 4/5] x86/fpu: Add consistency check between xstate_size and xfeatures Andrei Vagin
2026-05-26 20:50 ` [PATCH 5/5] selftests/x86: Add a consistency test for signal frames Andrei Vagin

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.