media: dvb-core: Fix use-after-free due to race at dvb_register_device()
commit 627bb528b086b4136315c25d6a447a98ea9448d3 upstream. dvb_register_device() dynamically allocates fops with kmemdup() to set the fops->owner. And these fops are registered in 'file->f_ops' using replace_fops() in the dvb_device_open() process, and kfree()d in dvb_free_device(). However, it is not common to use dynamically allocated fops instead of 'static const' fops as an argument of replace_fops(), and UAF may occur. These UAFs can occur on any dvb type using dvb_register_device(), such as dvb_dvr, dvb_demux, dvb_frontend, dvb_net, etc. So, instead of kfree() the fops dynamically allocated in dvb_register_device() in dvb_free_device() called during the .disconnect() process, kfree() it collectively in exit_dvbdev() called when the dvbdev.c module is removed. Link: https://lore.kernel.org/linux-media/20221117045925.14297-4-imv4bel@gmail.com Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com> Reported-by: kernel test robot <lkp@intel.com> Reported-by: Dan Carpenter <error27@gmail.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org> Signed-off-by: Ovidiu Panait <ovidiu.panait@windriver.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
5c61c3945a
commit
2d1c19597d
@@ -37,6 +37,7 @@
|
|||||||
#include <media/tuner.h>
|
#include <media/tuner.h>
|
||||||
|
|
||||||
static DEFINE_MUTEX(dvbdev_mutex);
|
static DEFINE_MUTEX(dvbdev_mutex);
|
||||||
|
static LIST_HEAD(dvbdevfops_list);
|
||||||
static int dvbdev_debug;
|
static int dvbdev_debug;
|
||||||
|
|
||||||
module_param(dvbdev_debug, int, 0644);
|
module_param(dvbdev_debug, int, 0644);
|
||||||
@@ -462,14 +463,15 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
|
|||||||
enum dvb_device_type type, int demux_sink_pads)
|
enum dvb_device_type type, int demux_sink_pads)
|
||||||
{
|
{
|
||||||
struct dvb_device *dvbdev;
|
struct dvb_device *dvbdev;
|
||||||
struct file_operations *dvbdevfops;
|
struct file_operations *dvbdevfops = NULL;
|
||||||
|
struct dvbdevfops_node *node = NULL, *new_node = NULL;
|
||||||
struct device *clsdev;
|
struct device *clsdev;
|
||||||
int minor;
|
int minor;
|
||||||
int id, ret;
|
int id, ret;
|
||||||
|
|
||||||
mutex_lock(&dvbdev_register_lock);
|
mutex_lock(&dvbdev_register_lock);
|
||||||
|
|
||||||
if ((id = dvbdev_get_free_id (adap, type)) < 0){
|
if ((id = dvbdev_get_free_id (adap, type)) < 0) {
|
||||||
mutex_unlock(&dvbdev_register_lock);
|
mutex_unlock(&dvbdev_register_lock);
|
||||||
*pdvbdev = NULL;
|
*pdvbdev = NULL;
|
||||||
pr_err("%s: couldn't find free device id\n", __func__);
|
pr_err("%s: couldn't find free device id\n", __func__);
|
||||||
@@ -477,18 +479,45 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
|
|||||||
}
|
}
|
||||||
|
|
||||||
*pdvbdev = dvbdev = kzalloc(sizeof(*dvbdev), GFP_KERNEL);
|
*pdvbdev = dvbdev = kzalloc(sizeof(*dvbdev), GFP_KERNEL);
|
||||||
|
|
||||||
if (!dvbdev){
|
if (!dvbdev){
|
||||||
mutex_unlock(&dvbdev_register_lock);
|
mutex_unlock(&dvbdev_register_lock);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
dvbdevfops = kmemdup(template->fops, sizeof(*dvbdevfops), GFP_KERNEL);
|
/*
|
||||||
|
* When a device of the same type is probe()d more than once,
|
||||||
|
* the first allocated fops are used. This prevents memory leaks
|
||||||
|
* that can occur when the same device is probe()d repeatedly.
|
||||||
|
*/
|
||||||
|
list_for_each_entry(node, &dvbdevfops_list, list_head) {
|
||||||
|
if (node->fops->owner == adap->module &&
|
||||||
|
node->type == type &&
|
||||||
|
node->template == template) {
|
||||||
|
dvbdevfops = node->fops;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!dvbdevfops){
|
if (dvbdevfops == NULL) {
|
||||||
kfree (dvbdev);
|
dvbdevfops = kmemdup(template->fops, sizeof(*dvbdevfops), GFP_KERNEL);
|
||||||
mutex_unlock(&dvbdev_register_lock);
|
if (!dvbdevfops) {
|
||||||
return -ENOMEM;
|
kfree(dvbdev);
|
||||||
|
mutex_unlock(&dvbdev_register_lock);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_node = kzalloc(sizeof(struct dvbdevfops_node), GFP_KERNEL);
|
||||||
|
if (!new_node) {
|
||||||
|
kfree(dvbdevfops);
|
||||||
|
kfree(dvbdev);
|
||||||
|
mutex_unlock(&dvbdev_register_lock);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_node->fops = dvbdevfops;
|
||||||
|
new_node->type = type;
|
||||||
|
new_node->template = template;
|
||||||
|
list_add_tail (&new_node->list_head, &dvbdevfops_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(dvbdev, template, sizeof(struct dvb_device));
|
memcpy(dvbdev, template, sizeof(struct dvb_device));
|
||||||
@@ -499,20 +528,20 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
|
|||||||
dvbdev->priv = priv;
|
dvbdev->priv = priv;
|
||||||
dvbdev->fops = dvbdevfops;
|
dvbdev->fops = dvbdevfops;
|
||||||
init_waitqueue_head (&dvbdev->wait_queue);
|
init_waitqueue_head (&dvbdev->wait_queue);
|
||||||
|
|
||||||
dvbdevfops->owner = adap->module;
|
dvbdevfops->owner = adap->module;
|
||||||
|
|
||||||
list_add_tail (&dvbdev->list_head, &adap->device_list);
|
list_add_tail (&dvbdev->list_head, &adap->device_list);
|
||||||
|
|
||||||
down_write(&minor_rwsem);
|
down_write(&minor_rwsem);
|
||||||
#ifdef CONFIG_DVB_DYNAMIC_MINORS
|
#ifdef CONFIG_DVB_DYNAMIC_MINORS
|
||||||
for (minor = 0; minor < MAX_DVB_MINORS; minor++)
|
for (minor = 0; minor < MAX_DVB_MINORS; minor++)
|
||||||
if (dvb_minors[minor] == NULL)
|
if (dvb_minors[minor] == NULL)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (minor == MAX_DVB_MINORS) {
|
if (minor == MAX_DVB_MINORS) {
|
||||||
|
if (new_node) {
|
||||||
|
list_del (&new_node->list_head);
|
||||||
|
kfree(dvbdevfops);
|
||||||
|
kfree(new_node);
|
||||||
|
}
|
||||||
list_del (&dvbdev->list_head);
|
list_del (&dvbdev->list_head);
|
||||||
kfree(dvbdevfops);
|
|
||||||
kfree(dvbdev);
|
kfree(dvbdev);
|
||||||
up_write(&minor_rwsem);
|
up_write(&minor_rwsem);
|
||||||
mutex_unlock(&dvbdev_register_lock);
|
mutex_unlock(&dvbdev_register_lock);
|
||||||
@@ -521,41 +550,47 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
|
|||||||
#else
|
#else
|
||||||
minor = nums2minor(adap->num, type, id);
|
minor = nums2minor(adap->num, type, id);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
dvbdev->minor = minor;
|
dvbdev->minor = minor;
|
||||||
dvb_minors[minor] = dvb_device_get(dvbdev);
|
dvb_minors[minor] = dvb_device_get(dvbdev);
|
||||||
up_write(&minor_rwsem);
|
up_write(&minor_rwsem);
|
||||||
|
|
||||||
ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads);
|
ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err("%s: dvb_register_media_device failed to create the mediagraph\n",
|
pr_err("%s: dvb_register_media_device failed to create the mediagraph\n",
|
||||||
__func__);
|
__func__);
|
||||||
|
if (new_node) {
|
||||||
|
list_del (&new_node->list_head);
|
||||||
|
kfree(dvbdevfops);
|
||||||
|
kfree(new_node);
|
||||||
|
}
|
||||||
dvb_media_device_free(dvbdev);
|
dvb_media_device_free(dvbdev);
|
||||||
list_del (&dvbdev->list_head);
|
list_del (&dvbdev->list_head);
|
||||||
kfree(dvbdevfops);
|
|
||||||
kfree(dvbdev);
|
kfree(dvbdev);
|
||||||
mutex_unlock(&dvbdev_register_lock);
|
mutex_unlock(&dvbdev_register_lock);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&dvbdev_register_lock);
|
|
||||||
|
|
||||||
clsdev = device_create(dvb_class, adap->device,
|
clsdev = device_create(dvb_class, adap->device,
|
||||||
MKDEV(DVB_MAJOR, minor),
|
MKDEV(DVB_MAJOR, minor),
|
||||||
dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id);
|
dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id);
|
||||||
if (IS_ERR(clsdev)) {
|
if (IS_ERR(clsdev)) {
|
||||||
pr_err("%s: failed to create device dvb%d.%s%d (%ld)\n",
|
pr_err("%s: failed to create device dvb%d.%s%d (%ld)\n",
|
||||||
__func__, adap->num, dnames[type], id, PTR_ERR(clsdev));
|
__func__, adap->num, dnames[type], id, PTR_ERR(clsdev));
|
||||||
|
if (new_node) {
|
||||||
|
list_del (&new_node->list_head);
|
||||||
|
kfree(dvbdevfops);
|
||||||
|
kfree(new_node);
|
||||||
|
}
|
||||||
dvb_media_device_free(dvbdev);
|
dvb_media_device_free(dvbdev);
|
||||||
list_del (&dvbdev->list_head);
|
list_del (&dvbdev->list_head);
|
||||||
kfree(dvbdevfops);
|
|
||||||
kfree(dvbdev);
|
kfree(dvbdev);
|
||||||
|
mutex_unlock(&dvbdev_register_lock);
|
||||||
return PTR_ERR(clsdev);
|
return PTR_ERR(clsdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
dprintk("DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n",
|
dprintk("DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n",
|
||||||
adap->num, dnames[type], id, minor, minor);
|
adap->num, dnames[type], id, minor, minor);
|
||||||
|
|
||||||
|
mutex_unlock(&dvbdev_register_lock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(dvb_register_device);
|
EXPORT_SYMBOL(dvb_register_device);
|
||||||
@@ -584,7 +619,6 @@ static void dvb_free_device(struct kref *ref)
|
|||||||
{
|
{
|
||||||
struct dvb_device *dvbdev = container_of(ref, struct dvb_device, ref);
|
struct dvb_device *dvbdev = container_of(ref, struct dvb_device, ref);
|
||||||
|
|
||||||
kfree (dvbdev->fops);
|
|
||||||
kfree (dvbdev);
|
kfree (dvbdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1090,9 +1124,17 @@ error:
|
|||||||
|
|
||||||
static void __exit exit_dvbdev(void)
|
static void __exit exit_dvbdev(void)
|
||||||
{
|
{
|
||||||
|
struct dvbdevfops_node *node, *next;
|
||||||
|
|
||||||
class_destroy(dvb_class);
|
class_destroy(dvb_class);
|
||||||
cdev_del(&dvb_device_cdev);
|
cdev_del(&dvb_device_cdev);
|
||||||
unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS);
|
unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS);
|
||||||
|
|
||||||
|
list_for_each_entry_safe(node, next, &dvbdevfops_list, list_head) {
|
||||||
|
list_del (&node->list_head);
|
||||||
|
kfree(node->fops);
|
||||||
|
kfree(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subsys_initcall(init_dvbdev);
|
subsys_initcall(init_dvbdev);
|
||||||
|
|||||||
@@ -189,6 +189,21 @@ struct dvb_device {
|
|||||||
void *priv;
|
void *priv;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct dvbdevfops_node - fops nodes registered in dvbdevfops_list
|
||||||
|
*
|
||||||
|
* @fops: Dynamically allocated fops for ->owner registration
|
||||||
|
* @type: type of dvb_device
|
||||||
|
* @template: dvb_device used for registration
|
||||||
|
* @list_head: list_head for dvbdevfops_list
|
||||||
|
*/
|
||||||
|
struct dvbdevfops_node {
|
||||||
|
struct file_operations *fops;
|
||||||
|
enum dvb_device_type type;
|
||||||
|
const struct dvb_device *template;
|
||||||
|
struct list_head list_head;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dvb_device_get - Increase dvb_device reference
|
* dvb_device_get - Increase dvb_device reference
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user