[PATCH v4 1/2] x86/fpu: Fix memory leak with dynamic fpstate and exec()

From: Guixiong Wei

Date: Mon Nov 02 2026 - 05:36:01 EST


Using an XFD-controlled xfeature can allocate a larger fpstate. On
exec(), fpstate_reset() replaces fpu->fpstate with the embedded buffer
without freeing the dynamic allocation. Once the pointer is overwritten,
the allocation cannot be freed on task exit.

Free dynamic state at the start of fpstate_reset(). In fpu_clone(),
clear the new task's fpstate pointer before resetting its state, so the
parent's allocation is not freed.

fpstate_reset() may now free the current task's buffer. fpu_flush_thread()
calls it before fpu_reset_fpstate_regs() sets TIF_NEED_FPU_LOAD. If a
context switch or a kernel FPU user happens in that window, the register
state still looks live and gets saved into the freed buffer. Set
TIF_NEED_FPU_LOAD before the reset so that nothing saves the hardware
state until it is reloaded from the reset in-memory state.

Fixes: 500afbf645a0 ("x86/fpu/xstate: Add fpstate_realloc()/free()")
Suggested-by: Chang S. Bae <chang.seok.bae@xxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Guixiong Wei <weiguixiong@xxxxxxxxxxxxx>
---
arch/x86/kernel/fpu/core.c | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/arch/x86/kernel/fpu/core.c b/arch/x86/kernel/fpu/core.c
index d1aeecd57f5ed..af1c1688d69bf 100644
--- a/arch/x86/kernel/fpu/core.c
+++ b/arch/x86/kernel/fpu/core.c
@@ -592,8 +592,15 @@ static void __fpstate_reset(struct fpstate *fpstate)
fpstate->user_xfeatures = fpu_user_cfg.default_features;
}

+/*
+ * Callers resetting the current task must ensure that its FPU register
+ * state isn't saved on context switch as the buffer may be freed here.
+ */
void fpstate_reset(struct fpu *fpu)
{
+ /* Free dynamic fpstate (if any) before resetting pointer: */
+ fpstate_free(fpu);
+
/* Set the fpstate pointer to the default fpstate */
fpu->fpstate = &fpu->__fpstate;
__fpstate_reset(fpu->fpstate);
@@ -670,6 +677,8 @@ int fpu_clone(struct task_struct *dst, u64 clone_flags, bool minimal,
/* The new task's FPU state cannot be valid in the hardware. */
dst_fpu->last_cpu = -1;

+ /* Break the link to the other FPU's fpstate: */
+ dst_fpu->fpstate = NULL;
fpstate_reset(dst_fpu);

if (!cpu_feature_enabled(X86_FEATURE_FPU))
@@ -866,6 +875,9 @@ void fpu__clear_user_states(struct fpu *fpu)

void fpu_flush_thread(void)
{
+ /* Prevent the hardware state from being saved on context switch */
+ set_thread_flag(TIF_NEED_FPU_LOAD);
+
fpstate_reset(x86_task_fpu(current));
fpu_reset_fpstate_regs();
}

base-commit: 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e
--
2.55.0