mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: 徐晗翔 <xuhx@hust.edu.cn>
To: heikki.krogerus@linux.intel.com
Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: 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)
Date: Wed, 4 Nov 2026 21:02:26 +0800 (GMT+08:00)	[thread overview]
Message-ID: <537efe25.1e59c.1a1a6a753b2.Coremail.xuhx@hust.edu.cn> (raw)
In-Reply-To: <2026102727-data-sequel-ce41@gregkh>

The typec class layer leaves the lifetime of its mux/switch machinery
to caller discipline that nothing enforces or documents, and three
concrete gaps fall out of that.  The reportable one is the provider
drvdata gap: a mux/switch handle holder keeps a device reference on
the provider's class device (class_find_device() takes get_device,
drivers/base/class.c:470), so after the PROVIDER driver unbinds,
typec_mux_unregister() only does device_unregister -- the mux_dev
shell (and its ->set callback pointer) survives as long as any owner
holds a handle, while the provider's devm private data is freed the
moment its .remove returns.  The next orientation/mode change then
calls mux_dev->set() (mux.c:393-412) into e.g.
qmp_combo_typec_mux_set() (phy-qcom-qmp-combo.c:4804) or
rk_udphy_typec_mux_set() (phy-rockchip-usbdp.c:1346), which fetch
devm drvdata of a freed object -- use-after-free read/write with
KASAN visibility; the same shape applies to the switch/retimer-side
providers with explicit removes (ptn36502, nb7vpq904m).  Two more
gaps in the same layer are contract-shaped and currently shielded
only by callers: (a) the aggregate typec_mux/typec_switch handles are
freshly allocated per get with no refcount/lock/RCU, typec_mux_set()
walks the callback list unlocked and typec_mux_put() kfrees on the
final put (the one in-tree violator of the resulting "drain before
put" discipline -- pmic_glink_altmode -- is reported separately); and
(b) mode_selection.c's exported state_update/delete pair has no
common lock or lifetime protocol at all (delete: partner->sel = NULL,
cancel_delayed_work_sync, mode_list_clean -- which does NOT take
sel->lock -- mutex_destroy, kfree; update: unlocked read of
partner->sel then mutex_lock + list access; today's only protection
is that every in-tree caller holds ucsi's con->lock, per the
enumeration below).  Fixes: a dead-flag + -ENODEV check in the set
path for (the drvdata gap), a documented drain+put contract (or a
class-layer primitive) for the handle protocol, and a lock/lifetime
contract (or in-delete locking) for mode_selection.

The detailed description:

* Gap 1 (reportable UAF): provider drvdata outlived by the handle

  Linux 7.3: the owner takes the handle via class_find_device()
  (mux.c:295) which get_device()s the mux_dev (class.c:470); the
  final typec_mux_put() (mux.c:376-391) drops it; the provider's
  unregister (mux.c:478-483) only device_unregister()s.  So:

      provider unbind: .remove -> typec_mux_unregister (shell kept)
                       .remove returns -> devres releases the
                       provider's private data (qmp/udphy/ptn)
      later mode/orientation change on the owner:
                       typec_mux_set(handle) -> mux_dev->set(...)
                       -> typec_mux_get_drvdata() -> FREED data

  Instances with this devm-drvdata pattern: qcom-qmp-combo (set
  :4804-4806, devm qmp, register :4924, unregister devm action
  :4894-4898/:4930), rockchip-usbdp (set :1346, register :1413-1415,
  unregister :1402-1406); ptn36502/nb7vpq904m have explicit removes
  but devm priv/regmap, and their own put does not cover an owner
  still holding the handle.  Nothing else closes this: module refs
  pin code; the parent-device reference pins the provider device
  struct, not its devm data; the unregister's device_del has no
  kernfs-drain semantics for this path (set is reached via the
  owner's private pointer, not sysfs).

  Fix: make the class layer mark the mux/switch dead at unregister
  (WRITE_ONCE flag) and have typec_mux_set()/typec_switch_set()
  return -ENODEV on it (READ_ONCE), or provide a drain primitive;
  a transitional provider-side option is to allocate the drvdata
  with the class device so the lifetimes coincide.

* Gap 2 (contract): aggregate handles have no concurrent protocol

  mux.c allocates a fresh aggregate handle per get (:82-129/:320-368)
  with no refcount/lock/RCU; typec_mux_set() (:393-412) walks the
  callback list unlocked; typec_mux_put() kfrees on the final put;
  and typec_unregister_altmode() even suggests the fragile order
  (put at class.c:712 before device_unregister at :713).  I audited
  all owners: UCSI/cros/tipd/gaokun serialize set-vs-put by their
  own construction (work drains, free_irq, notifier unregister --
  tabulated in my notes; the one in-tree violator,
  pmic_glink_altmode, has no cancel/flush at all and is reported
  separately).  The retimer class does it right (kref'd device,
  retimer.c:65-81) -- the mux/switch aggregate handles are the
  historical weak spot.  Fix: document the drain+put discipline in
  include/linux/usb/typec_mux.h (it currently says nothing about
  concurrency) or provide a class-layer drain+put primitive.

* Gap 3 (latent): mode_selection state_update vs delete

  mode_selection.c:176-197 (state_update: unlocked read of
  partner->sel at :179, then mutex_lock(&sel->lock) + list access)
  vs :271-283 (delete: partner->sel = NULL; cancel_delayed_work_sync
  (covers only that work); mode_list_clean() at :102-110 which does
  NOT take sel->lock and kfrees every node; mutex_destroy(); kfree
  (sel)).  Any overlap = UAF (locking a destroyed mutex; racing the
  list cleanup).  Today unreachable: the single update entry
  (ucsi_altmode_update_active, ucsi.c:354-384) is only called under
  con->lock (ucsi.c:1029/:1522/displayport.c:255 via the locked
  ucsi_con_mutex_lock/thunderbolt.c:109), and the only delete
  implementation (cros_ec) is likewise under con->lock
  (ucsi.c:1317-1318 from :2004/:1513/:1402/:2156) -- the header
  (typec_altmode.h:247-285) documents state_update as a generic
  altmode-driver entry point but states no locking precondition, so
  the first future caller that trusts the header reintroduces the
  UAF.  Fix: in-delete locking (mutex_lock/unlock(&sel->lock) before
  the cleanup) or a kref/RCU anchor, and document the contract.

  These three are reported together because they are the same layer
  and the same root style: safety outsourced to undocumented caller
  discipline.  The class layer's own retimer implementation shows
  the fix pattern.


  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-04 13:02 UTC|newest]

Thread overview: 21+ 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-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   ` 徐晗翔 [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=537efe25.1e59c.1a1a6a753b2.Coremail.xuhx@hust.edu.cn \
    --to=xuhx@hust.edu.cn \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.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®