]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
arm64/fpsimd: ptrace: Consistently handle partial writes to NT_ARM_(S)SVE
authorMark Rutland <mark.rutland@arm.com>
Thu, 8 May 2025 13:26:25 +0000 (14:26 +0100)
committerWill Deacon <will@kernel.org>
Thu, 8 May 2025 14:29:08 +0000 (15:29 +0100)
Partial writes to the NT_ARM_SVE and NT_ARM_SSVE regsets using an
payload are handled inconsistently and non-deterministically. A comment
within sve_set_common() indicates that we intended that a partial write
would preserve any effective FPSIMD/SVE state which was not overwritten,
but this has never worked consistently, and during syscalls the FPSIMD
vector state may be non-deterministically preserved and may be
erroneously migrated between streaming and non-streaming SVE modes.

The simplest fix is to handle a partial write by consistently zeroing
the remaining state. As detailed below I do not believe this will
adversely affect any real usage.

Neither GDB nor LLDB attempt partial writes to these regsets, and the
documentation (in Documentation/arch/arm64/sve.rst) has always indicated
that state preservation was not guaranteed, as is says:

| The effect of writing a partial, incomplete payload is unspecified.

When the logic was originally introduced in commit:

  43d4da2c45b2 ("arm64/sve: ptrace and ELF coredump support")

... there were two potential behaviours, depending on TIF_SVE:

* When TIF_SVE was clear, all SVE state would be zeroed, excluding the
  low 128 bits of vectors shared with FPSIMD, FPSR, and FPCR.

* When TIF_SVE was set, all SVE state would be zeroed, including the
  low 128 bits of vectors shared with FPSIMD, but excluding FPSR and
  FPCR.

Note that as writing to NT_ARM_SVE would set TIF_SVE, partial writes to
NT_ARM_SVE would not be idempotent, and if a first write preserved the
low 128 bits, a subsequent (potentially identical) partial write would
discard the low 128 bits.

When support for the NT_ARM_SSVE regset was added in commit:

  e12310a0d30f ("arm64/sme: Implement ptrace support for streaming mode SVE registers")

... the above behaviour was retained for writes to the NT_ARM_SVE
regset, though writes to the NT_ARM_SSVE would always zero the SVE
registers and would not inherit FPSIMD register state. This happened as
fpsimd_sync_to_sve() only copied the FPSIMD regs when TIF_SVE was clear
and PSTATE.SM==0.

