* [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability
@ 2026-08-17 4:20 Andrei Vagin
2026-08-17 4:20 ` [PATCH 1/8] x86/fpu: Document " Andrei Vagin
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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.
v4:
- Update documentation to describe architectural XSAVE layout
constraints and feature repurposing (e.g. MPX vs APX).
- The patch "x86/fpu: Allow restoring signal frames with larger
xstate_size" will be sent in a separate series.
- Address sashiko comments.
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: Alexander Mikhalitsyn <alexander@mihalicyn.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: "Chang S. Bae" <chang.seok.bae@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Thomas Gleixner <tglx@kernel.org>
Andrei Vagin (8):
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
selftests/x86: Add a test for signal frame FPU portability
x86/fpu: Fix potential underflow in xstate_calculate_size()
x86/fpu: Pre-fault only required size of xstate buffer
selftests/x86: Add a sigframe insufficient xstate_size test
Documentation/arch/x86/xstate.rst | 16 ++
arch/x86/include/uapi/asm/sigcontext.h | 17 ++
arch/x86/kernel/fpu/signal.c | 130 ++++++++---
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 | 229 ++++++++++++++++++
tools/testing/selftests/x86/xstate.c | 5 -
tools/testing/selftests/x86/xstate.h | 12 +
9 files changed, 378 insertions(+), 47 deletions(-)
create mode 100644 tools/testing/selftests/x86/sigframe_fpu_portability.c
--
2.54.0.1189.g8c84645362-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 1/8] x86/fpu: Document signal frame portability
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
@ 2026-08-17 4:20 ` Andrei Vagin
2026-08-17 4:20 ` [PATCH 2/8] x86/fpu: Clean up and rename variables in signal frame handling Andrei Vagin
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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 uapi 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 constrained by the architectural XSAVE layout
(component offsets and sizes); the destination machine must share
matching component layouts for all features present in the frame. While
layouts are consistent across CPUs from the same vendor for active
features, differences can occur across vendors or if the XSAVE space of
a deprecated feature (e.g. MPX) is repurposed for a newer feature
(e.g. APX).
Reviewed-by: Alexander Mikhalitsyn <alexander@mihalicyn.com>
Signed-off-by: Andrei Vagin <avagin@google.com>
---
Documentation/arch/x86/xstate.rst | 16 ++++++++++++++++
arch/x86/include/uapi/asm/sigcontext.h | 17 +++++++++++++++++
2 files changed, 33 insertions(+)
diff --git a/Documentation/arch/x86/xstate.rst b/Documentation/arch/x86/xstate.rst
index cec05ac464c1..0b4d541ab6f8 100644
--- a/Documentation/arch/x86/xstate.rst
+++ b/Documentation/arch/x86/xstate.rst
@@ -172,3 +172,19 @@ 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 constrained by the architectural XSAVE
+layout. Restoration is supported only if the destination host supports all
+features present in the frame and uses matching component offsets and sizes for
+them. While layout compatibility is generally maintained across CPUs from the
+same vendor, differences can occur across vendors or if the XSAVE space of a
+deprecated feature (e.g. MPX) is repurposed for a newer feature (e.g. APX).
diff --git a/arch/x86/include/uapi/asm/sigcontext.h b/arch/x86/include/uapi/asm/sigcontext.h
index d0d9b331d3a1..c01f55f1fc12 100644
--- a/arch/x86/include/uapi/asm/sigcontext.h
+++ b/arch/x86/include/uapi/asm/sigcontext.h
@@ -34,6 +34,23 @@
* 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 512-byte FXSAVE area and the 64-byte XSAVE header
+ * struct _header). This size is used in conjunction with the pointer to
+ * the xstate context to locate FP_XSTATE_MAGIC2. Note that in 32-bit signal
+ * frames (including 32-bit compat tasks on 64-bit kernels), the fpstate
+ * pointer points to a legacy 112-byte FPU environment (struct _fpstate_32)
+ * 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 destination supports all features present in the frame
+ * and shares matching XSAVE component offsets and sizes for those features.
+ * Note that portability is constrained by the architectural XSAVE layout
+ * and is not guaranteed across different vendors or if space from
+ * deprecated features (e.g. MPX) is repurposed for newer features
+ * (e.g. APX).
+ *
* This extended area typically grows with newer CPUs that have larger and
* larger XSAVE areas.
*/
--
2.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 2/8] x86/fpu: Clean up and rename variables in signal frame handling
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
2026-08-17 4:20 ` [PATCH 1/8] x86/fpu: Document " Andrei Vagin
@ 2026-08-17 4:20 ` Andrei Vagin
2026-08-17 4:20 ` [PATCH 3/8] x86/fpu: Split __fpu_restore_sig to extract compat path Andrei Vagin
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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>
Reviewed-by: Alexander Mikhalitsyn <alexander@mihalicyn.com>
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.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/8] x86/fpu: Split __fpu_restore_sig to extract compat path
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
2026-08-17 4:20 ` [PATCH 1/8] x86/fpu: Document " Andrei Vagin
2026-08-17 4:20 ` [PATCH 2/8] x86/fpu: Clean up and rename variables in signal frame handling Andrei Vagin
@ 2026-08-17 4:20 ` Andrei Vagin
2026-08-17 4:20 ` [PATCH 4/8] x86/fpu: Document reasoning of FX-only fallback Andrei Vagin
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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.
Reviewed-by: Alexander Mikhalitsyn <alexander@mihalicyn.com>
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.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 4/8] x86/fpu: Document reasoning of FX-only fallback
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
` (2 preceding siblings ...)
2026-08-17 4:20 ` [PATCH 3/8] x86/fpu: Split __fpu_restore_sig to extract compat path Andrei Vagin
@ 2026-08-17 4:20 ` Andrei Vagin
2026-08-17 4:20 ` [PATCH 5/8] selftests/x86: Add a test for signal frame FPU portability Andrei Vagin
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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.
Reviewed-by: Alexander Mikhalitsyn <alexander@mihalicyn.com>
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.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 5/8] selftests/x86: Add a test for signal frame FPU portability
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
` (3 preceding siblings ...)
2026-08-17 4:20 ` [PATCH 4/8] x86/fpu: Document reasoning of FX-only fallback Andrei Vagin
@ 2026-08-17 4:20 ` Andrei Vagin
2026-08-17 4:20 ` [PATCH 6/8] x86/fpu: Fix potential underflow in xstate_calculate_size() Andrei Vagin
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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.
Reviewed-by: Alexander Mikhalitsyn <alexander@mihalicyn.com>
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.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 6/8] x86/fpu: Fix potential underflow in xstate_calculate_size()
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
` (4 preceding siblings ...)
2026-08-17 4:20 ` [PATCH 5/8] selftests/x86: Add a test for signal frame FPU portability Andrei Vagin
@ 2026-08-17 4:20 ` Andrei Vagin
2026-08-17 4:20 ` [PATCH 7/8] x86/fpu: Pre-fault only required size of xstate buffer Andrei Vagin
2026-08-17 4:20 ` [PATCH 8/8] selftests/x86: Add a sigframe insufficient xstate_size test Andrei Vagin
7 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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")
Reviewed-by: Alexander Mikhalitsyn <alexander@mihalicyn.com>
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.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 7/8] x86/fpu: Pre-fault only required size of xstate buffer
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
` (5 preceding siblings ...)
2026-08-17 4:20 ` [PATCH 6/8] x86/fpu: Fix potential underflow in xstate_calculate_size() Andrei Vagin
@ 2026-08-17 4:20 ` Andrei Vagin
2026-08-17 4:20 ` [PATCH 8/8] selftests/x86: Add a sigframe insufficient xstate_size test Andrei Vagin
7 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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.
Reviewed-by: Alexander Mikhalitsyn <alexander@mihalicyn.com>
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.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 8/8] selftests/x86: Add a sigframe insufficient xstate_size test
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
` (6 preceding siblings ...)
2026-08-17 4:20 ` [PATCH 7/8] x86/fpu: Pre-fault only required size of xstate buffer Andrei Vagin
@ 2026-08-17 4:20 ` Andrei Vagin
7 siblings, 0 replies; 9+ messages in thread
From: Andrei Vagin @ 2026-08-17 4:20 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Chang S. Bae
Cc: linux-kernel, criu, Dave Hansen, x86, Andrei Vagin,
Alexander Mikhalitsyn, 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.
Reviewed-by: Alexander Mikhalitsyn <alexander@mihalicyn.com>
Signed-off-by: Andrei Vagin <avagin@google.com>
---
.../selftests/x86/sigframe_fpu_portability.c | 79 +++++++++++++++++--
1 file changed, 73 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..59d5c6049892 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,77 @@ 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;
+
+ if (!fp) {
+ sig_print("fpregs is NULL\n");
+ return;
+ }
+
+ sw = get_fpx_sw_bytes(fp);
+ if (sw->magic1 != FP_XSTATE_MAGIC1) {
+ sig_print("magic1 is not valid\n");
+ return;
+ }
+
+ /* 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.55.0.691.gc56d675ccc-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-08-17 4:21 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-17 4:20 [PATCH v4 0/8] x86/fpu: Restore and reinforce signal frame portability Andrei Vagin
2026-08-17 4:20 ` [PATCH 1/8] x86/fpu: Document " Andrei Vagin
2026-08-17 4:20 ` [PATCH 2/8] x86/fpu: Clean up and rename variables in signal frame handling Andrei Vagin
2026-08-17 4:20 ` [PATCH 3/8] x86/fpu: Split __fpu_restore_sig to extract compat path Andrei Vagin
2026-08-17 4:20 ` [PATCH 4/8] x86/fpu: Document reasoning of FX-only fallback Andrei Vagin
2026-08-17 4:20 ` [PATCH 5/8] selftests/x86: Add a test for signal frame FPU portability Andrei Vagin
2026-08-17 4:20 ` [PATCH 6/8] x86/fpu: Fix potential underflow in xstate_calculate_size() Andrei Vagin
2026-08-17 4:20 ` [PATCH 7/8] x86/fpu: Pre-fault only required size of xstate buffer Andrei Vagin
2026-08-17 4:20 ` [PATCH 8/8] selftests/x86: Add a sigframe insufficient xstate_size test Andrei Vagin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®