[PATCH v5 15/15] media: rc: Fix use after free in bpf progs
From: Sean Young
Date: Wed Jul 29 2026 - 11:47:03 EST
Since commit dccc0c3ddf8f ("media: rc: fix race between unregister and
urb/irq callbacks"), rcdev->raw is no longer set to NULL after device
unregister. raw->progs could point to stale data.
Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
Signed-off-by: Sean Young <sean@xxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
drivers/media/rc/bpf-lirc.c | 18 +++++++++++++-----
drivers/media/rc/rc-ir-raw.c | 11 +++--------
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/drivers/media/rc/bpf-lirc.c b/drivers/media/rc/bpf-lirc.c
index 2f7564f26445..14ab611e7445 100644
--- a/drivers/media/rc/bpf-lirc.c
+++ b/drivers/media/rc/bpf-lirc.c
@@ -148,12 +148,13 @@ static int lirc_bpf_attach(struct rc_dev *rcdev, struct bpf_prog *prog)
if (ret)
return ret;
- raw = rcdev->raw;
- if (!raw) {
+ if (!rcdev->registered) {
ret = -ENODEV;
goto unlock;
}
+ raw = rcdev->raw;
+
old_array = lirc_rcu_dereference(raw->progs);
if (old_array && bpf_prog_array_length(old_array) >= BPF_MAX_PROGS) {
ret = -E2BIG;
@@ -186,12 +187,13 @@ static int lirc_bpf_detach(struct rc_dev *rcdev, struct bpf_prog *prog)
if (ret)
return ret;
- raw = rcdev->raw;
- if (!raw) {
+ if (!rcdev->registered) {
ret = -ENODEV;
goto unlock;
}
+ raw = rcdev->raw;
+
old_array = lirc_rcu_dereference(raw->progs);
ret = bpf_prog_array_copy(old_array, prog, NULL, 0, &new_array);
/*
@@ -235,7 +237,8 @@ void lirc_bpf_free(struct rc_dev *rcdev)
struct bpf_prog_array_item *item;
struct bpf_prog_array *array;
- array = lirc_rcu_dereference(rcdev->raw->progs);
+ array = rcu_replace_pointer(rcdev->raw->progs, NULL,
+ lockdep_is_held(&ir_raw_handler_lock));
if (!array)
return;
@@ -316,6 +319,11 @@ int lirc_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr)
if (ret)
goto put;
+ if (!rcdev->registered) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+
progs = lirc_rcu_dereference(rcdev->raw->progs);
cnt = progs ? bpf_prog_array_length(progs) : 0;
diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c
index 86de1b26731d..54b323becb1f 100644
--- a/drivers/media/rc/rc-ir-raw.c
+++ b/drivers/media/rc/rc-ir-raw.c
@@ -632,8 +632,11 @@ void ir_raw_event_free(struct rc_dev *dev)
{
if (dev->raw) {
timer_delete_sync(&dev->raw->edge_handle);
+ mutex_lock(&ir_raw_handler_lock);
if (dev->raw->thread)
put_task_struct(dev->raw->thread);
+ lirc_bpf_free(dev);
+ mutex_unlock(&ir_raw_handler_lock);
kfree(dev->raw);
dev->raw = NULL;
}
@@ -643,9 +646,6 @@ void ir_raw_event_unregister(struct rc_dev *dev)
{
struct ir_raw_handler *handler;
- if (!dev || !dev->raw)
- return;
-
/*
* After ir_raw_event_unregister() is called, an sync
* call to ir_raw_event_handle() can still arrive. This function
@@ -665,11 +665,6 @@ void ir_raw_event_unregister(struct rc_dev *dev)
lirc_bpf_free(dev);
- /*
- * A user can be calling bpf(BPF_PROG_{QUERY|ATTACH|DETACH}), so
- * ensure that the raw member is null on unlock; this is how
- * "device gone" is checked.
- */
mutex_unlock(&ir_raw_handler_lock);
}
--
2.55.0