mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: 徐晗翔 <xuhx@hust.edu.cn>
To: rafael@kernel.org, lenb@kernel.org
Cc: linux-acpi@vger.kernel.org, viresh.kumar@linaro.org,
	linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: ACPI: CPPC: per-CPU cpc_desc is freed on processor unbind without clearing the published pointer, causing UAF in cpufreq accessors
Date: Tue, 10 Nov 2026 21:05:26 +0800 (GMT+08:00)	[thread overview]
Message-ID: <610cc333.22ad9.1a1c5903a6b.Coremail.xuhx@hust.edu.cn> (raw)
In-Reply-To: <2026102727-data-sequel-ce41@gregkh>

[-- Attachment #1: Type: text/plain, Size: 7640 bytes --]

acpi_processor_stop() can be reached through the processor driver's
sysfs unbind attribute while the target CPU is still online.  It calls
acpi_cppc_processor_exit(), which iounmap()s the _CPC system-memory
registers and kfree()s the per-CPU struct cpc_desc, but never clears
per_cpu(cpc_desc_ptr, pr->id).  The cpufreq accessors (and the
acpi_cppc sysfs attributes) read that per-CPU pointer without a common
lock, refcount, or RCU grace period, so after the unbind they can
continue to use the freed descriptor.  A KASAN kernel reproduced the
slab-use-after-free with a real "echo cpu0 > .../processor/unbind",
not with a synthetic direct call to the exit function.

The detailed description:

* Test environment / affected version

  Linux 7.3.0 (mainline 7.3 source, unmodified), KASAN build from
  verification/build-kasan-7.3, x86_64 QEMU/KVM guest, 2 vCPU,
  CONFIG_KASAN=y, CONFIG_BUG=y, CONFIG_HOTPLUG_SMT=y.  The guest used a
  test SSDT (cpcint.aml) that adds a 21-entry, all-integer _CPC package
  to \_SB.CPUS.C000/C001, so the normal acpi_cppc_processor_probe()
  created a real cpc_desc and a real acpi_cppc sysfs directory.

  The test kernel has CONFIG_ACPI_CPPC_LIB=y and
  CONFIG_ACPI_PROCESSOR=y, but CONFIG_ACPI_CPPC_CPUFREQ is not enabled.
  The concurrent accessor in the reproducer is therefore a module
  calling the exported cppc_get_perf_caps(0), not a real
  cppc_cpufreq tick.  The release side is the real driver-core
  unbind path; the accessor function and the released object are the
  same as in the cpufreq/sysfs paths described below.  Line numbers
  are from Linux 7.3.

* Mechanism

  The ACPI processor driver is bound to the cpu_subsys CPU device and
  has .remove = acpi_processor_stop (drivers/acpi/processor_driver.c:
  45-50).  Writing a CPU name to
  /sys/bus/cpu/drivers/processor/unbind runs
  unbind_store() -> device_release_driver() -> acpi_processor_stop()
  (drivers/base/bus.c:235-251, drivers/base/dd.c:1318-1375).
  __device_release_driver() takes only the CPU device's device_lock;
  it does not take a CPU-hotplug lock and does not require the CPU to
  be offline.  acpi_processor_stop() has no online check either and
  calls acpi_cppc_processor_exit(pr) at processor_driver.c:204.

  acpi_cppc_processor_exit() (drivers/acpi/cppc_acpi.c:1037-1068)
  performs, in this order:

    pcc_data refcount/free
      -> iounmap(cpc_ptr->cpc_regs[i].sys_mem_vaddr)
      -> kobject_put(&cpc_ptr->kobj)
      -> kfree(cpc_ptr)

  There is no corresponding
  per_cpu(cpc_desc_ptr, pr->id) = NULL.  The pointer was published by
  acpi_cppc_processor_probe() at cppc_acpi.c:1002.  cppc_ktype has no
  .release callback (cppc_acpi.c:300-303), so kobject_put() only
  removes the sysfs directory and drains its active references; it is
  not a lifetime barrier for the descriptor.

  The accessors take the pointer outside any lock:

    cppc_get_reg_val():    cpc_desc = per_cpu(cpc_desc_ptr, cpu);
                           if (!cpc_desc) return -ENODEV;
                           reg = &cpc_desc->cpc_regs[reg_idx];
    cppc_get_perf_caps():  same pattern (cppc_acpi.c:1488 onwards)
    cppc_get_perf_ctrs():  same pattern (cppc_acpi.c:1678 onwards)

  The NULL check does not help: after exit the per-CPU slot is still
  non-NULL and points to freed memory.

  The cpufreq policy is owned by the cpufreq core, not by the ACPI
  processor driver.  acpi_processor_stop() neither offlines the CPU nor
  tears down the policy, so a registered CPPC frequency-invariance
  source can keep calling cppc_get_perf_ctrs() after unbind
  (drivers/cpufreq/cppc_cpufreq.c:69-104, 150-184).  The
  cpuinfo_cur_freq sysfs attribute reaches the driver's .get callback
  through __cpufreq_get() (drivers/cpufreq/cpufreq.c:755-764), and the
  acpi_cppc attributes call cppc_get_perf_caps() from show_cppc_data()
  (cppc_acpi.c:160-186).

  There is also a mapping-lifetime sub-case.  For _CPC registers in
  ACPI_ADR_SPACE_SYSTEM_MEMORY, cpc_read()/cpc_write() use
  reg_res->sys_mem_vaddr directly (cppc_acpi.c:1162-1180 and
  1235-1250), while exit iounmap()s those addresses before
  kobject_put().  A sysfs show that is still running can therefore
  reach readb/readw/readl/readq_relaxed() after the mapping was
  revoked; an EPP store can do the same through cpc_write().  The
  kernfs drain inside kobject_put() protects the later kfree(cpc_ptr),
  but it starts only after iounmap(), so it does not protect the
  mapping.  This sub-case is source-level only in this report; the
  tested guest had an all-integer _CPC and did not establish a
  system-memory _CPC successfully.

  A separate SMT path is worth fixing even though it was not the
  reproduced one: cpuhp_smt_enable() calls _cpu_up(cpu, 0,
  CPUHP_ONLINE) without taking the CPU device's device_lock
  (kernel/cpu.c:2702-2725, called from the smt/control store at
  2920-2940), while unbind_store() takes only that device_lock.  For a
  never-online SMT sibling, the first acpi_soft_cpu_online() probe can
  therefore overlap the same CPU's unbind and hit the 1002-1004 window
  (publish cpc_desc, then kobject_init_and_add()).



* Reproducer (attached: acpi_case1unbind.c) and results on 7.3

  The attached module starts a kthread that repeatedly calls the
  exported cppc_get_perf_caps(0, &caps), then the guest performs the
  real unbind:

    insmod /acpi_case1unbind.ko
    sleep 1
    echo cpu0 > /sys/bus/cpu/drivers/processor/unbind

  It does not modify kernel source.  It needs the test SSDT that
  provides the _CPC package, and the module build uses the same
  build-kasan-7.3 tree.

  Actual KASAN output (trimmed):

    CASE1UNBIND: unbinding cpu0
    BUG: KASAN: slab-use-after-free in cppc_get_perf_caps+0x14a/0x880
    Read of size 4 at addr ff110000019a6018 by task cppc_hammer_unb/73
    Call Trace:
     cppc_get_perf_caps+0x14a/0x880
     hammer_fn+0xf5/0x110 [acpi_case1unbind]
     kthread
    ...
    Allocated by task 20:
     acpi_cppc_processor_probe+0x174/0xe60
     acpi_soft_cpu_online+0x90/0x190
    ...
    Freed by task 1:
     kfree+0x121/0x390
     acpi_processor_stop+0x77/0x90
     device_release_driver_internal+0x246/0x2e0
     unbind_store+0xa9/0xb0
     kernfs_fop_write_iter+0x1ef/0x290
    ...
    CASE1UNBIND: unbind returned 0

  The allocation stack is the normal probe path, and the free stack is
  the real sysfs unbind path.  This confirms that the remove callback
  ran while CPU0 was online and that the published per-CPU pointer
  survived the free.  The module is an accessor stand-in: it does not
  prove the scheduler-tick timing or the system-memory iounmap
  sub-case, and the test kernel did not have cppc_cpufreq enabled.
  The core "exit frees, published pointer remains, accessor reads"
  lifetime is nevertheless demonstrated end to end.


* Suggested direction 

  Clear per_cpu(cpc_desc_ptr, pr->id) before releasing the object, and
  give accessors a real lifetime rule: an RCU/refcount-protected
  lookup, or a quiescence point that stops the cpufreq policy/FIE and
  waits for in-flight accessors before iounmap()/kfree().  The remove
  path should also refuse to run while the CPU is online (or offline
  and stop cpufreq first).  For the SMT path, make the first-online
  probe and driver remove share a lock, or exclude remove during the
  probe window.  If sysfs mappings remain reachable during removal,
  move iounmap() after the kernfs drain or protect the vaddr with the
  same lifetime mechanism.  Report-first, no patch attached; happy to
  test any proposal.


  Please keep me in Cc; I am not subscribed to the lists.



Regards,
Hanxiang Xu
xuhx@hust.edu.cn

[-- Attachment #2: acpi_case1unbind.c --]
[-- Type: text/plain, Size: 968 bytes --]

// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/acpi.h>
#include <acpi/cppc_acpi.h>

static struct task_struct *hammer;

static int hammer_fn(void *data)
{
	struct cppc_perf_caps caps;
	unsigned long n = 0;

	while (!kthread_should_stop()) {
		memset(&caps, 0, sizeof(caps));
		cppc_get_perf_caps(0, &caps);
		n++;
		if (!(n & 0x3ff))
			cond_resched();
	}
	pr_info("ACPI_CASE1UNBIND: hammer iterations=%lu\n", n);
	return 0;
}

static int __init case1unbind_init(void)
{
	hammer = kthread_run(hammer_fn, NULL, "cppc_hammer_unbind");
	if (IS_ERR(hammer)) {
		pr_err("ACPI_CASE1UNBIND: kthread_run failed\n");
		return PTR_ERR(hammer);
	}
	pr_info("ACPI_CASE1UNBIND: hammer started; unbind cpu0 now\n");
	return 0;
}

static void __exit case1unbind_exit(void)
{
	if (hammer)
		kthread_stop(hammer);
}

module_init(case1unbind_init);
module_exit(case1unbind_exit);
MODULE_LICENSE("GPL");

  parent reply	other threads:[~2026-11-10 13:05 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27 12:35 usb: devio: usbdev_release() has no barrier against an in-flight async_completed() callback (use-after-free window on struct usb_dev_state)gregkh@linuxfoundation.org 徐晗翔
2026-10-27 14:12 ` Greg KH
2026-11-03 11:21   ` mm/slub: has_pcs_used() reads remote per-CPU sheaf state from flush_all_cpus_locked() without synchronization (data race; KCSAN blind spot: slub.o is uninstrumented) 徐晗翔
2026-11-04  9:41     ` Qing Wang
2026-11-04 10:07       ` Qing Wang
2026-11-03 11:30   ` futex/hrtimer: hrtimer_sleeper::task is read unsynchronized in 16 places on v7.3 -- the accessor series that fixes this is not in 7.3; please make sure it reaches the affected trees (backport nudge + stock-kernel KCSAN evidence) 徐晗翔
2026-11-03 11:48   ` cgroup: fork vs cgroup.procs write race in favordynmods mode -- v7.3 is affected; please make sure bf6fbf46b5ff reaches the stable trees (repro incl. uninstrumented natural hit) 徐晗翔
2026-11-03 14:51     ` Sasha Levin
2026-11-03 11:57   ` net: rtnetlink: rtnl_link_unregister() vs in-flight RTM_GETLINK dump readers -- v7.3 lacks the synchronize_rcu() fix (Fixes: 6b57ff21a310 family); A/B reproduction on stock 7.3 徐晗翔
2026-11-03 15:49     ` Eric Dumazet
2026-11-04 15:03     ` Sasha Levin
2026-11-03 12:07   ` wifi: wilc1000: SDIO/SPI remove destroys the srcu_struct before wiphy_unregister() -- late cfg80211 callbacks write through dangling srcu state (unfixed upstream; controlled + natural Oops) 徐晗翔
2026-11-03 12:10   ` gpio: gpiolib: final put of the gpio_device happens inside the SRCU read critical section on the setup-failure path -- deterministic use-after-free read at guard release (unfixed upstream) 徐晗翔
2026-11-05  8:52     ` Linus Walleij
2026-11-04 12:54   ` soc: qcom: pmic_glink_altmode: fix use-after-free on unbind (no cancel_work_sync for port work and enable_work) 徐晗翔
2026-11-04 13:04     ` Greg KH
2026-11-04 12:57   ` usb: typec: anx7411: unregister partner/altmodes before stopping the IRQ work -- use-after-free, double unregister, orphan partner on remove 徐晗翔
2026-11-04 13:28     ` Greg KH
2026-11-04 12:58   ` usb: typec: ucsi: deadlock on cros_ec unbind -- connector lock held across cancel_delayed_work_sync vs the mode-selection work's unbounded trylock polling 徐晗翔
2026-11-04 13:00   ` usb: typec: stusb160x/rt1719/hd3ss3220: IRQ thread vs remove -- double unregister / use-after-free on the partner and port 徐晗翔
2026-11-04 13:01   ` usb: typec: ucsi: displayport: data race on dp->header/ vdo_data/vdo_size -- work reads them unlocked while writers hold con->lock (VDM mismatch) 徐晗翔
2026-11-04 13:02   ` usb: typec: class: three lifetime/locking gaps around the mux/switch handles and mode_selection (drvdata UAF after provider unbind; no concurrent protocol for set/put; unlocked state_update vs delete) 徐晗翔
2026-11-10 12:56   ` driver core: uevent: netns teardown frees uevent_sock while a tagged uevent is still being broadcast (UAF) 徐晗翔
2026-11-10 13:00   ` 徐晗翔
2026-11-10 13:05   ` 徐晗翔 [this message]
2026-11-10 13:07   ` ACPI: CPPC: shared PCC subspace data can be freed while another CPU probes, reads, or tears it down 徐晗翔
2026-11-10 13:11   ` ACPI: ACPICA: Reset can delete an event semaphore while another thread is blocked in Wait 徐晗翔
2026-11-10 13:14   ` power: supply: bq25890: pump-express work can call power_supply_changed after the psy is unregistered 徐晗翔
2026-11-10 13:17   ` power: supply: core: get/set property callbacks can run after unregister and read freed driver data 徐晗翔
2026-11-10 13:18   ` power: supply: core: system-battery enumeration can return an unregistered battery 徐晗翔
2026-11-10 13:21   ` power: supply: core: changed work can run after unregister and use freed LED triggers 徐晗翔
2026-11-10 13:23   ` power: supply: bq24190: IRQ handler can call power_supply_changed after the psy is unregistered 徐晗翔

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=610cc333.22ad9.1a1c5903a6b.Coremail.xuhx@hust.edu.cn \
    --to=xuhx@hust.edu.cn \
    --cc=lenb@kernel.org \
    --cc=linux-acpi@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=rafael@kernel.org \
    --cc=viresh.kumar@linaro.org \
    /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®