cpufreq/amd-pstate: Fix TOCTOU when changing driver mode via sysfs

amd_pstate_update_status() evaluates mode_state_machine[cppc_state]
[mode_idx] before taking amd_pstate_driver_lock, and then re-evaluates it
again once the lock is held.  cppc_state is global and only stable under
the lock, so concurrent sysfs writes race:

  CPU 0                               CPU 1
  -----                               -----
  // cppc_state == A, mode == B
  if (mode_state_machine[A][B])       // sees non-NULL
                                      if (mode_state_machine[A][B])
                                        guard(&amd_pstate_driver_lock);
                                        mode_state_machine[A][B](B);
                                          cppc_state = B;
    guard(&amd_pstate_driver_lock);
    // cppc_state is now B
    mode_state_machine[B][B](B);      // NULL -> NULL deref

The re-read under the lock can resolve to a self-transition (NULL
pointer, immediate NULL deref) or to an unexpected transition that
redundantly runs amd_pstate_driver_cleanup(), double-freeing
current_pstate_driver->attr.

Take the lock first, then read cppc_state and resolve the transition
function exactly once into a local before calling it, so the check and
the call observe the same state.

Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/bug/linux-f86ff3a6-55df-4eef-8e4d-62ac270dba25
Fixes: 6f0b13f16f ("cpufreq/amd-pstate: Overhaul locking")
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
Tested-by: K Prateek Nayak <kprateek.nayak@amd.com>
Link: https://lore.kernel.org/r/20260922022347.1270746-1-mario.limonciello@amd.com
Signed-off-by: Mario Limonciello <superm1@kernel.org>
This commit is contained in:
Mario Limonciello 2026-09-21 21:23:47 -05:00 committed by Mario Limonciello
parent b400642c5e
commit b667a68091

View File

@ -1886,6 +1886,7 @@ EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status);
int amd_pstate_update_status(const char *buf, size_t size)
{
cppc_mode_transition_fn fn;
int mode_idx;
if (size > strlen("passive") || size < strlen("active"))
@ -1895,10 +1896,12 @@ int amd_pstate_update_status(const char *buf, size_t size)
if (mode_idx < 0)
return mode_idx;
if (mode_state_machine[cppc_state][mode_idx]) {
guard(mutex)(&amd_pstate_driver_lock);
return mode_state_machine[cppc_state][mode_idx](mode_idx);
}
guard(mutex)(&amd_pstate_driver_lock);
/* Resolve the transition under the lock; cppc_state is only stable while held. */
fn = mode_state_machine[cppc_state][mode_idx];
if (fn)
return fn(mode_idx);
return 0;
}