CVE-2026-90001 PUBLISHED

HID: bpf: serialize device reference release in struct_ops destroy path

Assigner: Linux
Reserved: 11.09.2026 Published: 16.09.2026 Updated: 16.09.2026

In the Linux kernel, the following vulnerability has been resolved:

HID: bpf: serialize device reference release in struct_ops destroy path

__hid_bpf_ops_destroy_device() and hid_bpf_unreg() can race on the same registration reference, double-putting struct hid_device and freeing it while hid_destroy_device() still uses it. Serialize the remove/NULL decision under hdev->bpf.prog_list_lock so exactly one path releases each registration reference: unreg re-checks ops->hdev under the lock and returns without putting when the destroy path already cleared it; all put_device() calls happen after the lock is dropped, which is safe because a concurrent unreg then observes ops->hdev == NULL under the lock.

Background: each successful attach (hid_bpf_ops_reg) acquires one device reference (hid_get_device()). Two paths can release it:

  • device destruction: hid_destroy_device() -> hid_bpf_destroy_device() -> __hid_bpf_ops_destroy_device(), which walks hdev->bpf.prog_list under rcu_read_lock() and drops one reference per attached program;
  • BPF link release: bpf map delete (no BPF_F_LINK) synchronously calls st_ops->unreg() -> hid_bpf_unreg(), which drops the reference for its own registration.

The coordination handshake (e->hdev = NULL on the destroy side vs "if (!hdev) return" on the unreg side) is a TOCTOU check: the two paths run under different lock domains (rcu_read_lock vs prog_list_lock), so a concurrent unreg can read ops->hdev as non-NULL, block on prog_list_lock, and then proceed while the destroy traversal executes - both paths then drop the same reference. The refcount reaches zero legitimately (each decrement is individually valid), so no refcount_t saturation fires: the device is simply freed while the transport is still inside hid_destroy_device(), and subsequent teardown touches freed memory.

The fix serializes the remove/NULL decision under prog_list_lock on both sides and moves the destroy-side puts outside the lock. With the lock held, plain reads/writes of ops->hdev are sufficient; no READ_ONCE/WRITE_ONCE are added, keeping the patch minimal.

Unlocked-read safety: the unlocked read of ops->hdev at the top of hid_bpf_unreg() cannot touch a freed device, because the unreg path itself still holds this registration's reference (released only by its own hid_put_device() after the lock is dropped), and a destroy traversal that already cleared ops->hdev makes the lock-internal re-check return early without any put. At most one of the two paths releases each registration reference.

Metrics

CVSS Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CVSS Score: 7.8

AV:L - HID-BPF attach and unreg are reached only through local bpf() syscalls (BPF_MAP_TYPE_STRUCT_OPS / BPF_PROG_TYPE_STRUCT_OPS). Concurrent hid_destroy_device() is also local (uhid UHID_DESTROY/close or HID teardown), not a network or adjacent-radio packet path. AC:L - The attacker controls both sides of the TOCTOU race: attach HID-BPF, then concurrently delete the struct_ops map (hid_bpf_unreg) and destroy the HID device (e.g. uhid close). Destroy never took prog_list_lock, so unreg does not block on it and the double-put is reliably hittable. PR:L - Loading STRUCT_OPS maps/programs requires CAP_BPF and CAP_PERFMON, which can be delegated via BPF tokens into user namespaces rather than init-namespace root. /dev/uhid is also granted to the seat user on typical desktop systems, so real root is not required. UI:N - The attacker creates their own uhid HID device, loads and attaches HID-BPF struct_ops, then races map delete against device destroy in their own processes. No victim action such as plugging in hardware or mounting a filesystem is required. S:U - The double-put use-after-free of struct hid_device stays inside the host kernel HID/BPF authority and is a standard local privilege-escalation primitive, not a VM, IOMMU, or sandbox boundary crossing. C:H - Double-putting struct hid_device frees it while hid_destroy_device() still uses it. Use-after-free of this large kobject, which holds driver callbacks and pointers, enables an attacker-controlled kernel memory read primitive. I:H - The same use-after-free lets the attacker reclaim the freed hid_device via heap spray and corrupt ll_driver/hiddev function pointers or adjacent heap objects, yielding arbitrary kernel writes and control-flow hijacking. A:H - Even without full exploitation, dropping the hid_device refcount to zero while hid_remove_device()/put_device() still run causes a kernel oops, panic, or hang on the subsequent use of freed memory.

Product Status

Vendor Linux
Product Linux
Versions Default: unaffected
  • affected from ebc0d8093e8c97de459615438edefad1a4ac352c to 401359684620145be710de97b87e1a47abfe1459 (excl.)
  • affected from ebc0d8093e8c97de459615438edefad1a4ac352c to c7f927aa8b55008ed5ea0814313d5dad771dcf3c (excl.)
  • affected from ebc0d8093e8c97de459615438edefad1a4ac352c to bfb7939788f3c8dd080a4dd81e38d625b35d194e (excl.)
  • affected from ebc0d8093e8c97de459615438edefad1a4ac352c to 9cdc7e6dc7a99ad7311ad5e7c145f2b9ce4e24b0 (excl.)
Vendor Linux
Product Linux
Versions Default: affected
  • Version 6.11 is affected
  • unaffected from 0 to 6.11 (excl.)
  • unaffected from 6.12.110 to 6.12.* (incl.)
  • unaffected from 6.18.51 to 6.18.* (incl.)
  • unaffected from 7.2.5 to 7.2.* (incl.)
  • unaffected from 7.3-rc2 to * (incl.)

References