mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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

  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®