* Fwd: [BUG] HID: sensor: custom: use-after-free of sensor_inst via open misc device after remove (hard lockup)
[not found] <CAM2dg7eZJEii+fJT1TG8XME-TJFqDtURCsr5XS71E5-5-mUV0A@mail.gmail.com>
@ 2026-10-23 10:42 ` Семён Марьясин
2026-10-23 14:12 ` Srinivas Pandruvada
0 siblings, 1 reply; 3+ messages in thread
From: Семён Марьясин @ 2026-10-23 10:42 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada
Cc: Benjamin Tissoires, Haoxiang Li, linux-input, linux-iio
[-- Attachment #1: Type: text/plain, Size: 5585 bytes --]
Hello,
hid-sensor-custom frees its per-sensor state (sensor_inst,
devm-allocated) on remove while userspace
can still hold the misc device /dev/HID-SENSOR-2000e1.N.auto open. A
reader blocked in read() stays
queued on sensor_inst->wait in freed memory; when it wakes up,
finish_wait() spins on a garbage
wait-queue lock with interrupts disabled and the machine hard-locks.
Any read()/poll()/release() on such
an fd after remove is a use-after-free as well.
The Sashiko AI review already flagged this as a pre-existing issue
on Haoxiang Li's "Fix fields lifetime issues" series
("The /dev/HID-SENSOR-* character device operations do not synchronize
with device
removal,
leading to Use-After-Free on devm-managed structures"):
https://lore.kernel.org/linux-input/20260707071545.3087073-1-haoxiang_li2024@163.com/
The applied patches don't touch it, and the current mainline is
unchanged. Below is a real-world hit.
Kernel: 7.2.8 (NixOS; drivers/hid/hid-sensor-custom.c is identical to
current mainline,
including ad8fb82b0442 and 3789d0802ddb)
Hardware: Dell Latitude 5420 (Tiger Lake, Intel ISH), BIOS 1.49.0;
Dell's vendor ISH sensors appear as
HID-SENSOR-2000e1.N.auto (e.g. an ST VL53L1 time-of-flight sensor).
Taint: O, from unrelated out-of-tree WWAN modules (qcserial, usb_wwan).
How it was hit
--------------
1. echo 1 > /sys/bus/platform/devices/HID-SENSOR-2000e1.2.auto/enable_sensor
2. xxd /dev/HID-SENSOR-2000e1.2.auto # streaming reader, left running
3. modprobe -r intel_ishtp_hid intel_ish_ipc && modprobe intel_ish_ipc
The hub went away and was re-enumerated fine; ~30 s later CPU0
hard-locked in the still-running xxd
and the hard-lockup detector panicked the machine (log recovered from
EFI pstore).
Presumably a minimal reproducer is just a blocked reader plus
unbinding the platform device
(echo HID-SENSOR-2000e1.N.auto >
/sys/bus/platform/drivers/hid_sensor_custom/unbind),
but only the module-reload path above was actually tested so far.
Log (trimmed)
-------------
[146608.877730] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
for response from ISHTP device
[146611.885959] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
for response from ISHTP device
[146614.893909] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
for response from ISHTP device
[146617.901872] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
for response from ISHTP device
[146619.972550] ish-hid {33AECD58-B679-4E54-9BD9-A04D34F0C226}:
[hid-ish]: enum_devices_done OK, num_hid_devices=3
[146620.031441] hid-sensor-hub 001F:8087:0AC2.0007: hidraw3: SENSOR
HUB HID v2.00 Device [hid-ishtp 8087:0AC2] on
[146643.503266] iwlwifi 0000:00:14.3: Queue 3 is stuck 46 70
[146648.924391] watchdog: CPU0: Watchdog detected hard LOCKUP on cpu 0
[146648.924796] CPU: 0 UID: 0 PID: 1316004 Comm: xxd Tainted: G
O 7.2.8 #1-NixOS PREEMPT(lazy)
[146648.924804] Hardware name: Dell Inc. Latitude 5420/03P5X2, BIOS
1.49.0 09/01/2025
[146648.924808] RIP: 0010:native_queued_spin_lock_slowpath+0x64/0x2c0
[146648.924851] Call Trace:
[146648.924855] <TASK>
[146648.924857] _raw_spin_lock_irqsave+0x3d/0x50
[146648.924872] finish_wait+0x3b/0x90
[146648.924886] hid_sensor_custom_read+0x102/0x150 [hid_sensor_custom]
[146648.924901] vfs_read+0xbf/0x390
[146648.924934] ksys_read+0xbf/0xf0
[146648.924939] do_syscall_64+0x134/0x6b0
[146648.925010] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[146648.925083] </TASK>
[146648.925086] Kernel panic - not syncing: Hard LOCKUP
Also attaching a full dmesg log (with only wi-fi noise filtered out).
Analysis (line numbers as in mainline 73bef5)
---------------------------------------------
- hid_sensor_custom_probe() allocates sensor_inst with devm_kzalloc()
(line 980), so it is freed right
after hid_sensor_custom_remove() returns, regardless of open files.
- The misc device's fops reach sensor_inst through
container_of(file->private_data, ..., custom_dev);
misc_deregister() does not revoke already-open files, so
read/poll/release keep dereferencing it.
- hid_sensor_custom_dev_if_remove() (lines 751-758) does wake_up(),
misc_deregister(), kfifo_free().
The woken reader in hid_sensor_custom_read() (line 664) re-evaluates
wait_event_interruptible(sensor_inst->wait,
!kfifo_is_empty(&sensor_inst->data_fifo)), finds the fifo
empty and goes back to sleep on sensor_inst->wait. Nothing tells it
the device is gone.
- Once sensor_inst is freed and reused, the next wakeup of that reader
runs finish_wait() on the freed
wait_queue_head: spin_lock_irqsave() on garbage, hard lockup.
hid_sensor_custom_release() similarly
does clear_bit() on freed sensor_inst->misc_opened when the fd is
finally closed.
Possible fix direction
----------------------
Tie sensor_inst's lifetime to its users rather than to devres, the way
hidraw handles disconnect:
refcount it (kref: one reference for the device, one per open file;
kfifo_free() and kfree() on the last
put), add a "removed" flag set in remove() before
wake_up_interruptible_all(), include it in the
wait_event condition, and make read()/poll() return -ENODEV / EPOLLHUP
| EPOLLERR once removed.
The same review pointed out two more problems in this teardown path
that a fix would naturally cover:
remove() frees data_fifo before sensor_hub_remove_callback(), so a
sample arriving in between is
kfifo_in()'d into freed memory; and an enabled sensor is never
sensor_hub_device_close()d on remove.
I can test patches on this machine.
Thanks,
Semyon Maryasin
P.S.: sorry for a possible duplicate, I think I messed with the headers.
[-- Attachment #2: dmesg-clean.txt --]
[-- Type: text/plain, Size: 14021 bytes --]
<6>[122586.929775] PM: suspend entry (s2idle)
<6>[122586.977820] Filesystems sync: 0.048 seconds
<6>[122586.988535] Freezing user space processes
<6>[122586.990269] Freezing user space processes completed (elapsed 0.001 seconds)
<6>[122586.990274] OOM killer disabled.
<6>[122586.990275] Freezing remaining freezable tasks
<6>[122586.991832] Freezing remaining freezable tasks completed (elapsed 0.001 seconds)
<6>[122586.991835] printk: Suspending console(s) (use no_console_suspend to debug)
<6>[122587.012388] e1000e: EEE TX LPI TIMER: 00000011
<6>[122587.394462] ACPI: EC: interrupt blocked
<6>[122590.828113] ACPI: EC: interrupt unblocked
<6>[122591.185256] i915 0000:00:02.0: vgaarb: VGA decodes changed: olddecodes=io,decodes=io:owns=io+mem
<6>[122591.205074] nvme nvme0: 8/0/0 default/read/poll queues
<6>[122591.207053] nvme nvme0: Ignoring bogus Namespace Identifiers
<6>[122592.245524] OOM killer enabled.
<6>[122592.245530] Restarting tasks: Starting
<6>[122592.248615] mei_hdcp 0000:00:16.0-b638ab7e-94e2-4ea2-a552-d1c54b627f04: bound 0000:00:02.0 (ops i915_hdcp_ops [i915])
<6>[122592.251481] Restarting tasks: Done
<6>[122592.251488] efivarfs: resyncing variable state
<6>[122592.259633] efivarfs: finished resyncing variable state
<5>[122592.259666] random: crng reseeded on system resumption
<6>[122592.294055] PM: suspend exit
<6>[122592.392682] e1000e 0000:00:1f.6 eth0: NIC Link is Down
<6>[122622.964242] PM: suspend entry (s2idle)
<6>[122623.008566] Filesystems sync: 0.044 seconds
<6>[122623.023288] Freezing user space processes
<6>[122623.025388] Freezing user space processes completed (elapsed 0.002 seconds)
<6>[122623.025393] OOM killer disabled.
<6>[122623.025394] Freezing remaining freezable tasks
<6>[122623.027011] Freezing remaining freezable tasks completed (elapsed 0.001 seconds)
<6>[122623.027014] printk: Suspending console(s) (use no_console_suspend to debug)
<6>[122623.045649] e1000e: EEE TX LPI TIMER: 00000011
<6>[122623.426308] ACPI: EC: interrupt blocked
<6>[142890.179053] ACPI: EC: interrupt unblocked
<6>[142890.577373] i915 0000:00:02.0: vgaarb: VGA decodes changed: olddecodes=io,decodes=io:owns=io+mem
<6>[142890.602715] nvme nvme0: 8/0/0 default/read/poll queues
<6>[142890.604761] nvme nvme0: Ignoring bogus Namespace Identifiers
<6>[142891.665519] OOM killer enabled.
<6>[142891.665532] Restarting tasks: Starting
<6>[142891.669205] mei_hdcp 0000:00:16.0-b638ab7e-94e2-4ea2-a552-d1c54b627f04: bound 0000:00:02.0 (ops i915_hdcp_ops [i915])
<6>[142891.681315] Restarting tasks: Done
<6>[142891.681353] efivarfs: resyncing variable state
<6>[142891.727579] efivarfs: finished resyncing variable state
<5>[142891.727685] random: crng reseeded on system resumption
<6>[142891.870922] PM: suspend exit
<6>[142892.153878] e1000e 0000:00:1f.6 eth0: NIC Link is Down
<3>[146608.877730] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting for response from ISHTP device
<3>[146611.885959] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting for response from ISHTP device
<3>[146614.893909] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting for response from ISHTP device
<3>[146617.901872] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting for response from ISHTP device
<6>[146619.972550] ish-hid {33AECD58-B679-4E54-9BD9-A04D34F0C226}: [hid-ish]: enum_devices_done OK, num_hid_devices=3
<6>[146619.981751] hid-sensor-hub 001F:8087:0AC2.0005: hidraw0: SENSOR HUB HID v2.00 Device [hid-ishtp 8087:0AC2] on
<6>[146620.011396] hid-sensor-hub 001F:8087:0AC2.0006: hidraw1: SENSOR HUB HID v2.00 Device [hid-ishtp 8087:0AC2] on
<6>[146620.031441] hid-sensor-hub 001F:8087:0AC2.0007: hidraw3: SENSOR HUB HID v2.00 Device [hid-ishtp 8087:0AC2] on
<0>[146648.924391] watchdog: CPU0: Watchdog detected hard LOCKUP on cpu 0
<4>[146648.924399] Modules linked in: intel_ishtp_hid intel_ish_ipc intel_ishtp ext4 mbcache jbd2 sd_mod uas usb_storage scsi_mod scsi_common nf_conntrack_netlink unix_diag ccm tcp_diag inet_diag qrtr rfcomm snd_seq_dummy snd_hrtimer snd_seq snd_seq_device xfrm_user xfrm_algo xt_set ip_set xt_addrtype overlay xt_CHECKSUM xt_MASQUERADE af_packet aes algif_hash algif_skcipher af_alg uinput wireguard libcurve25519 bnep libchacha20poly1305 libchacha libpoly1305 snd_hda_codec_intelhdmi ip6_udp_tunnel snd_hda_codec_hdmi udp_tunnel msr snd_ctl_led dell_pc snd_hda_codec_alc269 snd_hda_codec_realtek_lib snd_hda_scodec_component snd_hda_codec_generic hid_sensor_custom_intel_hinge snd_hda_intel snd_sof_pci_intel_tgl snd_sof_pci_intel_cnl snd_sof_intel_hda_generic soundwire_intel hid_sensor_gyro_3d hid_sensor_trigger snd_sof_intel_hda_sdw_bpt kfifo_buf hid_sensor_iio_common snd_sof_intel_hda_common industrialio snd_soc_hdac_hda hid_sensor_custom snd_sof_intel_hda_mlink snd_sof_intel_hda soundwire_cadence snd_sof_pci snd_sof_xtensa_dsp
<4>[146648.924491] snd_sof joydev snd_sof_utils snd_soc_acpi_intel_match snd_soc_acpi_intel_sdca_quirks soundwire_generic_allocation hid_sensor_hub snd_soc_sdw_utils snd_soc_acpi soundwire_bus mousedev nls_iso8859_1 snd_soc_sdca nls_cp437 ofpart vfat crc8 cmdlinepart ee1004 spi_nor hid_multitouch snd_soc_avs iTCO_wdt mtd hid_generic intel_pmc_bxt snd_soc_hda_codec snd_hda_ext_core snd_hda_codec intel_rapl_msr snd_hda_core uvcvideo snd_intel_dspcfg intel_uncore_frequency intel_uncore_frequency_common snd_intel_sdw_acpi videobuf2_vmalloc snd_hwdep uvc x86_pkg_temp_thermal intel_powerclamp cdc_mbim snd_soc_core videobuf2_memops cdc_wdm videobuf2_v4l2 cdc_ncm cdc_ether videobuf2_common snd_compress iwlmvm ac97_bus snd_pcm_dmaengine dell_rbtn videodev coretemp snd_pcm usbnet dell_wmi mei_hdcp mei_pxp mei_wdt mc mii aesni_intel dell_laptop gf128mul mac80211 e1000e fat snd_timer i2c_i801 rapl dell_smbios ucsi_acpi snd ptp typec_ucsi btusb dell_wmi_sysman i2c_smbus intel_cstate dcdbas spi_intel_pci libarc4 xe intel_uncore psmouse
<4>[146648.924696] int340x_thermal_zone 8250_pci edac_core virt_dma intel_soc_dts_iosf rfkill evdev mac_hid ecc crc16 serio_raw xt_conntrack xt_tcpudp ip6t_rpfilter ipt_rpfilter xt_pkttype nft_compat nf_tables sch_fq_codel kvm_intel kvm xt_nat x_tables nf_nat br_netfilter nf_conntrack bridge qcserial(O) nf_defrag_ipv6 nf_defrag_ipv4 usb_wwan(O) tun veth stp fuse loop llc irqbypass cpufreq_powersave ctr dmi_sysfs nvme atkbd rtsx_pci_sdmmc libps2 nvme_core mmc_core vivaldi_fmap xhci_pci i8042 nvme_keyring rtsx_pci nvme_auth thunderbolt serio xhci_hcd btrfs i915 libblake2b drm_buddy i2c_algo_bit video wmi ttm intel_gtt drm_display_helper dm_mod raid6_pq cec xor configfs efivarfs autofs4 [last unloaded: intel_ishtp]
<4>[146648.924796] CPU: 0 UID: 0 PID: 1316004 Comm: xxd Tainted: G O 7.2.8 #1-NixOS PREEMPT(lazy)
<4>[146648.924803] Tainted: [O]=OOT_MODULE
<4>[146648.924804] Hardware name: Dell Inc. Latitude 5420/03P5X2, BIOS 1.49.0 09/01/2025
<4>[146648.924808] RIP: 0010:native_queued_spin_lock_slowpath+0x64/0x2c0
<4>[146648.924821] Code: 77 71 f0 0f ba 29 08 0f 92 c2 8b 01 0f b6 d2 c1 e2 08 30 e4 09 d0 3d ff 00 00 00 77 4d 85 c0 74 10 0f b6 01 84 c0 74 09 f3 90 <0f> b6 01 84 c0 75 f7 b8 01 00 00 00 66 89 01 e9 98 1b 00 00 8b 37
<4>[146648.924824] RSP: 0018:ffffd1d80fbbfa80 EFLAGS: 00000002
<4>[146648.924828] RAX: 0000000000000001 RBX: 0000000000000202 RCX: ffff8eb6c9f17f28
<4>[146648.924831] RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff8eb6c9f17f28
<4>[146648.924833] RBP: ffffd1d80fbbfae0 R08: 0000000000000001 R09: 0000000000000001
<4>[146648.924836] R10: ffff8ec019080000 R11: 0000000000000000 R12: ffff8eb6c9f17f28
<4>[146648.924838] R13: ffff8ec1b5d03180 R14: 0000000000000001 R15: 0000000000000000
<4>[146648.924840] FS: 00007e18ed70a780(0000) GS:ffff8ec66aeb9000(0000) knlGS:0000000000000000
<4>[146648.924844] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
<4>[146648.924846] CR2: 0000742747faf000 CR3: 0000000bf305f004 CR4: 0000000000f72ef0
<4>[146648.924849] PKRU: 55555554
<4>[146648.924851] Call Trace:
<4>[146648.924855] <TASK>
<4>[146648.924857] _raw_spin_lock_irqsave+0x3d/0x50
<4>[146648.924872] finish_wait+0x3b/0x90
<4>[146648.924886] hid_sensor_custom_read+0x102/0x150 [hid_sensor_custom]
<4>[146648.924894] ? __pfx_autoremove_wake_function+0x10/0x10
<4>[146648.924901] vfs_read+0xbf/0x390
<4>[146648.924910] ? tty_insert_flip_string_and_push_buffer+0x95/0xc0
<4>[146648.924921] ? remove_wait_queue+0x24/0x60
<4>[146648.924926] ? n_tty_write+0x3b9/0x4e0
<4>[146648.924934] ksys_read+0xbf/0xf0
<4>[146648.924939] do_syscall_64+0x134/0x6b0
<4>[146648.924946] ? file_tty_write.isra.0+0x20e/0x320
<4>[146648.924952] ? vfs_write+0x25d/0x470
<4>[146648.924958] ? ksys_write+0x73/0xf0
<4>[146648.924963] ? do_syscall_64+0x173/0x6b0
<4>[146648.924966] ? n_tty_write+0x3b9/0x4e0
<4>[146648.924972] ? __wake_up+0x44/0x60
<4>[146648.924978] ? file_tty_write.isra.0+0x20e/0x320
<4>[146648.924983] ? vfs_write+0x25d/0x470
<4>[146648.924989] ? ksys_write+0x73/0xf0
<4>[146648.924993] ? do_syscall_64+0x173/0x6b0
<4>[146648.924997] ? do_syscall_64+0x173/0x6b0
<4>[146648.925000] ? do_syscall_64+0xeb/0x6b0
<4>[146648.925002] ? clear_bhb_loop+0x60/0xb0
<4>[146648.925010] entry_SYSCALL_64_after_hwframe+0x77/0x7f
<4>[146648.925016] RIP: 0033:0x7e18ed49915e
<4>[146648.925059] Code: 03 00 00 59 5e 48 83 f8 fc 74 39 c9 31 d2 31 c9 31 f6 31 ff 45 31 c0 45 31 c9 45 31 d2 45 31 db c3 0f 1f 00 48 8b 45 10 0f 05 <c9> 31 d2 31 c9 31 f6 31 ff 45 31 c0 45 31 c9 45 31 d2 45 31 db c3
<4>[146648.925062] RSP: 002b:00007ffd67ba5f60 EFLAGS: 00000202 ORIG_RAX: 0000000000000000
<4>[146648.925065] RAX: ffffffffffffffda RBX: 0000614264f63310 RCX: 00007e18ed49915e
<4>[146648.925067] RDX: 0000000000001000 RSI: 0000614264f634f0 RDI: 0000000000000003
<4>[146648.925069] RBP: 00007ffd67ba5f70 R08: 0000000000000000 R09: 0000000000000000
<4>[146648.925071] R10: 0000000000000000 R11: 0000000000000202 R12: 00007e18ed5f9010
<4>[146648.925073] R13: 00007e18ed5f8ec0 R14: 0000000000000000 R15: 0000000000000000
<4>[146648.925083] </TASK>
<0>[146648.925086] Kernel panic - not syncing: Hard LOCKUP
<4>[146648.925089] CPU: 0 UID: 0 PID: 1316004 Comm: xxd Tainted: G O 7.2.8 #1-NixOS PREEMPT(lazy)
<4>[146648.925093] Tainted: [O]=OOT_MODULE
<4>[146648.925094] Hardware name: Dell Inc. Latitude 5420/03P5X2, BIOS 1.49.0 09/01/2025
<4>[146648.925096] Call Trace:
<4>[146648.925099] <NMI>
<4>[146648.925101] dump_stack_lvl+0x5d/0x80
<4>[146648.925110] vpanic+0x20f/0x410
<4>[146648.925118] panic+0x6b/0x70
<4>[146648.925124] nmi_panic.cold+0xc/0xc
<4>[146648.925129] watchdog_hardlockup_check.cold+0x11e/0x1e8
<4>[146648.925136] __perf_event_overflow+0x120/0x3c0
<4>[146648.925148] handle_pmi_common+0x1e0/0x450
<4>[146648.925162] intel_pmu_handle_irq+0xf4/0x4c0
<4>[146648.925167] perf_event_nmi_handler+0x42/0x70
<4>[146648.925176] nmi_handle.part.0+0x53/0x110
<4>[146648.925185] default_do_nmi+0x10e/0x170
<4>[146648.925193] exc_nmi+0x129/0x1a0
<4>[146648.925198] end_repeat_nmi+0xf/0x53
<4>[146648.925203] RIP: 0010:native_queued_spin_lock_slowpath+0x64/0x2c0
<4>[146648.925207] Code: 77 71 f0 0f ba 29 08 0f 92 c2 8b 01 0f b6 d2 c1 e2 08 30 e4 09 d0 3d ff 00 00 00 77 4d 85 c0 74 10 0f b6 01 84 c0 74 09 f3 90 <0f> b6 01 84 c0 75 f7 b8 01 00 00 00 66 89 01 e9 98 1b 00 00 8b 37
<4>[146648.925210] RSP: 0018:ffffd1d80fbbfa80 EFLAGS: 00000002
<4>[146648.925213] RAX: 0000000000000001 RBX: 0000000000000202 RCX: ffff8eb6c9f17f28
<4>[146648.925215] RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff8eb6c9f17f28
<4>[146648.925217] RBP: ffffd1d80fbbfae0 R08: 0000000000000001 R09: 0000000000000001
<4>[146648.925219] R10: ffff8ec019080000 R11: 0000000000000000 R12: ffff8eb6c9f17f28
<4>[146648.925221] R13: ffff8ec1b5d03180 R14: 0000000000000001 R15: 0000000000000000
<4>[146648.925227] ? native_queued_spin_lock_slowpath+0x64/0x2c0
<4>[146648.925231] ? native_queued_spin_lock_slowpath+0x64/0x2c0
<4>[146648.925235] </NMI>
<4>[146648.925236] <TASK>
<4>[146648.925238] _raw_spin_lock_irqsave+0x3d/0x50
<4>[146648.925243] finish_wait+0x3b/0x90
<4>[146648.925249] hid_sensor_custom_read+0x102/0x150 [hid_sensor_custom]
<4>[146648.925254] ? __pfx_autoremove_wake_function+0x10/0x10
<4>[146648.925261] vfs_read+0xbf/0x390
<4>[146648.925264] ? tty_insert_flip_string_and_push_buffer+0x95/0xc0
<4>[146648.925271] ? remove_wait_queue+0x24/0x60
<4>[146648.925276] ? n_tty_write+0x3b9/0x4e0
<4>[146648.925281] ksys_read+0xbf/0xf0
<4>[146648.925285] do_syscall_64+0x134/0x6b0
<4>[146648.925289] ? file_tty_write.isra.0+0x20e/0x320
<4>[146648.925294] ? vfs_write+0x25d/0x470
<4>[146648.925300] ? ksys_write+0x73/0xf0
<4>[146648.925304] ? do_syscall_64+0x173/0x6b0
<4>[146648.925308] ? n_tty_write+0x3b9/0x4e0
<4>[146648.925313] ? __wake_up+0x44/0x60
<4>[146648.925319] ? file_tty_write.isra.0+0x20e/0x320
<4>[146648.925323] ? vfs_write+0x25d/0x470
<4>[146648.925330] ? ksys_write+0x73/0xf0
<4>[146648.925334] ? do_syscall_64+0x173/0x6b0
<4>[146648.925337] ? do_syscall_64+0x173/0x6b0
<4>[146648.925340] ? do_syscall_64+0xeb/0x6b0
<4>[146648.925343] ? clear_bhb_loop+0x60/0xb0
<4>[146648.925348] entry_SYSCALL_64_after_hwframe+0x77/0x7f
<4>[146648.925352] RIP: 0033:0x7e18ed49915e
<4>[146648.925359] Code: 03 00 00 59 5e 48 83 f8 fc 74 39 c9 31 d2 31 c9 31 f6 31 ff 45 31 c0 45 31 c9 45 31 d2 45 31 db c3 0f 1f 00 48 8b 45 10 0f 05 <c9> 31 d2 31 c9 31 f6 31 ff 45 31 c0 45 31 c9 45 31 d2 45 31 db c3
<4>[146648.925361] RSP: 002b:00007ffd67ba5f60 EFLAGS: 00000202 ORIG_RAX: 0000000000000000
<4>[146648.925365] RAX: ffffffffffffffda RBX: 0000614264f63310 RCX: 00007e18ed49915e
<4>[146648.925367] RDX: 0000000000001000 RSI: 0000614264f634f0 RDI: 0000000000000003
<4>[146648.925369] RBP: 00007ffd67ba5f70 R08: 0000000000000000 R09: 0000000000000000
<4>[146648.925371] R10: 0000000000000000 R11: 0000000000000202 R12: 00007e18ed5f9010
<4>[146648.925373] R13: 00007e18ed5f8ec0 R14: 0000000000000000 R15: 0000000000000000
<4>[146648.925379] </TASK>
<0>[146648.925436] Kernel Offset: 0x20c00000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: Fwd: [BUG] HID: sensor: custom: use-after-free of sensor_inst via open misc device after remove (hard lockup)
2026-10-23 10:42 ` Fwd: [BUG] HID: sensor: custom: use-after-free of sensor_inst via open misc device after remove (hard lockup) Семён Марьясин
@ 2026-10-23 14:12 ` Srinivas Pandruvada
2026-10-27 12:36 ` Семён Марьясин
0 siblings, 1 reply; 3+ messages in thread
From: Srinivas Pandruvada @ 2026-10-23 14:12 UTC (permalink / raw)
To: Семён Марьясин,
Jiri Kosina, Jonathan Cameron
Cc: Benjamin Tissoires, Haoxiang Li, linux-input, linux-iio
[-- Attachment #1: Type: text/plain, Size: 6221 bytes --]
Hi,
On Fri, 2026-10-23 at 13:42 +0300, Семён Марьясин wrote:
> Hello,
>
> hid-sensor-custom frees its per-sensor state (sensor_inst,
> devm-allocated) on remove while userspace
> can still hold the misc device /dev/HID-SENSOR-2000e1.N.auto open. A
> reader blocked in read() stays
> queued on sensor_inst->wait in freed memory; when it wakes up,
> finish_wait() spins on a garbage
> wait-queue lock with interrupts disabled and the machine hard-locks.
> Any read()/poll()/release() on such
> an fd after remove is a use-after-free as well.
>
Please give a try with the attached diff.
Thanks,
Srinivas
> The Sashiko AI review already flagged this as a pre-existing issue
> on Haoxiang Li's "Fix fields lifetime issues" series
> ("The /dev/HID-SENSOR-* character device operations do not
> synchronize
> with device
> removal,
> leading to Use-After-Free on devm-managed structures"):
> https://lore.kernel.org/linux-input/20260707071545.3087073-1-haoxiang_li2024@163.com/
> The applied patches don't touch it, and the current mainline is
> unchanged. Below is a real-world hit.
>
> Kernel: 7.2.8 (NixOS; drivers/hid/hid-sensor-custom.c is identical to
> current mainline,
> including ad8fb82b0442 and 3789d0802ddb)
> Hardware: Dell Latitude 5420 (Tiger Lake, Intel ISH), BIOS 1.49.0;
> Dell's vendor ISH sensors appear as
> HID-SENSOR-2000e1.N.auto (e.g. an ST VL53L1 time-of-flight
> sensor).
> Taint: O, from unrelated out-of-tree WWAN modules (qcserial,
> usb_wwan).
>
> How it was hit
> --------------
>
> 1. echo 1 > /sys/bus/platform/devices/HID-SENSOR-
> 2000e1.2.auto/enable_sensor
> 2. xxd /dev/HID-SENSOR-2000e1.2.auto # streaming reader, left
> running
> 3. modprobe -r intel_ishtp_hid intel_ish_ipc && modprobe
> intel_ish_ipc
>
> The hub went away and was re-enumerated fine; ~30 s later CPU0
> hard-locked in the still-running xxd
> and the hard-lockup detector panicked the machine (log recovered from
> EFI pstore).
>
> Presumably a minimal reproducer is just a blocked reader plus
> unbinding the platform device
> (echo HID-SENSOR-2000e1.N.auto >
> /sys/bus/platform/drivers/hid_sensor_custom/unbind),
> but only the module-reload path above was actually tested so far.
>
> Log (trimmed)
> -------------
>
> [146608.877730] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
> for response from ISHTP device
> [146611.885959] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
> for response from ISHTP device
> [146614.893909] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
> for response from ISHTP device
> [146617.901872] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
> for response from ISHTP device
> [146619.972550] ish-hid {33AECD58-B679-4E54-9BD9-A04D34F0C226}:
> [hid-ish]: enum_devices_done OK, num_hid_devices=3
> [146620.031441] hid-sensor-hub 001F:8087:0AC2.0007: hidraw3: SENSOR
> HUB HID v2.00 Device [hid-ishtp 8087:0AC2] on
> [146643.503266] iwlwifi 0000:00:14.3: Queue 3 is stuck 46 70
> [146648.924391] watchdog: CPU0: Watchdog detected hard LOCKUP on cpu
> 0
> [146648.924796] CPU: 0 UID: 0 PID: 1316004 Comm: xxd Tainted: G
> O 7.2.8 #1-NixOS PREEMPT(lazy)
> [146648.924804] Hardware name: Dell Inc. Latitude 5420/03P5X2, BIOS
> 1.49.0 09/01/2025
> [146648.924808] RIP: 0010:native_queued_spin_lock_slowpath+0x64/0x2c0
> [146648.924851] Call Trace:
> [146648.924855] <TASK>
> [146648.924857] _raw_spin_lock_irqsave+0x3d/0x50
> [146648.924872] finish_wait+0x3b/0x90
> [146648.924886] hid_sensor_custom_read+0x102/0x150
> [hid_sensor_custom]
> [146648.924901] vfs_read+0xbf/0x390
> [146648.924934] ksys_read+0xbf/0xf0
> [146648.924939] do_syscall_64+0x134/0x6b0
> [146648.925010] entry_SYSCALL_64_after_hwframe+0x77/0x7f
> [146648.925083] </TASK>
> [146648.925086] Kernel panic - not syncing: Hard LOCKUP
>
> Also attaching a full dmesg log (with only wi-fi noise filtered out).
>
> Analysis (line numbers as in mainline 73bef5)
> ---------------------------------------------
>
> - hid_sensor_custom_probe() allocates sensor_inst with devm_kzalloc()
> (line 980), so it is freed right
> after hid_sensor_custom_remove() returns, regardless of open files.
> - The misc device's fops reach sensor_inst through
> container_of(file->private_data, ..., custom_dev);
> misc_deregister() does not revoke already-open files, so
> read/poll/release keep dereferencing it.
> - hid_sensor_custom_dev_if_remove() (lines 751-758) does wake_up(),
> misc_deregister(), kfifo_free().
> The woken reader in hid_sensor_custom_read() (line 664) re-
> evaluates
> wait_event_interruptible(sensor_inst->wait,
> !kfifo_is_empty(&sensor_inst->data_fifo)), finds the fifo
> empty and goes back to sleep on sensor_inst->wait. Nothing tells it
> the device is gone.
> - Once sensor_inst is freed and reused, the next wakeup of that
> reader
> runs finish_wait() on the freed
> wait_queue_head: spin_lock_irqsave() on garbage, hard lockup.
> hid_sensor_custom_release() similarly
> does clear_bit() on freed sensor_inst->misc_opened when the fd is
> finally closed.
>
> Possible fix direction
> ----------------------
>
> Tie sensor_inst's lifetime to its users rather than to devres, the
> way
> hidraw handles disconnect:
> refcount it (kref: one reference for the device, one per open file;
> kfifo_free() and kfree() on the last
> put), add a "removed" flag set in remove() before
> wake_up_interruptible_all(), include it in the
> wait_event condition, and make read()/poll() return -ENODEV /
> EPOLLHUP
> > EPOLLERR once removed.
>
> The same review pointed out two more problems in this teardown path
> that a fix would naturally cover:
> remove() frees data_fifo before sensor_hub_remove_callback(), so a
> sample arriving in between is
> kfifo_in()'d into freed memory; and an enabled sensor is never
> sensor_hub_device_close()d on remove.
>
> I can test patches on this machine.
>
> Thanks,
> Semyon Maryasin
>
> P.S.: sorry for a possible duplicate, I think I messed with the
> headers.
[-- Attachment #2: test_patch.diff --]
[-- Type: text/x-patch, Size: 7630 bytes --]
diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
index afffea894021..c12417cddab4 100644
--- a/drivers/hid/hid-sensor-custom.c
+++ b/drivers/hid/hid-sensor-custom.c
@@ -11,6 +11,7 @@
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
+#include <linux/kref.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
@@ -37,6 +38,8 @@ struct hid_sensor_custom_field {
struct hid_sensor_custom {
struct mutex mutex;
+ struct kref ref;
+ bool removed;
struct platform_device *pdev;
struct hid_sensor_hub_device *hsdev;
struct hid_sensor_hub_callbacks callbacks;
@@ -636,6 +639,15 @@ static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom *
kfree(sensor_inst->fields);
}
+static void hid_sensor_custom_free(struct kref *ref)
+{
+ struct hid_sensor_custom *sensor_inst =
+ container_of(ref, struct hid_sensor_custom, ref);
+
+ kfifo_free(&sensor_inst->data_fifo);
+ kfree(sensor_inst);
+}
+
static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf,
size_t count, loff_t *f_ps)
{
@@ -649,22 +661,35 @@ static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf,
if (count < sizeof(struct hid_sensor_sample))
return -EINVAL;
- do {
- if (kfifo_is_empty(&sensor_inst->data_fifo)) {
- if (file->f_flags & O_NONBLOCK)
- return -EAGAIN;
+ for (;;) {
+ ret = mutex_lock_interruptible(&sensor_inst->mutex);
+ if (ret)
+ return ret;
- ret = wait_event_interruptible(sensor_inst->wait,
- !kfifo_is_empty(&sensor_inst->data_fifo));
- if (ret)
- return ret;
+ if (sensor_inst->removed) {
+ ret = -ENODEV;
+ goto unlock;
}
ret = kfifo_to_user(&sensor_inst->data_fifo, buf, count,
&copied);
+ if (ret || copied)
+ goto unlock;
+ mutex_unlock(&sensor_inst->mutex);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(sensor_inst->wait,
+ READ_ONCE(sensor_inst->removed) ||
+ !kfifo_is_empty(&sensor_inst->data_fifo));
if (ret)
return ret;
+ }
- } while (copied == 0);
+unlock:
+ mutex_unlock(&sensor_inst->mutex);
+ if (ret)
+ return ret;
return copied;
}
@@ -677,6 +702,7 @@ static int hid_sensor_custom_release(struct inode *inode, struct file *file)
struct hid_sensor_custom, custom_dev);
clear_bit(0, &sensor_inst->misc_opened);
+ kref_put(&sensor_inst->ref, hid_sensor_custom_free);
return 0;
}
@@ -684,14 +710,22 @@ static int hid_sensor_custom_release(struct inode *inode, struct file *file)
static int hid_sensor_custom_open(struct inode *inode, struct file *file)
{
struct hid_sensor_custom *sensor_inst;
+ int ret;
sensor_inst = container_of(file->private_data,
struct hid_sensor_custom, custom_dev);
- /* We essentially have single reader and writer */
- if (test_and_set_bit(0, &sensor_inst->misc_opened))
- return -EBUSY;
+ mutex_lock(&sensor_inst->mutex);
+ if (sensor_inst->removed) {
+ ret = -ENODEV;
+ } else if (test_and_set_bit(0, &sensor_inst->misc_opened)) {
+ ret = -EBUSY;
+ } else {
+ kref_get(&sensor_inst->ref);
+ ret = stream_open(inode, file);
+ }
+ mutex_unlock(&sensor_inst->mutex);
- return stream_open(inode, file);
+ return ret;
}
static __poll_t hid_sensor_custom_poll(struct file *file,
@@ -705,6 +739,9 @@ static __poll_t hid_sensor_custom_poll(struct file *file,
poll_wait(file, &sensor_inst->wait, wait);
+ if (READ_ONCE(sensor_inst->removed))
+ return EPOLLHUP | EPOLLERR;
+
if (!kfifo_is_empty(&sensor_inst->data_fifo))
mask = EPOLLIN | EPOLLRDNORM;
@@ -712,6 +749,7 @@ static __poll_t hid_sensor_custom_poll(struct file *file,
}
static const struct file_operations hid_sensor_custom_fops = {
+ .owner = THIS_MODULE,
.open = hid_sensor_custom_open,
.read = hid_sensor_custom_read,
.release = hid_sensor_custom_release,
@@ -734,20 +772,17 @@ static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst)
sensor_inst->custom_dev.name = dev_name(&sensor_inst->pdev->dev);
sensor_inst->custom_dev.fops = &hid_sensor_custom_fops;
ret = misc_register(&sensor_inst->custom_dev);
- if (ret) {
- kfifo_free(&sensor_inst->data_fifo);
- return ret;
- }
- return 0;
+ return ret;
}
static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom
*sensor_inst)
{
- wake_up(&sensor_inst->wait);
+ mutex_lock(&sensor_inst->mutex);
+ WRITE_ONCE(sensor_inst->removed, true);
+ mutex_unlock(&sensor_inst->mutex);
+ wake_up_interruptible_all(&sensor_inst->wait);
misc_deregister(&sensor_inst->custom_dev);
- kfifo_free(&sensor_inst->data_fifo);
-
}
/*
@@ -970,8 +1005,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
int ret;
const struct hid_sensor_custom_match *match;
- sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
- GFP_KERNEL);
+ sensor_inst = kzalloc_obj(*sensor_inst);
if (!sensor_inst)
return -ENOMEM;
@@ -981,6 +1015,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
sensor_inst->hsdev = hsdev;
sensor_inst->pdev = pdev;
mutex_init(&sensor_inst->mutex);
+ kref_init(&sensor_inst->ref);
platform_set_drvdata(pdev, sensor_inst);
ret = hid_sensor_custom_get_known(hsdev, &match);
@@ -992,7 +1027,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
if (ret) {
dev_err(&pdev->dev,
"register_platform_device failed\n");
- return ret;
+ goto err_free;
}
return 0;
@@ -1002,7 +1037,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
&sensor_inst->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- return ret;
+ goto err_free;
}
ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
@@ -1027,6 +1062,8 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
&enable_sensor_attr_group);
err_remove_callback:
sensor_hub_remove_callback(hsdev, hsdev->usage);
+err_free:
+ kref_put(&sensor_inst->ref, hid_sensor_custom_free);
return ret;
}
@@ -1038,14 +1075,18 @@ static void hid_sensor_custom_remove(struct platform_device *pdev)
if (sensor_inst->custom_pdev) {
platform_device_unregister(sensor_inst->custom_pdev);
- return;
+ goto out;
}
hid_sensor_custom_dev_if_remove(sensor_inst);
hid_sensor_custom_remove_attributes(sensor_inst);
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
+ if (sensor_inst->enable)
+ sensor_hub_device_close(hsdev);
sensor_hub_remove_callback(hsdev, hsdev->usage);
+out:
+ kref_put(&sensor_inst->ref, hid_sensor_custom_free);
}
static const struct platform_device_id hid_sensor_custom_ids[] = {
diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c
index 90666ff629de..2bac8b82ce37 100644
--- a/drivers/hid/hid-sensor-hub.c
+++ b/drivers/hid/hid-sensor-hub.c
@@ -173,7 +173,8 @@ int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev,
struct sensor_hub_data *pdata = hid_get_drvdata(hsdev->hdev);
unsigned long flags;
- spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
+ spin_lock_irqsave(&pdata->lock, flags);
+ spin_lock(&pdata->dyn_callback_lock);
list_for_each_entry(callback, &pdata->dyn_callback_list, list)
if (callback->usage_id == usage_id &&
callback->hsdev == hsdev) {
@@ -181,7 +182,8 @@ int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev,
kfree(callback);
break;
}
- spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+ spin_unlock(&pdata->dyn_callback_lock);
+ spin_unlock_irqrestore(&pdata->lock, flags);
return 0;
}
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: Fwd: [BUG] HID: sensor: custom: use-after-free of sensor_inst via open misc device after remove (hard lockup)
2026-10-23 14:12 ` Srinivas Pandruvada
@ 2026-10-27 12:36 ` Семён Марьясин
0 siblings, 0 replies; 3+ messages in thread
From: Семён Марьясин @ 2026-10-27 12:36 UTC (permalink / raw)
To: Srinivas Pandruvada
Cc: Jiri Kosina, Jonathan Cameron, Benjamin Tissoires, Haoxiang Li,
linux-input, linux-iio
[-- Attachment #1: Type: text/plain, Size: 7078 bytes --]
Hello! Tested this patch (had to adapt it to apply it to 7.2.8 kernel;
attaching the adapted version).
Without patch: xxd running while intel_ishtp_hid module is removed ->
xxd hangs, system either crashes or at least logs "hid-sensor-hub
001F:8087:0AC2.0004: timeout waiting for response from ISHTP device"
With patch: xxd correctly closes with message "No such device", no
error in kernel logs, no crashes — checked for several times.
пт, 23 окт. 2026 г. в 17:12, Srinivas Pandruvada
<srinivas.pandruvada@linux.intel.com>:
>
> Hi,
> On Fri, 2026-10-23 at 13:42 +0300, Семён Марьясин wrote:
> > Hello,
> >
> > hid-sensor-custom frees its per-sensor state (sensor_inst,
> > devm-allocated) on remove while userspace
> > can still hold the misc device /dev/HID-SENSOR-2000e1.N.auto open. A
> > reader blocked in read() stays
> > queued on sensor_inst->wait in freed memory; when it wakes up,
> > finish_wait() spins on a garbage
> > wait-queue lock with interrupts disabled and the machine hard-locks.
> > Any read()/poll()/release() on such
> > an fd after remove is a use-after-free as well.
> >
>
> Please give a try with the attached diff.
>
>
> Thanks,
> Srinivas
>
>
> > The Sashiko AI review already flagged this as a pre-existing issue
> > on Haoxiang Li's "Fix fields lifetime issues" series
> > ("The /dev/HID-SENSOR-* character device operations do not
> > synchronize
> > with device
> > removal,
> > leading to Use-After-Free on devm-managed structures"):
> > https://lore.kernel.org/linux-input/20260707071545.3087073-1-haoxiang_li2024@163.com/
> > The applied patches don't touch it, and the current mainline is
> > unchanged. Below is a real-world hit.
> >
> > Kernel: 7.2.8 (NixOS; drivers/hid/hid-sensor-custom.c is identical to
> > current mainline,
> > including ad8fb82b0442 and 3789d0802ddb)
> > Hardware: Dell Latitude 5420 (Tiger Lake, Intel ISH), BIOS 1.49.0;
> > Dell's vendor ISH sensors appear as
> > HID-SENSOR-2000e1.N.auto (e.g. an ST VL53L1 time-of-flight
> > sensor).
> > Taint: O, from unrelated out-of-tree WWAN modules (qcserial,
> > usb_wwan).
> >
> > How it was hit
> > --------------
> >
> > 1. echo 1 > /sys/bus/platform/devices/HID-SENSOR-
> > 2000e1.2.auto/enable_sensor
> > 2. xxd /dev/HID-SENSOR-2000e1.2.auto # streaming reader, left
> > running
> > 3. modprobe -r intel_ishtp_hid intel_ish_ipc && modprobe
> > intel_ish_ipc
> >
> > The hub went away and was re-enumerated fine; ~30 s later CPU0
> > hard-locked in the still-running xxd
> > and the hard-lockup detector panicked the machine (log recovered from
> > EFI pstore).
> >
> > Presumably a minimal reproducer is just a blocked reader plus
> > unbinding the platform device
> > (echo HID-SENSOR-2000e1.N.auto >
> > /sys/bus/platform/drivers/hid_sensor_custom/unbind),
> > but only the module-reload path above was actually tested so far.
> >
> > Log (trimmed)
> > -------------
> >
> > [146608.877730] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
> > for response from ISHTP device
> > [146611.885959] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
> > for response from ISHTP device
> > [146614.893909] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
> > for response from ISHTP device
> > [146617.901872] hid-sensor-hub 001F:8087:0AC2.0003: timeout waiting
> > for response from ISHTP device
> > [146619.972550] ish-hid {33AECD58-B679-4E54-9BD9-A04D34F0C226}:
> > [hid-ish]: enum_devices_done OK, num_hid_devices=3
> > [146620.031441] hid-sensor-hub 001F:8087:0AC2.0007: hidraw3: SENSOR
> > HUB HID v2.00 Device [hid-ishtp 8087:0AC2] on
> > [146643.503266] iwlwifi 0000:00:14.3: Queue 3 is stuck 46 70
> > [146648.924391] watchdog: CPU0: Watchdog detected hard LOCKUP on cpu
> > 0
> > [146648.924796] CPU: 0 UID: 0 PID: 1316004 Comm: xxd Tainted: G
> > O 7.2.8 #1-NixOS PREEMPT(lazy)
> > [146648.924804] Hardware name: Dell Inc. Latitude 5420/03P5X2, BIOS
> > 1.49.0 09/01/2025
> > [146648.924808] RIP: 0010:native_queued_spin_lock_slowpath+0x64/0x2c0
> > [146648.924851] Call Trace:
> > [146648.924855] <TASK>
> > [146648.924857] _raw_spin_lock_irqsave+0x3d/0x50
> > [146648.924872] finish_wait+0x3b/0x90
> > [146648.924886] hid_sensor_custom_read+0x102/0x150
> > [hid_sensor_custom]
> > [146648.924901] vfs_read+0xbf/0x390
> > [146648.924934] ksys_read+0xbf/0xf0
> > [146648.924939] do_syscall_64+0x134/0x6b0
> > [146648.925010] entry_SYSCALL_64_after_hwframe+0x77/0x7f
> > [146648.925083] </TASK>
> > [146648.925086] Kernel panic - not syncing: Hard LOCKUP
> >
> > Also attaching a full dmesg log (with only wi-fi noise filtered out).
> >
> > Analysis (line numbers as in mainline 73bef5)
> > ---------------------------------------------
> >
> > - hid_sensor_custom_probe() allocates sensor_inst with devm_kzalloc()
> > (line 980), so it is freed right
> > after hid_sensor_custom_remove() returns, regardless of open files.
> > - The misc device's fops reach sensor_inst through
> > container_of(file->private_data, ..., custom_dev);
> > misc_deregister() does not revoke already-open files, so
> > read/poll/release keep dereferencing it.
> > - hid_sensor_custom_dev_if_remove() (lines 751-758) does wake_up(),
> > misc_deregister(), kfifo_free().
> > The woken reader in hid_sensor_custom_read() (line 664) re-
> > evaluates
> > wait_event_interruptible(sensor_inst->wait,
> > !kfifo_is_empty(&sensor_inst->data_fifo)), finds the fifo
> > empty and goes back to sleep on sensor_inst->wait. Nothing tells it
> > the device is gone.
> > - Once sensor_inst is freed and reused, the next wakeup of that
> > reader
> > runs finish_wait() on the freed
> > wait_queue_head: spin_lock_irqsave() on garbage, hard lockup.
> > hid_sensor_custom_release() similarly
> > does clear_bit() on freed sensor_inst->misc_opened when the fd is
> > finally closed.
> >
> > Possible fix direction
> > ----------------------
> >
> > Tie sensor_inst's lifetime to its users rather than to devres, the
> > way
> > hidraw handles disconnect:
> > refcount it (kref: one reference for the device, one per open file;
> > kfifo_free() and kfree() on the last
> > put), add a "removed" flag set in remove() before
> > wake_up_interruptible_all(), include it in the
> > wait_event condition, and make read()/poll() return -ENODEV /
> > EPOLLHUP
> > > EPOLLERR once removed.
> >
> > The same review pointed out two more problems in this teardown path
> > that a fix would naturally cover:
> > remove() frees data_fifo before sensor_hub_remove_callback(), so a
> > sample arriving in between is
> > kfifo_in()'d into freed memory; and an enabled sensor is never
> > sensor_hub_device_close()d on remove.
> >
> > I can test patches on this machine.
> >
> > Thanks,
> > Semyon Maryasin
> >
> > P.S.: sorry for a possible duplicate, I think I messed with the
> > headers.
--
С уважением,
Семён Марьясин
[-- Attachment #2: test_patch.diff --]
[-- Type: text/x-patch, Size: 7671 bytes --]
diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
index c2b425a..fe67e4b 100644
--- a/drivers/hid/hid-sensor-custom.c
+++ b/drivers/hid/hid-sensor-custom.c
@@ -11,6 +11,7 @@
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
+#include <linux/kref.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/poll.h>
@@ -37,6 +38,8 @@ struct hid_sensor_custom_field {
struct hid_sensor_custom {
struct mutex mutex;
+ struct kref ref;
+ bool removed;
struct platform_device *pdev;
struct hid_sensor_hub_device *hsdev;
struct hid_sensor_hub_callbacks callbacks;
@@ -643,6 +646,15 @@ static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom *
kfree(sensor_inst->fields);
}
+static void hid_sensor_custom_free(struct kref *ref)
+{
+ struct hid_sensor_custom *sensor_inst =
+ container_of(ref, struct hid_sensor_custom, ref);
+
+ kfifo_free(&sensor_inst->data_fifo);
+ kfree(sensor_inst);
+}
+
static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf,
size_t count, loff_t *f_ps)
{
@@ -656,22 +668,35 @@ static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf,
if (count < sizeof(struct hid_sensor_sample))
return -EINVAL;
- do {
- if (kfifo_is_empty(&sensor_inst->data_fifo)) {
- if (file->f_flags & O_NONBLOCK)
- return -EAGAIN;
+ for (;;) {
+ ret = mutex_lock_interruptible(&sensor_inst->mutex);
+ if (ret)
+ return ret;
- ret = wait_event_interruptible(sensor_inst->wait,
- !kfifo_is_empty(&sensor_inst->data_fifo));
- if (ret)
- return ret;
+ if (sensor_inst->removed) {
+ ret = -ENODEV;
+ goto unlock;
}
ret = kfifo_to_user(&sensor_inst->data_fifo, buf, count,
&copied);
+ if (ret || copied)
+ goto unlock;
+ mutex_unlock(&sensor_inst->mutex);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(sensor_inst->wait,
+ READ_ONCE(sensor_inst->removed) ||
+ !kfifo_is_empty(&sensor_inst->data_fifo));
if (ret)
return ret;
+ }
- } while (copied == 0);
+unlock:
+ mutex_unlock(&sensor_inst->mutex);
+ if (ret)
+ return ret;
return copied;
}
@@ -684,6 +709,7 @@ static int hid_sensor_custom_release(struct inode *inode, struct file *file)
struct hid_sensor_custom, custom_dev);
clear_bit(0, &sensor_inst->misc_opened);
+ kref_put(&sensor_inst->ref, hid_sensor_custom_free);
return 0;
}
@@ -691,14 +717,22 @@ static int hid_sensor_custom_release(struct inode *inode, struct file *file)
static int hid_sensor_custom_open(struct inode *inode, struct file *file)
{
struct hid_sensor_custom *sensor_inst;
+ int ret;
sensor_inst = container_of(file->private_data,
struct hid_sensor_custom, custom_dev);
- /* We essentially have single reader and writer */
- if (test_and_set_bit(0, &sensor_inst->misc_opened))
- return -EBUSY;
+ mutex_lock(&sensor_inst->mutex);
+ if (sensor_inst->removed) {
+ ret = -ENODEV;
+ } else if (test_and_set_bit(0, &sensor_inst->misc_opened)) {
+ ret = -EBUSY;
+ } else {
+ kref_get(&sensor_inst->ref);
+ ret = stream_open(inode, file);
+ }
+ mutex_unlock(&sensor_inst->mutex);
- return stream_open(inode, file);
+ return ret;
}
static __poll_t hid_sensor_custom_poll(struct file *file,
@@ -712,6 +746,9 @@ static __poll_t hid_sensor_custom_poll(struct file *file,
poll_wait(file, &sensor_inst->wait, wait);
+ if (READ_ONCE(sensor_inst->removed))
+ return EPOLLHUP | EPOLLERR;
+
if (!kfifo_is_empty(&sensor_inst->data_fifo))
mask = EPOLLIN | EPOLLRDNORM;
@@ -719,6 +756,7 @@ static __poll_t hid_sensor_custom_poll(struct file *file,
}
static const struct file_operations hid_sensor_custom_fops = {
+ .owner = THIS_MODULE,
.open = hid_sensor_custom_open,
.read = hid_sensor_custom_read,
.release = hid_sensor_custom_release,
@@ -741,20 +779,17 @@ static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst)
sensor_inst->custom_dev.name = dev_name(&sensor_inst->pdev->dev);
sensor_inst->custom_dev.fops = &hid_sensor_custom_fops;
ret = misc_register(&sensor_inst->custom_dev);
- if (ret) {
- kfifo_free(&sensor_inst->data_fifo);
- return ret;
- }
- return 0;
+ return ret;
}
static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom
*sensor_inst)
{
- wake_up(&sensor_inst->wait);
+ mutex_lock(&sensor_inst->mutex);
+ WRITE_ONCE(sensor_inst->removed, true);
+ mutex_unlock(&sensor_inst->mutex);
+ wake_up_interruptible_all(&sensor_inst->wait);
misc_deregister(&sensor_inst->custom_dev);
- kfifo_free(&sensor_inst->data_fifo);
-
}
/*
@@ -977,8 +1012,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
int ret;
const struct hid_sensor_custom_match *match;
- sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
- GFP_KERNEL);
+ sensor_inst = kzalloc_obj(*sensor_inst);
if (!sensor_inst)
return -ENOMEM;
@@ -988,6 +1022,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
sensor_inst->hsdev = hsdev;
sensor_inst->pdev = pdev;
mutex_init(&sensor_inst->mutex);
+ kref_init(&sensor_inst->ref);
platform_set_drvdata(pdev, sensor_inst);
ret = hid_sensor_custom_get_known(hsdev, &match);
@@ -999,7 +1034,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
if (ret) {
dev_err(&pdev->dev,
"register_platform_device failed\n");
- return ret;
+ goto err_free;
}
return 0;
@@ -1009,7 +1044,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
&sensor_inst->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- return ret;
+ goto err_free;
}
ret = hid_sensor_custom_add_attributes(sensor_inst);
@@ -1034,6 +1069,8 @@ err_remove_attributes:
hid_sensor_custom_remove_attributes(sensor_inst);
err_remove_callback:
sensor_hub_remove_callback(hsdev, hsdev->usage);
+err_free:
+ kref_put(&sensor_inst->ref, hid_sensor_custom_free);
return ret;
}
@@ -1045,7 +1082,7 @@ static void hid_sensor_custom_remove(struct platform_device *pdev)
if (sensor_inst->custom_pdev) {
platform_device_unregister(sensor_inst->custom_pdev);
- return;
+ goto out;
}
hid_sensor_custom_dev_if_remove(sensor_inst);
@@ -1053,7 +1090,11 @@ static void hid_sensor_custom_remove(struct platform_device *pdev)
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
hid_sensor_custom_remove_attributes(sensor_inst);
+ if (sensor_inst->enable)
+ sensor_hub_device_close(hsdev);
sensor_hub_remove_callback(hsdev, hsdev->usage);
+out:
+ kref_put(&sensor_inst->ref, hid_sensor_custom_free);
}
static const struct platform_device_id hid_sensor_custom_ids[] = {
diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c
index 6470a29..f270b59 100644
--- a/drivers/hid/hid-sensor-hub.c
+++ b/drivers/hid/hid-sensor-hub.c
@@ -173,7 +173,8 @@ int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev,
struct sensor_hub_data *pdata = hid_get_drvdata(hsdev->hdev);
unsigned long flags;
- spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
+ spin_lock_irqsave(&pdata->lock, flags);
+ spin_lock(&pdata->dyn_callback_lock);
list_for_each_entry(callback, &pdata->dyn_callback_list, list)
if (callback->usage_id == usage_id &&
callback->hsdev == hsdev) {
@@ -181,7 +182,8 @@ int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev,
kfree(callback);
break;
}
- spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+ spin_unlock(&pdata->dyn_callback_lock);
+ spin_unlock_irqrestore(&pdata->lock, flags);
return 0;
}
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-27 12:36 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <CAM2dg7eZJEii+fJT1TG8XME-TJFqDtURCsr5XS71E5-5-mUV0A@mail.gmail.com>
2026-10-23 10:42 ` Fwd: [BUG] HID: sensor: custom: use-after-free of sensor_inst via open misc device after remove (hard lockup) Семён Марьясин
2026-10-23 14:12 ` Srinivas Pandruvada
2026-10-27 12:36 ` Семён Марьясин
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox