Re: [PATCH v2 1/2] x86/fpu: Fix memory leak with dynamic fpstate and exec()
From: Guixiong Wei
Date: Mon Oct 19 2026 - 21:28:37 EST
> From: "Chang S. Bae"<chang.seok.bae@xxxxxxxxx>
> Date: Thu, Oct 15, 2026, 08:19
> Subject: Re: [PATCH v2 1/2] x86/fpu: Fix memory leak with dynamic fpstate and exec()
> To: "Guixiong Wei"<weiguixiong@xxxxxxxxxxxxx>, <x86@xxxxxxxxxx>
> Cc: "Thomas Gleixner"<tglx@xxxxxxxxxx>, "Ingo Molnar"<mingo@xxxxxxxxxx>, "Borislav Petkov"<bp@xxxxxxxxx>, "Dave Hansen"<dave.hansen@xxxxxxxxxxxxxxx>, "H . Peter Anvin"<hpa@xxxxxxxxx>, <linux-kernel@xxxxxxxxxxxxxxx>, <stable@xxxxxxxxxxxxxxx>
> On 10/7/2026 10:59 PM, Guixiong Wei wrote:
> > The fpstate embedded in struct fpu only accommodates the default
> > xfeatures. After a task obtains permission for an XFD-controlled
> > xfeature, its first use raises #NM. The handler calls
> > __xfd_enable_feature(), which uses fpstate_realloc() to install a larger
> > fpstate allocated with vzalloc().
> >
> > On exec(), fpu_flush_thread() calls fpstate_reset(), which overwrites
> > fpu->fpstate with the embedded fpstate. This leaks the dynamic
> > allocation because arch_release_task_struct() cannot free it when the
> > task exits.
>
> Hmm, I'd start with Dave's wording here, or tweak it slightly.
Will do. v3 will picks up Dave's phrasing for the opening paragraphs
describing how the dynamic fpstate is allocated and leaked.
>
> > Make fpstate_reset() own the complete lifetime transition. Preserve the
> > old pointer, install and initialize the embedded fpstate, and then free
> > the old allocation. This detaches the allocation from fpu->fpstate
> > before freeing it and makes future reset callers release dynamic state
> > automatically.
>
> I'm not sure what the intended argument is here. Does this mean the old
> allocation cannot or should not be freed at the beginning of
> fpstate_reset()? If so, please explain functional issues. Otherwise, I'd
> simply follow Dave earlier suggestion.
Agreed, no functional reason. Will free at the beginning of
fpstate_reset() in v3.
>
> > On an AMX-capable KVM guest, 1000 iterations left 1000 dynamic fpstate
> > allocations on an unfixed kernel and none with this change.
>
> Since the selftest can reproduce the issue without KVM, I'm not sure why
> the KVM guest is relevant here. In any case, the entire sentence could
> be dropped if the problem description is clear enough.
>
Will do. Dropped in v3;
> >
> > void fpstate_reset(struct fpu *fpu)
> > {
> > + struct fpstate *oldfpstate = fpu->fpstate;
> > +
>
> Hmm, again, why not just call fpstate_free(fpu->fpstate) here?
>
> I see Dave also noted this comment along the lines:
> /* Free dynamic fpstate (if any) before resetting pointer: */
>
Will do.
> > /* Set the fpstate pointer to the default fpstate */
> > fpu->fpstate = &fpu->__fpstate;
> > __fpstate_reset(fpu->fpstate);
> > @@ -610,6 +612,8 @@ void fpstate_reset(struct fpu *fpu)
> > * guest FPUs, which allows for common guest and userspace ABI.
> > */
> > fpu->guest_perm.__user_state_size = fpu_user_cfg.default_size;
> > +
> > + fpstate_free(oldfpstate);
> > }
>
>
> > @@ -670,6 +674,7 @@ 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;
> >
> > + dst_fpu->fpstate = NULL;
> > fpstate_reset(dst_fpu);
>
> Dave also suggested adding a comment here:
>
> /* Break the link to the other FPU's fpstate: */
> dst_fpu->fpstate = NULL;
>
Will do.
> >
> > if (!cpu_feature_enabled(X86_FEATURE_FPU))
> > diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c
> > index a7b6524a9dea2..ea3736a74fd43 100644
> > --- a/arch/x86/kernel/fpu/xstate.c
> > +++ b/arch/x86/kernel/fpu/xstate.c
> > @@ -1556,10 +1556,10 @@ static int __init xfd_update_static_branch(void)
> > }
> > arch_initcall(xfd_update_static_branch)
> >
> > -void fpstate_free(struct fpu *fpu)
> > +void fpstate_free(struct fpstate *fpstate)
> > {
> > - if (fpu->fpstate && fpu->fpstate != &fpu->__fpstate)
> > - vfree(fpu->fpstate);
> > + if (fpstate && fpstate->is_valloc)
> > + vfree(fpstate);
> > }
>
> Shouldn't is_valloc be cleared here, or when fpstate_reset() replaces
> fpu->fpstate with the embedded fpstate?
>
No need: is_valloc lives inside the vmalloc'ed buffer itself, so
vfree() releases the flag together with the storage.
> Thanks,
> Chang
>