Subsequently, when FPSIMD/SVE state tracking was changed across commits:

  baa8515281b3 ("arm64/fpsimd: Track the saved FPSIMD state type separately to TIF_SVE")
  a0136be443d5 (arm64/fpsimd: Load FP state based on recorded data type")
  bbc6172eefdb ("arm64/fpsimd: SME no longer requires SVE register state")
  8c845e273104 ("arm64/sve: Leave SVE enabled on syscall if we don't context switch")

... there was no corresponding update to the ptrace code, nor to
fpsimd_sync_to_sve(), which stil considers TIF_SVE and PSTATE.SM rather
than the saved fp_type. The saved state can be in the FPSIMD format
regardless of whether TIF_SVE is set or clear, and the saved type can
change non-deterministically during syscalls. Consequently a subsequent
partial write to the NT_ARM_SVE or NT_ARM_SSVE regsets may
non-deterministically preserve the FPSIMD state, and may migrate this
state between streaming and non-streaming modes.

Clean this up by never attempting to preserve ANY state when writing an
SVE payload to the NT_ARM_SVE/NT_ARM_SSVE regsets, zeroing all relevant
state including FPSR and FPCR. This simplifies the code, makes the
behaviour deterministic, and avoids migrating state between streaming
and non-streaming modes. As above, I do not believe this should
adversely affect existing userspace applications.

At the same time, remove fpsimd_sync_to_sve(). It is no longer used,
doesn't do what its documentation implies, and gets in the way of other
cleanups and fixes.

Fixes: 43d4da2c45b2 ("arm64/sve: ptrace and ELF coredump support")
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: David Spickett <david.spickett@arm.com>
Cc: Luis Machado <luis.machado@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Mark Brown <broonie@kernel.org>
Cc: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20250508132644.1395904-6-mark.rutland@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
arch/arm64/include/asm/fpsimd.h
arch/arm64/kernel/fpsimd.c
arch/arm64/kernel/ptrace.c

index 11b9c6a5e03774583b3f9217490e74d3c341a2e0..da1bd5b9a7e70edf1311dce8412eb82f9d7063cf 100644 (file)
@@ -198,7 +198,6 @@ struct vl_info {
 
 extern void sve_alloc(struct task_struct *task, bool flush);
 extern void fpsimd_release_task(struct task_struct *task);
-extern void fpsimd_sync_to_sve(struct task_struct *task);
 extern void sve_sync_to_fpsimd(struct task_struct *task);
 extern void sve_sync_from_fpsimd_zeropad(struct task_struct *task);
 
index 9f1890d692dfcf3f4038ff367ddfc34409e87961..e06e4d8eda49e2a227ad68036b8043c03b0d291b 100644 (file)
@@ -759,21 +759,6 @@ void sve_alloc(struct task_struct *task, bool flush)
                kzalloc(sve_state_size(task), GFP_KERNEL);
 }
 
-/*
- * Ensure that task->thread.sve_state is up to date with respect to
- * the user task, irrespective of when SVE is in use or not.
- *
- * This should only be called by ptrace.  task must be non-runnable.
- * task->thread.sve_state must point to at least sve_state_size(task)
- * bytes of allocated kernel memory.
- */
-void fpsimd_sync_to_sve(struct task_struct *task)
-{
-       if (!test_tsk_thread_flag(task, TIF_SVE) &&
-           !thread_sm_enabled(&task->thread))
-               fpsimd_to_sve(task);
-}
-
 /*
  * Ensure that task->thread.uw.fpsimd_state is up to date with respect to
  * the user task, irrespective of whether SVE is in use or not.
index f79b0d5f71ac949d4d2c1c75309a421a22475d9d..259d90f70e0258c451d32cf3c51a0d980ff40516 100644 (file)
@@ -910,8 +910,6 @@ static int sve_set_common(struct task_struct *target,
 
        /* Enter/exit streaming mode */
        if (system_supports_sme()) {
-               u64 old_svcr = target->thread.svcr;
-
                switch (type) {
                case ARM64_VEC_SVE:
                        target->thread.svcr &= ~SVCR_SM_MASK;
@@ -931,23 +929,20 @@ static int sve_set_common(struct task_struct *target,
                        ret = -EINVAL;
                        goto out;
                }
-
-               /*
-                * If we switched then invalidate any existing SVE
-                * state and ensure there's storage.
-                */
-               if (target->thread.svcr != old_svcr)
-                       sve_alloc(target, true);
        }
 
+       /* Always zero V regs, FPSR, and FPCR */
+       memset(&current->thread.uw.fpsimd_state, 0,
+              sizeof(current->thread.uw.fpsimd_state));
+
        /* Registers: FPSIMD-only case */
 
        BUILD_BUG_ON(SVE_PT_FPSIMD_OFFSET != sizeof(header));
        if ((header.flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD) {
-               ret = __fpr_set(target, regset, pos, count, kbuf, ubuf,
-                               SVE_PT_FPSIMD_OFFSET);
                clear_tsk_thread_flag(target, TIF_SVE);
                target->thread.fp_type = FP_STATE_FPSIMD;
+               ret = __fpr_set(target, regset, pos, count, kbuf, ubuf,
+                               SVE_PT_FPSIMD_OFFSET);
                goto out;
        }
 
@@ -966,6 +961,7 @@ static int sve_set_common(struct task_struct *target,
                goto out;
        }
 
+       /* Always zero SVE state */
        sve_alloc(target, true);
        if (!target->thread.sve_state) {
                ret = -ENOMEM;
@@ -975,13 +971,9 @@ static int sve_set_common(struct task_struct *target,
        }
 
        /*
-        * Ensure target->thread.sve_state is up to date with target's
-        * FPSIMD regs, so that a short copyin leaves trailing
-        * registers unmodified.  Only enable SVE if we are
-        * configuring normal SVE, a system with streaming SVE may not
-        * have normal SVE.
+        * Only enable SVE if we are configuring normal SVE, a system with
+        * streaming SVE may not have normal SVE.
         */
-       fpsimd_sync_to_sve(target);
        if (type == ARM64_VEC_SVE)
                set_tsk_thread_flag(target, TIF_SVE);
        target->thread.fp_type = FP_STATE_SVE;