From: Shuah Khan <skhan@linuxfoundation.org>
To: Lukas Beckmann <lbckmnn@mailbox.org>,
Thomas Renninger <trenn@suse.com>, Shuah Khan <shuah@kernel.org>,
"John B. Wyatt IV" <jwyatt@redhat.com>,
John Kacur <jkacur@redhat.com>,
Dominik Brodowski <linux@dominikbrodowski.net>
Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org,
stable@vger.kernel.org, Shuah Khan <skhan@linuxfoundation.org>
Subject: Re: [PATCH] cpupower: fix use-after-free and stale limits in frequency-set
Date: Fri, 13 Nov 2026 16:09:23 -0700 [thread overview]
Message-ID: <1bb7b407-0e69-421d-8ee3-81489abd1dda@linuxfoundation.org> (raw)
In-Reply-To: <20261112-cpupower-freq-set-use-after-free-v1-1-4e0034927b3f@mailbox.org>
On 11/12/26 12:49, Lukas Beckmann wrote:
> cmd_freq_set() currently has two issues:
>
> 1. use-after-free
>
> When cmd_freq_set() is invoked with --max and --min, it eventually calls
> do_new_policy() for every selected CPU with the same cpufreq_policy
> struct. do_new_policy() first retrieves the current policy and copies
> the governor pointer to new_pol.governor. After that the policy is
> applied and cur_pol (including the string to which new_pol.governor is
> pointing) is freed. For the next CPU, cpufreq_set_policy() reads that
> already freed memory leading to an error message (because verify_gov()
> fails).
>
> 2. max/min is not retrieved for each individual CPU:
>
> If cmd_freq_set() is invoked with e.g. --min and --governor, max is set
> by do_new_policy() on the first selected CPU by retrieving it from the
> current policy. Since the same struct is passed to the next selected
> CPU, it is not retrieved again, resulting in writing the first CPU's
> max to all selected CPUs.
>
> Fix both issues by using a local copy in do_new_policy().
Good find. The change log is rather long and confusing to follow.
>
> Fixes: 7fe2f6399a84 ("cpupowerutils - cpufrequtils extended with quite some features")
> Cc: stable@vger.kernel.org
> Signed-off-by: Lukas Beckmann <lbckmnn@mailbox.org>
> ---
> Testing:
>
> Issue 1:
> ASAN run of the (unpatched) cpupower example:
>
> ./cpupower frequency-set -d 2500MHz -u 2500MHz
> Setting cpu: 0
> Setting cpu: 1
> =================================================================
> ==386949==ERROR: AddressSanitizer: heap-use-after-free on address 0x7c17340e0350 at pc 0x7ff73559241c bp 0x7fff5d8114a0 sp 0x7fff5d810c58
> READ of size 2 at 0x7c17340e0350 thread T0
> #0 0x7ff73559241b (/usr/lib/libasan.so.8+0x8841b) (BuildId: b8a4241051a1621937fdc46e867ba7ecb56d96ea)
> #1 0x7ff7353cb475 in verify_gov lib/cpufreq.c:585
> #2 0x7ff7353cc0dd in cpufreq_set_policy lib/cpufreq.c:631
> #3 0x55f590fb79ab in do_new_policy utils/cpufreq-set.c:163
> #4 0x55f590fb79ab in do_one_cpu utils/cpufreq-set.c:192
> #5 0x55f590fb79ab in cmd_freq_set utils/cpufreq-set.c:327
> #6 0x55f590fb3ee6 in main utils/cpupower.c:235
> ...
>
> 0x7c17340e0350 is located 0 bytes inside of 11-byte region [0x7c17340e0350,0x7c17340e035b)
> freed by thread T0 here:
> #0 0x7ff735634e91 (/usr/lib/libasan.so.8+0x12ae91) (BuildId: b8a4241051a1621937fdc46e867ba7ecb56d96ea)
> #1 0x7ff7353cb8ef in cpufreq_put_policy lib/cpufreq.c:283
> #2 0x55f590fb79b6 in do_new_policy utils/cpufreq-set.c:165
> #3 0x55f590fb79b6 in do_one_cpu utils/cpufreq-set.c:192
> #4 0x55f590fb79b6 in cmd_freq_set utils/cpufreq-set.c:327
>
> previously allocated by thread T0 here:
> #0 0x7ff73562efce in strdup (/usr/lib/libasan.so.8+0x124fce) (BuildId: b8a4241051a1621937fdc46e867ba7ecb56d96ea)
> #1 0x7ff7353cb1c8 in sysfs_cpufreq_get_one_string lib/cpufreq.c:156
> #2 0x7ff7353cb845 in cpufreq_get_policy lib/cpufreq.c:262
> #3 0x55f590fb7a4f in do_new_policy utils/cpufreq-set.c:146
> #4 0x55f590fb7a4f in do_one_cpu utils/cpufreq-set.c:192
> #5 0x55f590fb7a4f in cmd_freq_set utils/cpufreq-set.c:327
>
> Running ASAN with the patch shows only an unrelated memory leak which
> was already reported in [1]. I can fix that in another patch if needed.
>
> Issue 2:
> Minimal example without the patch:
>
> echo 2000000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
> echo 3000000 > /sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq
> cat /sys/devices/system/cpu/cpu[0,1]/cpufreq/scaling_max_freq
> 2000000
> 3000000
>
> ./cpupower -c 0,1 frequency-set --min 1GHz -g performance
> Setting cpu: 0
> Setting cpu: 1
>
> cat /sys/devices/system/cpu/cpu[0,1]/cpufreq/scaling_max_freq
> 2000000
> 2000000
>
> The maximum of CPU1 is set to the one of CPU0.
>
The real issue here is not that the maximum of CPU1 is set to
the one of CPU0.
The real problem is that stale new_pol is used for all cpus
from do_one_cpu() slow path when more than one policy value
changes.
Please re-write this in your own words in a simplifies way
describing what lead to the problem.
> Minimal example with the patch:
>
> echo 2000000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
> echo 3000000 > /sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq
> cat /sys/devices/system/cpu/cpu[0,1]/cpufreq/scaling_max_freq
> 2000000
> 3000000
>
> ./cpupower -c 0,1 frequency-set --min 1GHz -g performance
> Setting cpu: 0
> Setting cpu: 1
>
> cat /sys/devices/system/cpu/cpu[0,1]/cpufreq/scaling_max_freq
> 2000000
> 3000000
>
> The maximum of CPU1 is no longer set to the maximum of CPU0.
>
> [1] https://lore.kernel.org/all/20250827-power-memoryleak-v1-1-e4baf7b59a41@uniontech.com/
The above patch doesn't make sense. I need more information
to determine, if it is needed.
> ---
> tools/power/cpupower/utils/cpufreq-set.c | 17 +++++++++--------
> 1 file changed, 9 insertions(+), 8 deletions(-)
>
> diff --git a/tools/power/cpupower/utils/cpufreq-set.c b/tools/power/cpupower/utils/cpufreq-set.c
> index 06cd4b280132..d9f6ca522fe5 100644
> --- a/tools/power/cpupower/utils/cpufreq-set.c
> +++ b/tools/power/cpupower/utils/cpufreq-set.c
> @@ -141,9 +141,10 @@ static unsigned long string_to_frequency(const char *str)
> }
> }
>
> -static int do_new_policy(unsigned int cpu, struct cpufreq_policy *new_pol)
> +static int do_new_policy(unsigned int cpu, const struct cpufreq_policy *new_pol)
> {
> struct cpufreq_policy *cur_pol = cpufreq_get_policy(cpu);
> + struct cpufreq_policy pol = *new_pol;
Don't assign new_pol to pol.
> int ret;
>
> if (!cur_pol) {
> @@ -151,16 +152,16 @@ static int do_new_policy(unsigned int cpu, struct cpufreq_policy *new_pol)
> return -EINVAL;
> }
>
> - if (!new_pol->min)
> - new_pol->min = cur_pol->min;
pol->min = cur_pol->min;
Use new_pol in the if logic using pol as temp - changing
pol to temp_pol
> + if (!pol.min)
> + pol.min = cur_pol->min;
>
> - if (!new_pol->max)
> - new_pol->max = cur_pol->max;
> + if (!pol.max)
> + pol.max = cur_pol->max;
>
> - if (!new_pol->governor)
> - new_pol->governor = cur_pol->governor;
> + if (!pol.governor)
> + pol.governor = cur_pol->governor;
>
> - ret = cpufreq_set_policy(cpu, new_pol);
> + ret = cpufreq_set_policy(cpu, &pol);
>
> cpufreq_put_policy(cur_pol);
>
thanks,
-- Shuah
prev parent reply other threads:[~2026-11-13 23:09 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 19:49 Lukas Beckmann
2026-11-13 23:09 ` Shuah Khan [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1bb7b407-0e69-421d-8ee3-81489abd1dda@linuxfoundation.org \
--to=skhan@linuxfoundation.org \
--cc=jkacur@redhat.com \
--cc=jwyatt@redhat.com \
--cc=lbckmnn@mailbox.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=linux@dominikbrodowski.net \
--cc=shuah@kernel.org \
--cc=stable@vger.kernel.org \
--cc=trenn@suse.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®