* [PATCH v2 1/2] HID: bpf: synchronize hardware operations with device destruction
2026-11-03 2:45 [PATCH v2 0/2] HID: bpf: fix device destruction races Yifan Zhang
@ 2026-11-03 2:45 ` Yifan Zhang
2026-11-03 3:00 ` Alexei Starovoitov
2026-11-03 2:45 ` [PATCH v2 2/2] HID: bpf: reject struct_ops registration during " Yifan Zhang
1 sibling, 1 reply; 4+ messages in thread
From: Yifan Zhang @ 2026-11-03 2:45 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
Add a lifetime lock that serializes admission of HID-BPF hardware
operations with device destruction. Take an SRCU read lock while holding
the lifetime lock before accessing the report tables or the low-level
driver. Destruction marks the device destroyed under the same lock and
waits for the SRCU readers before cleaning up the SRCU state.
The lifetime lock is released before calling the low-level driver. This
avoids reacquiring the same mutex through a reentrant low-level path and
does not introduce a lock dependency on driver_input_lock.
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 | 73 ++++++++++++++++++++++--------
include/linux/hid_bpf.h | 1 +
2 files changed, 55 insertions(+), 19 deletions(-)
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
index 6b467c74601cb..0c0c8e9939f05 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.c
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -398,6 +398,18 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz,
return 0;
}
+static int hid_bpf_hw_operation_lock(struct hid_device *hdev)
+{
+ int idx = -ENODEV;
+
+ mutex_lock(&hdev->bpf.lifetime_lock);
+ if (!READ_ONCE(hdev->bpf.destroyed))
+ idx = srcu_read_lock(&hdev->bpf.srcu);
+ mutex_unlock(&hdev->bpf.lifetime_lock);
+
+ return idx;
+}
+
/**
* hid_bpf_hw_request - Communicate with a HID device
*
@@ -416,17 +428,21 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
struct hid_bpf_ctx_kern *ctx_kern;
size_t size = buf__sz;
u8 *dma_data;
- int ret;
+ int idx, ret;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
if (ctx_kern->from_bpf)
return -EDEADLOCK;
+ idx = hid_bpf_hw_operation_lock(ctx->hid);
+ if (idx < 0)
+ return idx;
+
/* check arguments */
ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true);
if (ret)
- return ret;
+ goto out_unlock;
switch (reqtype) {
case HID_REQ_GET_REPORT:
@@ -437,21 +453,24 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
case HID_REQ_SET_PROTOCOL:
break;
default:
- return -EINVAL;
+ ret = -EINVAL;
+ goto out_unlock;
}
dma_data = kmemdup(buf, size, GFP_KERNEL);
- if (!dma_data)
- return -ENOMEM;
+ if (!dma_data) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
ret = hid_ops->hid_hw_raw_request(ctx->hid,
- dma_data[0],
- dma_data,
- size,
- rtype,
- reqtype,
- (u64)(long)ctx,
- true); /* prevent infinite recursions */
+ dma_data[0],
+ dma_data,
+ size,
+ rtype,
+ reqtype,
+ (u64)(long)ctx,
+ true); /* prevent infinite recursions */
if (ret > (ssize_t)size)
ret = size;
@@ -459,6 +478,8 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
memcpy(buf, dma_data, ret);
kfree(dma_data);
+out_unlock:
+ srcu_read_unlock(&ctx->hid->bpf.srcu, idx);
return ret;
}
@@ -477,24 +498,33 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
struct hid_bpf_ctx_kern *ctx_kern;
size_t size = buf__sz;
u8 *dma_data;
- int ret;
+ int idx, ret;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
if (ctx_kern->from_bpf)
return -EDEADLOCK;
+ idx = hid_bpf_hw_operation_lock(ctx->hid);
+ if (idx < 0)
+ return idx;
+
/* check arguments */
ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true);
if (ret)
- return ret;
+ goto out_unlock;
dma_data = kmemdup(buf, size, GFP_KERNEL);
- if (!dma_data)
- return -ENOMEM;
+ if (!dma_data) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
- ret = hid_ops->hid_hw_output_report(ctx->hid, dma_data, size, (u64)(long)ctx, true);
+ ret = hid_ops->hid_hw_output_report(ctx->hid, dma_data, size,
+ (u64)(long)ctx, true);
kfree(dma_data);
+out_unlock:
+ srcu_read_unlock(&ctx->hid->bpf.srcu, idx);
return ret;
}
@@ -641,8 +671,12 @@ 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);
+ mutex_lock(&hdev->bpf.lifetime_lock);
+ WRITE_ONCE(hdev->bpf.destroyed, true);
+ mutex_unlock(&hdev->bpf.lifetime_lock);
+ mutex_unlock(&hdev->bpf.prog_list_lock);
__hid_bpf_ops_destroy_device(hdev);
@@ -655,6 +689,7 @@ int hid_bpf_device_init(struct hid_device *hdev)
{
INIT_LIST_HEAD(&hdev->bpf.prog_list);
mutex_init(&hdev->bpf.prog_list_lock);
+ mutex_init(&hdev->bpf.lifetime_lock);
return init_srcu_struct(&hdev->bpf.srcu);
}
EXPORT_SYMBOL_GPL(hid_bpf_device_init);
diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h
index 19fffa4574a47..3624937d340b3 100644
--- a/include/linux/hid_bpf.h
+++ b/include/linux/hid_bpf.h
@@ -195,6 +195,7 @@ struct hid_bpf {
struct hid_bpf_ops *rdesc_ops;
struct list_head prog_list;
struct mutex prog_list_lock; /* protects prog_list update */
+ struct mutex lifetime_lock; /* protects destroyed and SRCU admission */
struct srcu_struct srcu; /* protects prog_list read-only access */
};
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH v2 2/2] HID: bpf: reject struct_ops registration during device destruction
2026-11-03 2:45 [PATCH v2 0/2] HID: bpf: fix device destruction races Yifan Zhang
2026-11-03 2:45 ` [PATCH v2 1/2] HID: bpf: synchronize hardware operations with device destruction Yifan Zhang
@ 2026-11-03 2:45 ` Yifan Zhang
1 sibling, 0 replies; 4+ messages in thread
From: Yifan Zhang @ 2026-11-03 2:45 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. Device destruction sets the
flag while holding the same lock, so a registration either completes before
destruction starts or fails with -ENODEV without accessing the SRCU state.
Also clear ops->hdev on every registration error before dropping the device
reference. This keeps a failed struct_ops instance reusable and prevents a
dangling device pointer from reaching a later unregister operation.
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 | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c
index c90b68956cb3b..31e2cb146b92c 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;
@@ -229,11 +234,14 @@ static int hid_bpf_reg(void *kdata, struct bpf_link *link)
synchronize_srcu(&hdev->bpf.srcu);
out_unlock:
- mutex_unlock(&hdev->bpf.prog_list_lock);
-
if (err) {
if (hdev->bpf.rdesc_ops == ops)
hdev->bpf.rdesc_ops = NULL;
+ ops->hdev = NULL;
+ }
+ mutex_unlock(&hdev->bpf.prog_list_lock);
+
+ if (err) {
hid_put_device(hdev);
} else if (ops->hid_rdesc_fixup) {
hid_bpf_reconnect(hdev);
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 4+ messages in thread