A HID-BPF syscall context pins struct hid_device, but it does not pin the transport-private data referenced by the low-level driver. A hardware request can therefore sleep in the UHID report-wait path while closing the UHID file destroys the HID device, wakes the waiter, and frees struct uhid_device. The awakened request then accesses the freed object. KASAN reports: BUG: KASAN: use-after-free in __uhid_report_queue_and_wait Read of size 1 uhid_hid_raw_request __hid_hw_raw_request hid_bpf_hw_request bpf_prog_test_run_syscall Hold prog_list_lock across HID-BPF hardware operations. Device destruction sets destroyed while holding the same lock, so it waits for an operation that has already started. Later operations return -ENODEV before reaching the low-level driver. Apply the same lifetime rule to output reports. The reproducer completed 100 close-versus-request trials without a KASAN report with this change. Fixes: 91a7f802d185 ("HID: bpf: introduce hid_hw_request()") Closes: https://lore.kernel.org/linux-input/179317460423.87432.1451672332362541157@stu.pku.edu.cn/ Signed-off-by: Yifan Zhang --- drivers/hid/bpf/hid_bpf_dispatch.c | 36 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 6b467c74601cb..4fea42dd2f392 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -444,14 +444,20 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, if (!dma_data) return -ENOMEM; - ret = hid_ops->hid_hw_raw_request(ctx->hid, - dma_data[0], - dma_data, - size, - rtype, - reqtype, - (u64)(long)ctx, - true); /* prevent infinite recursions */ + mutex_lock(&ctx->hid->bpf.prog_list_lock); + if (READ_ONCE(ctx->hid->bpf.destroyed)) { + ret = -ENODEV; + } else { + ret = hid_ops->hid_hw_raw_request(ctx->hid, + dma_data[0], + dma_data, + size, + rtype, + reqtype, + (u64)(long)ctx, + true); /* prevent infinite recursions */ + } + mutex_unlock(&ctx->hid->bpf.prog_list_lock); if (ret > (ssize_t)size) ret = size; @@ -492,7 +498,13 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) if (!dma_data) return -ENOMEM; - ret = hid_ops->hid_hw_output_report(ctx->hid, dma_data, size, (u64)(long)ctx, true); + mutex_lock(&ctx->hid->bpf.prog_list_lock); + if (READ_ONCE(ctx->hid->bpf.destroyed)) + ret = -ENODEV; + else + ret = hid_ops->hid_hw_output_report(ctx->hid, dma_data, size, + (u64)(long)ctx, true); + mutex_unlock(&ctx->hid->bpf.prog_list_lock); kfree(dma_data); return ret; @@ -641,8 +653,10 @@ void hid_bpf_destroy_device(struct hid_device *hdev) if (!hdev) return; - /* mark the device as destroyed in bpf so we don't reattach it */ - hdev->bpf.destroyed = true; + /* Block new BPF hardware operations before transport teardown. */ + mutex_lock(&hdev->bpf.prog_list_lock); + WRITE_ONCE(hdev->bpf.destroyed, true); + mutex_unlock(&hdev->bpf.prog_list_lock); __hid_bpf_ops_destroy_device(hdev); -- 2.39.5 (Apple Git-154)