* [PATCH 0/2] HID: bpf: close device teardown races @ 2026-11-02 11:58 Yifan Zhang 2026-11-02 11:58 ` [PATCH 1/2] HID: bpf: serialize hardware operations with device destruction Yifan Zhang 2026-11-02 11:58 ` [PATCH 2/2] HID: bpf: reject struct_ops registration during " Yifan Zhang 0 siblings, 2 replies; 6+ messages in thread From: Yifan Zhang @ 2026-11-02 11:58 UTC (permalink / raw) To: Jiri Kosina, Benjamin Tissoires, linux-input Cc: Greg Kroah-Hartman, Alexei Starovoitov, bpf, linux-kernel HID-BPF can race device destruction in two paths. A hardware request can outlive transport-private state, and a struct_ops registration can resume after destruction has cleaned the device's SRCU state. Serialize HID-BPF hardware operations with the destruction transition, then reject registrations that acquire prog_list_lock after that transition. Yifan Zhang (2): HID: bpf: serialize hardware operations with device destruction HID: bpf: reject struct_ops registration during device destruction drivers/hid/bpf/hid_bpf_dispatch.c | 36 +++++++++++++++++++--------- drivers/hid/bpf/hid_bpf_struct_ops.c | 5 ++++ 2 files changed, 30 insertions(+), 11 deletions(-) -- 2.39.5 (Apple Git-154) ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] HID: bpf: serialize hardware operations with device destruction 2026-11-02 11:58 [PATCH 0/2] HID: bpf: close device teardown races Yifan Zhang @ 2026-11-02 11:58 ` Yifan Zhang 2026-11-02 12:15 ` sashiko-bot 2026-11-02 16:21 ` Alexei Starovoitov 2026-11-02 11:58 ` [PATCH 2/2] HID: bpf: reject struct_ops registration during " Yifan Zhang 1 sibling, 2 replies; 6+ messages in thread From: Yifan Zhang @ 2026-11-02 11:58 UTC (permalink / raw) To: Jiri Kosina, Benjamin Tissoires, linux-input Cc: Greg Kroah-Hartman, Alexei Starovoitov, bpf, linux-kernel 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 <yfzhang23@stu.pku.edu.cn> --- 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) ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] HID: bpf: serialize hardware operations with device destruction 2026-11-02 11:58 ` [PATCH 1/2] HID: bpf: serialize hardware operations with device destruction Yifan Zhang @ 2026-11-02 12:15 ` sashiko-bot 2026-11-02 16:21 ` Alexei Starovoitov 1 sibling, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-11-02 12:15 UTC (permalink / raw) To: Yifan Zhang, linux-input; +Cc: bpf, dmitry.torokhov Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [Critical] HID: bpf: report UAF in __hid_bpf_hw_check_params() during device teardown - [High] HID: bpf: ABBA deadlock between prog_list_lock and srcu in hw request kfuncs - [High] HID: bpf: recursive prog_list_lock deadlock in hid_bpf_hw_request() - [High] HID: bpf: lock inversion between prog_list_lock and driver_input_lock -- commit 49a737ce63da80cb915d4530a8a28ce52af759cc Author: Yifan Zhang <yfzhang23@stu.pku.edu.cn> HID: bpf: serialize hardware operations with device destruction This commit serializes HID-BPF hardware requests and output reports with device destruction by holding prog_list_lock and checking the destroyed flag across low-level driver calls in hid_bpf_hw_request() and hid_bpf_hw_output_report(). > 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); [Severity: High] Can acquiring ctx->hid->bpf.prog_list_lock here in hid_bpf_hw_request() (and below in hid_bpf_hw_output_report()) in drivers/hid/bpf/hid_bpf_dispatch.c cause an ABBA deadlock with hdev->bpf.srcu? In drivers/hid/bpf/hid_bpf_struct_ops.c, both hid_bpf_reg() and hid_bpf_unreg() hold prog_list_lock while waiting on synchronize_srcu(): drivers/hid/bpf/hid_bpf_struct_ops.c:hid_bpf_unreg() { ... mutex_lock(&hdev->bpf.prog_list_lock); ... list_del_rcu(&ops->list); synchronize_srcu(&hdev->bpf.srcu); ... mutex_unlock(&hdev->bpf.prog_list_lock); } When hid_bpf_hw_request() or hid_bpf_hw_output_report() is invoked from a syscall BPF program, it also takes prog_list_lock and then calls __hid_hw_raw_request() or __hid_hw_output_report(), which enters dispatch_hid_bpf_raw_requests() or dispatch_hid_bpf_output_report() and takes srcu_read_lock(&hdev->bpf.srcu). Both paths establish the lock order prog_list_lock -> hdev->bpf.srcu. However, hid_bpf_hw_request() and hid_bpf_hw_output_report() are also registered in hid_bpf_kfunc_ids for BPF_PROG_TYPE_STRUCT_OPS, so sleepable struct_ops callbacks (e->hid_hw_request and e->hid_hw_output_report) can call them directly. When a non-BPF caller such as hidraw invokes __hid_hw_raw_request(..., from_bpf=false) or __hid_hw_output_report(..., from_bpf=false), dispatch_hid_bpf_raw_requests() or dispatch_hid_bpf_output_report() holds srcu_read_lock(&hdev->bpf.srcu) across the callback: drivers/hid/bpf/hid_bpf_dispatch.c:dispatch_hid_bpf_raw_requests() { ... idx = srcu_read_lock(&hdev->bpf.srcu); list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list, srcu_read_lock_held(&hdev->bpf.srcu)) { if (!e->hid_hw_request) continue; ret = e->hid_hw_request(&ctx_kern.ctx, reportnum, rtype, reqtype, source); if (ret) goto out; } ret = 0; out: srcu_read_unlock(&hdev->bpf.srcu, idx); return ret; } When that struct_ops callback calls hid_bpf_hw_request() or hid_bpf_hw_output_report(), it acquires prog_list_lock while holding srcu_read_lock(&hdev->bpf.srcu), inverting the lock order to hdev->bpf.srcu -> prog_list_lock and deadlocking if another thread is in hid_bpf_reg() or hid_bpf_unreg(). [Severity: High] Could holding ctx->hid->bpf.prog_list_lock across hid_ops->hid_hw_raw_request() in hid_bpf_hw_request() (and across hid_ops->hid_hw_output_report() in hid_bpf_hw_output_report()) also lead to a recursive mutex self-deadlock on the same task? When hid_ops->hid_hw_raw_request(..., true) or hid_ops->hid_hw_output_report(..., true) runs with prog_list_lock held, __hid_hw_raw_request() and __hid_hw_output_report() still invoke dispatch_hid_bpf_raw_requests() and dispatch_hid_bpf_output_report(), which call any attached sleepable struct_ops e->hid_hw_request or e->hid_hw_output_report programs on that hid_device. The recursion check at the start of hid_bpf_hw_request() and hid_bpf_hw_output_report() only inspects ctx_kern->from_bpf on the passed context: drivers/hid/bpf/hid_bpf_dispatch.c:hid_bpf_hw_request() { ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx); if (ctx_kern->from_bpf) return -EDEADLOCK; ... } Because hid_bpf_allocate_context() is exposed in hid_bpf_kfunc_ids to sleepable struct_ops programs and returns a fresh context with from_bpf == false, wouldn't a struct_ops program that calls hid_bpf_allocate_context(hctx->hid->id) and passes the new context to hid_bpf_hw_request() or hid_bpf_hw_output_report() bypass the ctx_kern->from_bpf check and try to lock ctx->hid->bpf.prog_list_lock a second time? [Severity: High] Does holding ctx->hid->bpf.prog_list_lock across hid_ops->hid_hw_raw_request() and hid_ops->hid_hw_output_report() in drivers/hid/bpf/hid_bpf_dispatch.c introduce a lock inversion with hdev->driver_input_lock? In drivers/hid/hid-core.c, hid_device_probe() and hid_device_remove() acquire hdev->driver_input_lock before calling __hid_device_probe() (which runs call_hid_bpf_rdesc_fixup() and hdrv->probe()) and hdrv->remove(). If a sleepable struct_ops callback (such as hid_rdesc_fixup, hid_hw_request, or hid_hw_output_report) invoked under driver_input_lock calls hid_bpf_hw_request() or hid_bpf_hw_output_report(), it acquires prog_list_lock while holding driver_input_lock (driver_input_lock -> prog_list_lock). Conversely, while hid_bpf_hw_request() or hid_bpf_hw_output_report() holds prog_list_lock across hid_ops->hid_hw_raw_request() or hid_ops->hid_hw_output_report(), an attached sleepable struct_ops callback e->hid_hw_request or e->hid_hw_output_report can call hid_bpf_input_report(), which is exposed in hid_bpf_kfunc_ids and acquires driver_input_lock before checking ctx_kern->from_bpf: drivers/hid/bpf/hid_bpf_dispatch.c:hid_bpf_input_report() { ... ret = down_interruptible(&ctx->hid->driver_input_lock); if (ret) return ret; ... } Wouldn't that establish the reverse ordering prog_list_lock -> driver_input_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); [Severity: Critical] Is checking ctx->hid->bpf.destroyed after __hid_bpf_hw_check_params() in hid_bpf_hw_request() and hid_bpf_hw_output_report() sufficient to prevent a use-after-free on struct hid_report during device teardown? In drivers/hid/bpf/hid_bpf_dispatch.c, both hid_bpf_hw_request() and hid_bpf_hw_output_report() call __hid_bpf_hw_check_params() before acquiring ctx->hid->bpf.prog_list_lock and before checking ctx->hid->bpf.destroyed: drivers/hid/bpf/hid_bpf_dispatch.c:hid_bpf_hw_request() { ... /* check arguments */ ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); if (ret) return ret; ... mutex_lock(&ctx->hid->bpf.prog_list_lock); if (READ_ONCE(ctx->hid->bpf.destroyed)) { ... } __hid_bpf_hw_check_params() looks up and dereferences struct hid_report from ctx->hid->report_enum: drivers/hid/bpf/hid_bpf_dispatch.c:__hid_bpf_hw_check_params() { ... report_enum = ctx->hid->report_enum + rtype; report = hid_ops->hid_get_report(report_enum, buf); if (!report) return -EINVAL; report_len = hid_report_len(report); ... } During hid_destroy_device() in drivers/hid/hid-core.c, hid_bpf_destroy_device() sets hdev->bpf.destroyed = true and unlocks prog_list_lock, and then hid_remove_device() -> device_del() -> hid_device_remove() -> hid_close_report() frees each report in report_enum->report_id_hash[] before zeroing report_enum: drivers/hid/hid-core.c:hid_close_report() { ... for (i = 0; i < HID_REPORT_TYPES; i++) { struct hid_report_enum *report_enum = device->report_enum + i; for (j = 0; j < HID_MAX_IDS; j++) { struct hid_report *report = report_enum->report_id_hash[j]; if (report) hid_free_report(report); } memset(report_enum, 0, sizeof(*report_enum)); } } Because a syscall BPF context allocated by hid_bpf_allocate_context() only pins struct hid_device via get_device(&hdev->dev), can a concurrent call to hid_bpf_hw_request() or hid_bpf_hw_output_report() race with hid_close_report() inside __hid_bpf_hw_check_params() and dereference a freed struct hid_report before reaching the prog_list_lock and destroyed check? -- Sashiko AI review · https://sashiko.dev/#/patchset/179362071770.29269.16022650793604369465@stu.pku.edu.cn?part=1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] HID: bpf: serialize hardware operations with device destruction 2026-11-02 11:58 ` [PATCH 1/2] HID: bpf: serialize hardware operations with device destruction Yifan Zhang 2026-11-02 12:15 ` sashiko-bot @ 2026-11-02 16:21 ` Alexei Starovoitov 1 sibling, 0 replies; 6+ messages in thread From: Alexei Starovoitov @ 2026-11-02 16:21 UTC (permalink / raw) To: Yifan Zhang, Jiri Kosina, Benjamin Tissoires, linux-input Cc: Greg Kroah-Hartman, bpf, linux-kernel On Mon, Nov 02, 2026 at 03:58 AM Yifan Zhang <yfzhang23@stu.pku.edu.cn> wrote: > 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. sashiko is right. This will deadlock. pw-bot: cr ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/2] HID: bpf: reject struct_ops registration during device destruction 2026-11-02 11:58 [PATCH 0/2] HID: bpf: close device teardown races Yifan Zhang 2026-11-02 11:58 ` [PATCH 1/2] HID: bpf: serialize hardware operations with device destruction Yifan Zhang @ 2026-11-02 11:58 ` Yifan Zhang 2026-11-02 12:13 ` sashiko-bot 1 sibling, 1 reply; 6+ messages in thread From: Yifan Zhang @ 2026-11-02 11:58 UTC (permalink / raw) To: Jiri Kosina, Benjamin Tissoires, linux-input Cc: Greg Kroah-Hartman, Alexei Starovoitov, bpf, linux-kernel hid_bpf_reg() can obtain a device reference before hid_destroy_device() removes the device from the bus, then block on prog_list_lock while HID-BPF destruction cleans the device's SRCU state. The registration path does not check the destroyed flag after taking the lock, so it can publish a new struct_ops entry and call synchronize_srcu() on the cleaned SRCU object. KASAN reports: BUG: KASAN: slab-use-after-free in check_init_srcu_struct Read of size 8 check_init_srcu_struct synchronize_srcu hid_bpf_reg bpf_struct_ops_link_create Check destroyed while holding prog_list_lock. The preceding teardown serialization change sets the flag while holding the same lock, so either a registration completes before destruction starts or it fails with -ENODEV without accessing SRCU. The reproducer no longer triggers a KASAN report with this change. Fixes: ebc0d8093e8c ("HID: bpf: implement HID-BPF through bpf_struct_ops") Closes: https://lore.kernel.org/linux-input/179317460937.87455.1799017093083467190@stu.pku.edu.cn/ Signed-off-by: Yifan Zhang <yfzhang23@stu.pku.edu.cn> --- drivers/hid/bpf/hid_bpf_struct_ops.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c index c90b68956cb3b..4c433f419c85e 100644 --- a/drivers/hid/bpf/hid_bpf_struct_ops.c +++ b/drivers/hid/bpf/hid_bpf_struct_ops.c @@ -201,6 +201,11 @@ static int hid_bpf_reg(void *kdata, struct bpf_link *link) mutex_lock(&hdev->bpf.prog_list_lock); + if (READ_ONCE(hdev->bpf.destroyed)) { + err = -ENODEV; + goto out_unlock; + } + count = list_count_nodes(&hdev->bpf.prog_list); if (count >= HID_BPF_MAX_PROGS_PER_DEV) { err = -E2BIG; -- 2.39.5 (Apple Git-154) ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] HID: bpf: reject struct_ops registration during device destruction 2026-11-02 11:58 ` [PATCH 2/2] HID: bpf: reject struct_ops registration during " Yifan Zhang @ 2026-11-02 12:13 ` sashiko-bot 0 siblings, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-11-02 12:13 UTC (permalink / raw) To: Yifan Zhang, linux-input; +Cc: bpf, dmitry.torokhov Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Critical] HID: bpf: dangling ops->hdev pointer on error path in hid_bpf_reg() - [High] HID: bpf: deadlock on prog_list_lock and srcu in hid_bpf_hw_request() -- commit 318c71f825ad280448e4c11859ac62903f968c18 Author: Yifan Zhang <yfzhang23@stu.pku.edu.cn> HID: bpf: reject struct_ops registration during device destruction This series serializes HID-BPF hardware requests and output reports against device teardown using prog_list_lock and the destroyed flag, and rejects new struct_ops registrations once device destruction has started. > 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); [Severity: High] Can taking prog_list_lock in hid_bpf_hw_request() and hid_bpf_hw_output_report() deadlock against hdev->bpf.srcu or against itself? In drivers/hid/bpf/hid_bpf_struct_ops.c, hid_bpf_reg() and hid_bpf_unreg() acquire prog_list_lock and then wait in synchronize_srcu(): drivers/hid/bpf/hid_bpf_struct_ops.c:hid_bpf_reg() { mutex_lock(&hdev->bpf.prog_list_lock); ... synchronize_srcu(&hdev->bpf.srcu); } Meanwhile, in drivers/hid/bpf/hid_bpf_dispatch.c, dispatch_hid_bpf_raw_requests() and dispatch_hid_bpf_output_report() hold srcu_read_lock(&hdev->bpf.srcu) while invoking sleepable struct_ops programs (e->hid_hw_request and e->hid_hw_output_report): drivers/hid/bpf/hid_bpf_dispatch.c:dispatch_hid_bpf_raw_requests() { idx = srcu_read_lock(&hdev->bpf.srcu); list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list, srcu_read_lock_held(&hdev->bpf.srcu)) { if (!e->hid_hw_request) continue; ret = e->hid_hw_request(&ctx_kern.ctx, reportnum, rtype, reqtype, source); ... } srcu_read_unlock(&hdev->bpf.srcu, idx); } Those sleepable callbacks are permitted to call the kfuncs hid_bpf_hw_request() and hid_bpf_hw_output_report() (as done in tools/testing/selftests/hid/progs/hid.c). With prog_list_lock taken inside those kfuncs, a task running in dispatch_hid_bpf_raw_requests() or dispatch_hid_bpf_output_report() will lock srcu -> prog_list_lock, while a concurrent hid_bpf_reg() or hid_bpf_unreg() locks prog_list_lock -> srcu, causing an ABBA deadlock. Additionally, because prog_list_lock is held across hid_ops->hid_hw_raw_request() and hid_ops->hid_hw_output_report(), which invoke dispatch_hid_bpf_raw_requests() and dispatch_hid_bpf_output_report() on the same task, if an attached sleepable struct_ops program allocates a fresh context via hid_bpf_allocate_context() (where from_bpf is false) and calls hid_bpf_hw_request() or hid_bpf_hw_output_report() on the same device, the task will attempt to acquire prog_list_lock a second time and self-deadlock. [ ... ] > diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c > index c90b68956cb3b..4c433f419c85e 100644 > --- a/drivers/hid/bpf/hid_bpf_struct_ops.c > +++ b/drivers/hid/bpf/hid_bpf_struct_ops.c > @@ -201,6 +201,11 @@ static int hid_bpf_reg(void *kdata, struct bpf_link *link) > > mutex_lock(&hdev->bpf.prog_list_lock); > > + if (READ_ONCE(hdev->bpf.destroyed)) { > + err = -ENODEV; > + goto out_unlock; > + } [Severity: Critical] Does jumping to out_unlock on error in hid_bpf_reg() leave a dangling pointer in ops->hdev after hid_put_device(hdev) is called? In drivers/hid/bpf/hid_bpf_struct_ops.c, hid_bpf_reg() assigns ops->hdev = hdev before acquiring prog_list_lock, and the error path under out_unlock drops the hdev reference without clearing ops->hdev: drivers/hid/bpf/hid_bpf_struct_ops.c:hid_bpf_reg() { hdev = hid_get_device(ops->hid_id); if (IS_ERR(hdev)) return PTR_ERR(hdev); ops->hdev = hdev; mutex_lock(&hdev->bpf.prog_list_lock); if (READ_ONCE(hdev->bpf.destroyed)) { err = -ENODEV; goto out_unlock; } ... out_unlock: mutex_unlock(&hdev->bpf.prog_list_lock); if (err) { if (hdev->bpf.rdesc_ops == ops) hdev->bpf.rdesc_ops = NULL; hid_put_device(hdev); } } When bpf_struct_ops_link_create() fails in st_ops->reg(), the struct_ops map's kdata is not reset, so leaving ops->hdev non-NULL causes any subsequent attach attempt on the map to fail at if (ops->hdev) return -EINVAL. More importantly, if the map is already attached via an active bpf_link while hid_destroy_device(hdev) runs, hid_bpf_destroy_device() calls __hid_bpf_ops_destroy_device(hdev) (which clears ops->hdev = NULL and drops the active link's hdev reference) before hid_remove_device(hdev) removes the device from hid_bus_type. If a second bpf_struct_ops_link_create() on the same map races in this window, it passes if (ops->hdev), gets a temporary reference via hid_get_device(), sets ops->hdev = hdev, hits the new hdev->bpf.destroyed check, and drops the temporary reference via hid_put_device(hdev) while leaving ops->hdev pointing to hdev. Once hid_destroy_device() frees hdev, closing the first bpf_link calls hid_bpf_unreg(), which sees ops->hdev != NULL and dereferences the freed hdev. Should ops->hdev be cleared in the if (err) block (or assigned only after the error checks succeed)? -- Sashiko AI review · https://sashiko.dev/#/patchset/179362071770.29269.16022650793604369465@stu.pku.edu.cn?part=2 ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-02 16:21 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-02 11:58 [PATCH 0/2] HID: bpf: close device teardown races Yifan Zhang 2026-11-02 11:58 ` [PATCH 1/2] HID: bpf: serialize hardware operations with device destruction Yifan Zhang 2026-11-02 12:15 ` sashiko-bot 2026-11-02 16:21 ` Alexei Starovoitov 2026-11-02 11:58 ` [PATCH 2/2] HID: bpf: reject struct_ops registration during " Yifan Zhang 2026-11-02 12:13 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox