Skip to content

Commit 627bb52

Browse files
V4belmchehab
authored andcommitted
media: dvb-core: Fix use-after-free due to race at dvb_register_device()
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/[email protected] Signed-off-by: Hyunwoo Kim <[email protected]> Reported-by: kernel test robot <[email protected]> Reported-by: Dan Carpenter <[email protected]> Signed-off-by: Mauro Carvalho Chehab <[email protected]>
1 parent 4172385 commit 627bb52

File tree

2 files changed

+78
-21
lines changed

2 files changed

+78
-21
lines changed

drivers/media/dvb-core/dvbdev.c

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <media/tuner.h>
2828

2929
static DEFINE_MUTEX(dvbdev_mutex);
30+
static LIST_HEAD(dvbdevfops_list);
3031
static int dvbdev_debug;
3132

3233
module_param(dvbdev_debug, int, 0644);
@@ -453,33 +454,61 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
453454
enum dvb_device_type type, int demux_sink_pads)
454455
{
455456
struct dvb_device *dvbdev;
456-
struct file_operations *dvbdevfops;
457+
struct file_operations *dvbdevfops = NULL;
458+
struct dvbdevfops_node *node = NULL, *new_node = NULL;
457459
struct device *clsdev;
458460
int minor;
459461
int id, ret;
460462

461463
mutex_lock(&dvbdev_register_lock);
462464

463-
if ((id = dvbdev_get_free_id (adap, type)) < 0){
465+
if ((id = dvbdev_get_free_id (adap, type)) < 0) {
464466
mutex_unlock(&dvbdev_register_lock);
465467
*pdvbdev = NULL;
466468
pr_err("%s: couldn't find free device id\n", __func__);
467469
return -ENFILE;
468470
}
469471

470472
*pdvbdev = dvbdev = kzalloc(sizeof(*dvbdev), GFP_KERNEL);
471-
472473
if (!dvbdev){
473474
mutex_unlock(&dvbdev_register_lock);
474475
return -ENOMEM;
475476
}
476477

477-
dvbdevfops = kmemdup(template->fops, sizeof(*dvbdevfops), GFP_KERNEL);
478+
/*
479+
* When a device of the same type is probe()d more than once,
480+
* the first allocated fops are used. This prevents memory leaks
481+
* that can occur when the same device is probe()d repeatedly.
482+
*/
483+
list_for_each_entry(node, &dvbdevfops_list, list_head) {
484+
if (node->fops->owner == adap->module &&
485+
node->type == type &&
486+
node->template == template) {
487+
dvbdevfops = node->fops;
488+
break;
489+
}
490+
}
478491

479-
if (!dvbdevfops){
480-
kfree (dvbdev);
481-
mutex_unlock(&dvbdev_register_lock);
482-
return -ENOMEM;
492+
if (dvbdevfops == NULL) {
493+
dvbdevfops = kmemdup(template->fops, sizeof(*dvbdevfops), GFP_KERNEL);
494+
if (!dvbdevfops) {
495+
kfree(dvbdev);
496+
mutex_unlock(&dvbdev_register_lock);
497+
return -ENOMEM;
498+
}
499+
500+
new_node = kzalloc(sizeof(struct dvbdevfops_node), GFP_KERNEL);
501+
if (!new_node) {
502+
kfree(dvbdevfops);
503+
kfree(dvbdev);
504+
mutex_unlock(&dvbdev_register_lock);
505+
return -ENOMEM;
506+
}
507+
508+
new_node->fops = dvbdevfops;
509+
new_node->type = type;
510+
new_node->template = template;
511+
list_add_tail (&new_node->list_head, &dvbdevfops_list);
483512
}
484513

485514
memcpy(dvbdev, template, sizeof(struct dvb_device));
@@ -490,20 +519,20 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
490519
dvbdev->priv = priv;
491520
dvbdev->fops = dvbdevfops;
492521
init_waitqueue_head (&dvbdev->wait_queue);
493-
494522
dvbdevfops->owner = adap->module;
495-
496523
list_add_tail (&dvbdev->list_head, &adap->device_list);
497-
498524
down_write(&minor_rwsem);
499525
#ifdef CONFIG_DVB_DYNAMIC_MINORS
500526
for (minor = 0; minor < MAX_DVB_MINORS; minor++)
501527
if (dvb_minors[minor] == NULL)
502528
break;
503-
504529
if (minor == MAX_DVB_MINORS) {
530+
if (new_node) {
531+
list_del (&new_node->list_head);
532+
kfree(dvbdevfops);
533+
kfree(new_node);
534+
}
505535
list_del (&dvbdev->list_head);
506-
kfree(dvbdevfops);
507536
kfree(dvbdev);
508537
up_write(&minor_rwsem);
509538
mutex_unlock(&dvbdev_register_lock);
@@ -512,41 +541,47 @@ int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev,
512541
#else
513542
minor = nums2minor(adap->num, type, id);
514543
#endif
515-
516544
dvbdev->minor = minor;
517545
dvb_minors[minor] = dvb_device_get(dvbdev);
518546
up_write(&minor_rwsem);
519-
520547
ret = dvb_register_media_device(dvbdev, type, minor, demux_sink_pads);
521548
if (ret) {
522549
pr_err("%s: dvb_register_media_device failed to create the mediagraph\n",
523550
__func__);
524-
551+
if (new_node) {
552+
list_del (&new_node->list_head);
553+
kfree(dvbdevfops);
554+
kfree(new_node);
555+
}
525556
dvb_media_device_free(dvbdev);
526557
list_del (&dvbdev->list_head);
527-
kfree(dvbdevfops);
528558
kfree(dvbdev);
529559
mutex_unlock(&dvbdev_register_lock);
530560
return ret;
531561
}
532562

533-
mutex_unlock(&dvbdev_register_lock);
534-
535563
clsdev = device_create(dvb_class, adap->device,
536564
MKDEV(DVB_MAJOR, minor),
537565
dvbdev, "dvb%d.%s%d", adap->num, dnames[type], id);
538566
if (IS_ERR(clsdev)) {
539567
pr_err("%s: failed to create device dvb%d.%s%d (%ld)\n",
540568
__func__, adap->num, dnames[type], id, PTR_ERR(clsdev));
569+
if (new_node) {
570+
list_del (&new_node->list_head);
571+
kfree(dvbdevfops);
572+
kfree(new_node);
573+
}
541574
dvb_media_device_free(dvbdev);
542575
list_del (&dvbdev->list_head);
543-
kfree(dvbdevfops);
544576
kfree(dvbdev);
577+
mutex_unlock(&dvbdev_register_lock);
545578
return PTR_ERR(clsdev);
546579
}
580+
547581
dprintk("DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n",
548582
adap->num, dnames[type], id, minor, minor);
549583

584+
mutex_unlock(&dvbdev_register_lock);
550585
return 0;
551586
}
552587
EXPORT_SYMBOL(dvb_register_device);
@@ -575,7 +610,6 @@ static void dvb_free_device(struct kref *ref)
575610
{
576611
struct dvb_device *dvbdev = container_of(ref, struct dvb_device, ref);
577612

578-
kfree (dvbdev->fops);
579613
kfree (dvbdev);
580614
}
581615

@@ -1081,9 +1115,17 @@ static int __init init_dvbdev(void)
10811115

10821116
static void __exit exit_dvbdev(void)
10831117
{
1118+
struct dvbdevfops_node *node, *next;
1119+
10841120
class_destroy(dvb_class);
10851121
cdev_del(&dvb_device_cdev);
10861122
unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS);
1123+
1124+
list_for_each_entry_safe(node, next, &dvbdevfops_list, list_head) {
1125+
list_del (&node->list_head);
1126+
kfree(node->fops);
1127+
kfree(node);
1128+
}
10871129
}
10881130

10891131
subsys_initcall(init_dvbdev);

include/media/dvbdev.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,21 @@ struct dvb_device {
193193
void *priv;
194194
};
195195

196+
/**
197+
* struct dvbdevfops_node - fops nodes registered in dvbdevfops_list
198+
*
199+
* @fops: Dynamically allocated fops for ->owner registration
200+
* @type: type of dvb_device
201+
* @template: dvb_device used for registration
202+
* @list_head: list_head for dvbdevfops_list
203+
*/
204+
struct dvbdevfops_node {
205+
struct file_operations *fops;
206+
enum dvb_device_type type;
207+
const struct dvb_device *template;
208+
struct list_head list_head;
209+
};
210+
196211
/**
197212
* dvb_device_get - Increase dvb_device reference
198213
*

0 commit comments

Comments
 (0)