[PATCH v3 1/3] media: dvb-core: fix use-after-free in dvb_remove_device()
From: YuanZhe Liu
Date: Mon Oct 26 2026 - 22:03:23 EST
dvb_remove_device() clears dvb_minors[] and drops the minors-table
reference while holding minor_rwsem, but then keeps using "dvbdev":
down_write(&minor_rwsem);
dvb_minors[dvbdev->minor] = NULL;
dvb_device_put(dvbdev); /* may kfree(dvbdev) */
up_write(&minor_rwsem);
dvb_media_device_free(dvbdev);
device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor));
list_del(&dvbdev->list_head);
If the put() happens to drop the last reference, the remaining three
statements are use-after-free. KASAN reports the read of dvbdev->minor
inside device_destroy(), and the list_del() then writes list poison
into the freed object.
The reference accounting itself is fine: dvb_unregister_device()
pairs the put() with its own one for the caller, and direct callers
such as dvb_unregister_frontend() and dvb_ca_en50221_release() rely
on dvb_remove_device() consuming the minors-table reference only.
The problem is the ordering, so move the put() after the last use of
the object. The entry is already gone from dvb_minors[], hence no
concurrent open() can acquire a new reference in between.
While at it, fix the error paths of dvb_register_device(): once the
new device has been installed in dvb_minors[], a failure in
dvb_register_media_device() or device_create() frees it with kfree()
without clearing the array entry. dvb_minors[minor] is then left
pointing to freed memory and the next open() dereferences it. Clear
the entry under minor_rwsem and release both the minors-table and
the caller's reference with dvb_device_put() instead of freeing the
object directly.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@xxxxxxxxxxxxxxx
Co-developed-by: Bin Yu <byu@xxxxxxxxxxxxx>
Signed-off-by: Bin Yu <byu@xxxxxxxxxxxxx>
Co-developed-by: Cheng Wen <wencheng@xxxxxxxxxxxxx>
Signed-off-by: Cheng Wen <wencheng@xxxxxxxxxxxxx>
Signed-off-by: YuanZhe Liu <25031212351@xxxxxxxxxxxxxxxxx>
---
drivers/media/dvb-core/dvbdev.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/drivers/media/dvb-core/dvbdev.c b/drivers/media/dvb-core/dvbdev.c
index d753d329502a..610f8634e8d4 100644
--- a/drivers/media/dvb-core/dvbdev.c
+++ b/drivers/media/dvb-core/dvbdev.c
@@ -537,10 +537,10 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
kfree(new_node);
}
list_del(&dvbdev->list_head);
- kfree(dvbdev);
*pdvbdev = NULL;
up_write(&minor_rwsem);
mutex_unlock(&dvbdev_register_lock);
+ dvb_device_put(dvbdev);
return -EINVAL;
}
@@ -556,11 +556,16 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
kfree(dvbdevfops);
kfree(new_node);
}
+ down_write(&minor_rwsem);
+ dvb_minors[minor] = NULL;
+ up_write(&minor_rwsem);
dvb_media_device_free(dvbdev);
list_del(&dvbdev->list_head);
- kfree(dvbdev);
*pdvbdev = NULL;
mutex_unlock(&dvbdev_register_lock);
+ /* drop the minors-table and the caller's references */
+ dvb_device_put(dvbdev);
+ dvb_device_put(dvbdev);
return ret;
}
@@ -575,11 +580,16 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
kfree(dvbdevfops);
kfree(new_node);
}
+ down_write(&minor_rwsem);
+ dvb_minors[minor] = NULL;
+ up_write(&minor_rwsem);
dvb_media_device_free(dvbdev);
list_del(&dvbdev->list_head);
- kfree(dvbdev);
*pdvbdev = NULL;
mutex_unlock(&dvbdev_register_lock);
+ /* drop the minors-table and the caller's references */
+ dvb_device_put(dvbdev);
+ dvb_device_put(dvbdev);
return PTR_ERR(clsdev);
}
@@ -598,7 +608,6 @@ void dvb_remove_device(struct dvb_device *dvbdev)
down_write(&minor_rwsem);
dvb_minors[dvbdev->minor] = NULL;
- dvb_device_put(dvbdev);
up_write(&minor_rwsem);
dvb_media_device_free(dvbdev);
@@ -606,6 +615,13 @@ void dvb_remove_device(struct dvb_device *dvbdev)
device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor));
list_del(&dvbdev->list_head);
+
+ /*
+ * Drop the minors-table reference only after the last use of the
+ * object above: should this be the last reference, freeing it any
+ * earlier would turn those statements into a use-after-free.
+ */
+ dvb_device_put(dvbdev);
}
EXPORT_SYMBOL(dvb_remove_device);
--
2.34.1