From: 徐晗翔 <xuhx@hust.edu.cn>
To: rafael@kernel.org, sudeep.holla@kernel.org, jassisinghbrar@gmail.com
Cc: linux-acpi@vger.kernel.org, viresh.kumar@linaro.org,
linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org,
lenb@kernel.org
Subject: ACPI: CPPC: shared PCC subspace data can be freed while another CPU probes, reads, or tears it down
Date: Tue, 10 Nov 2026 21:07:07 +0800 (GMT+08:00) [thread overview]
Message-ID: <7e7371c7.22ae1.1a1c591c7ae.Coremail.xuhx@hust.edu.cn> (raw)
In-Reply-To: <2026102727-data-sequel-ce41@gregkh>
pcc_data[] is a global array of shared PCC-subspace objects. The
reference count is an ordinary int updated without a lock, and
acpi_cppc_processor_exit() can kfree() the object and set the slot to
NULL while another CPU's accessor is using it, while another CPU's
first-online probe is allocating/registering it, or while another
remove is decrementing the same count. The result is a UAF, a NULL
dereference, a lost refcount update/leak, and a PCC channel/shmem
lifetime problem. This report is source-level: the test environment
does not have a PCC/PCCT controller, so no dynamic reproducer is
attached. I am not claiming the double-free variant.
The detailed description:
* Test environment / affected version
Linux 7.3.0 source (verification/linux-7.3), line numbers from 7.3.
The available QEMU/KASAN guest used for the sibling CPPC report has
no PCCT table and no PCC mailbox controller, so it cannot create a
real pcc_data object. I did not fake a pcc_data object or call the
teardown path with a synthetic pointer; that would not be a valid
dynamic reproduction. The affected code is the normal
CONFIG_ACPI_CPPC_LIB path in drivers/acpi/cppc_acpi.c plus the PCC
mailbox release in drivers/mailbox/pcc.c.
* Mechanism
A PCC subspace can be shared by multiple CPUs. pcc_data[pcc_ss_id]
is the global slot, and its refcount is the number of participating
CPUs (cppc_acpi.c:48-83).
Allocation (cppc_acpi.c:714-729) is:
if (pcc_data[pcc_ss_id])
pcc_data[pcc_ss_id]->refcount++;
else {
pcc_data[pcc_ss_id] = kzalloc(...);
...
pcc_data[pcc_ss_id]->refcount++;
}
There is no lock, and the slot check, allocation, publication and
increment are not atomic.
Teardown (cppc_acpi.c:1037-1051) is:
if (pcc_ss_id >= 0 && pcc_data[pcc_ss_id]) {
if (pcc_data[pcc_ss_id]->pcc_channel_acquired) {
pcc_data[pcc_ss_id]->refcount--;
if (!pcc_data[pcc_ss_id]->refcount) {
pcc_mbox_free_channel(...);
kfree(pcc_data[pcc_ss_id]);
pcc_data[pcc_ss_id] = NULL;
}
}
}
This is a plain read-modify-write, followed by a separate read for
the zero test, with no pcc_lock and no teardown mutex. The release
happens before kobject_put(&cpc_ptr->kobj), so the kernfs drain that
protects the cpc_desc sysfs object does not protect pcc_data at all.
Accessors take the global slot and immediately use it:
cppc_get_reg_val_in_pcc(): pcc_ss_data = pcc_data[pcc_ss_id];
down_write(&pcc_ss_data->pcc_lock);
cppc_set_reg_val_in_pcc(): cpc_write(...); then the same lookup
cppc_get_perf_caps(): same pattern (cppc_acpi.c:1488 onwards)
cppc_get_perf_ctrs(): same pattern (cppc_acpi.c:1678 onwards)
GET_PCC_VADDR(): pcc_data[id]->pcc_channel->shmem directly
None of these check for NULL, and none hold a reference that would
keep pcc_data alive across the use.
Four independent interleavings follow:
A. exit vs accessor. The last CPU is unbound while its tick,
cpuinfo_cur_freq, or an already-running acpi_cppc sysfs show is
in a PCC accessor. exit frees pcc_data and iounmap()s the
channel shmem; the accessor then calls down_write() on freed
memory or dereferences NULL. This is the simplest UAF and does
not require two CPUs to be removed at once.
B. remove vs remove. Two CPUs sharing the subspace both read
refcount == 2, both write 1, and neither frees: the object and
channel leak. If one remove has already freed and cleared the
slot while the other has a stale non-NULL value, the second
remove can dereference NULL or freed memory. The exact
double-free requires both removals to observe zero, which needs
an under-count or another remove path; I am not claiming it.
C. first-online probe vs remove. CPU B is doing its first
physical online and runs acpi_cppc_processor_probe(); CPU A is
being unbound and runs acpi_cppc_processor_exit(). They share
the subspace but hold different CPU device locks. The probe's
pcc_data_alloc() can read the slot just before exit frees it,
then increment freed memory; or it can enter register_pcc_channel(),
sleep in pcc_mbox_request_channel(), and then write the channel
pointer into a slot that exit has already freed and cleared.
acpi_cppc_processor_probe() also dereferences
pcc_data[pcc_subspace_id]->pcc_channel_acquired at
cppc_acpi.c:981 without a NULL check.
D. channel release vs in-flight send. pcc_mbox_free_channel()
(drivers/mailbox/pcc.c:431-448) only iounmap()s the shmem and
calls mbox_free_channel(); it does not wait for an in-flight
mbox_send_message()/txdone or for send_pcc_cmd() to finish.
An accessor already in send_pcc_cmd() can therefore use the
revoked mapping or the freed channel.
The probe failure path is a separate, non-race lifetime defect:
acpi_cppc_processor_probe()'s out_free path iounmap()s and
kfree()s cpc_ptr but does not decrement the pcc_data refcount that
pcc_data_alloc() just took (cppc_acpi.c:1015-1023). It can leak the
channel/object, but it is not the concurrent UAF above.
* Reproducer and results
No dynamic reproducer is attached. The current QEMU test
environment has no PCCT/PCC mailbox controller, so a real pcc_data
cannot be created; I did not substitute a fake object. The evidence
for this report is the source-level lifetime above, with the exact
line references in the mechanism section.
Static evidence summary:
- cppc_acpi.c:714-729: plain refcount++ in pcc_data_alloc()
- cppc_acpi.c:1037-1051: plain refcount--, zero test, free, NULL,
all without pcc_lock
- cppc_acpi.c:981-989: probe reads pcc_data[...] without a NULL
check and calls register_pcc_channel()
- cppc_acpi.c:644-675: register_pcc_channel() writes the channel
after a sleeping pcc_mbox_request_channel()
- cppc_acpi.c:1313-1336, 1366-1389, 1488+, 1678+: accessors use
pcc_data[] without a reference or NULL check
- cppc_acpi.c:97-98: GET_PCC_VADDR() dereferences the slot directly
- drivers/mailbox/pcc.c:431-448: free does not wait for in-flight
sends
* Suggested direction
Make pcc_data refcounted with refcount_t and update it under a
dedicated teardown mutex (or pcc_lock), with the slot lookup and
increment done atomically. Accessors should acquire a reference (or
use RCU) before dropping the lock, and the free path should clear the
slot first, then wait for readers/in-flight sends before
pcc_mbox_free_channel(). Free pcc_data after the kernfs drain, or
make the sysfs show hold a pcc_data reference for its whole call.
The probe error path should roll back the pcc_data_alloc() increment.
Report-first, no patch attached; I can test a proposal on PCC
hardware or a suitable QEMU model if one is available.
Please keep me in Cc; I am not subscribed to the lists.
Regards,
Hanxiang Xu
xuhx@hust.edu.cn
next prev parent reply other threads:[~2026-11-10 13:07 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 ` ACPI: CPPC: per-CPU cpc_desc is freed on processor unbind without clearing the published pointer, causing UAF in cpufreq accessors 徐晗翔
2026-11-10 13:07 ` 徐晗翔 [this message]
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=7e7371c7.22ae1.1a1c591c7ae.Coremail.xuhx@hust.edu.cn \
--to=xuhx@hust.edu.cn \
--cc=jassisinghbrar@gmail.com \
--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=sudeep.holla@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®