Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

media: dvb-core: Fix use-after-free due on race condition at dvb_net

A race condition may occur between the .disconnect function, which
is called when the device is disconnected, and the dvb_device_open()
function, which is called when the device node is open()ed.
This results in several types of UAFs.

The root cause of this is that you use the dvb_device_open() function,
which does not implement a conditional statement
that checks 'dvbnet->exit'.

So, add 'remove_mutex` to protect 'dvbnet->exit' and use
locked_dvb_net_open() function to check 'dvbnet->exit'.

[mchehab: fix a checkpatch warning]

Link: https://lore.kernel.org/linux-media/20221117045925.14297-3-imv4bel@gmail.com
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>

authored by

Hyunwoo Kim and committed by
Mauro Carvalho Chehab
4172385b 6769a0b7

+39 -3
+35 -3
drivers/media/dvb-core/dvb_net.c
··· 1564 1564 return dvb_usercopy(file, cmd, arg, dvb_net_do_ioctl); 1565 1565 } 1566 1566 1567 + static int locked_dvb_net_open(struct inode *inode, struct file *file) 1568 + { 1569 + struct dvb_device *dvbdev = file->private_data; 1570 + struct dvb_net *dvbnet = dvbdev->priv; 1571 + int ret; 1572 + 1573 + if (mutex_lock_interruptible(&dvbnet->remove_mutex)) 1574 + return -ERESTARTSYS; 1575 + 1576 + if (dvbnet->exit) { 1577 + mutex_unlock(&dvbnet->remove_mutex); 1578 + return -ENODEV; 1579 + } 1580 + 1581 + ret = dvb_generic_open(inode, file); 1582 + 1583 + mutex_unlock(&dvbnet->remove_mutex); 1584 + 1585 + return ret; 1586 + } 1587 + 1567 1588 static int dvb_net_close(struct inode *inode, struct file *file) 1568 1589 { 1569 1590 struct dvb_device *dvbdev = file->private_data; 1570 1591 struct dvb_net *dvbnet = dvbdev->priv; 1571 1592 1593 + mutex_lock(&dvbnet->remove_mutex); 1594 + 1572 1595 dvb_generic_release(inode, file); 1573 1596 1574 - if(dvbdev->users == 1 && dvbnet->exit == 1) 1597 + if (dvbdev->users == 1 && dvbnet->exit == 1) { 1598 + mutex_unlock(&dvbnet->remove_mutex); 1575 1599 wake_up(&dvbdev->wait_queue); 1600 + } else { 1601 + mutex_unlock(&dvbnet->remove_mutex); 1602 + } 1603 + 1576 1604 return 0; 1577 1605 } 1578 1606 ··· 1608 1580 static const struct file_operations dvb_net_fops = { 1609 1581 .owner = THIS_MODULE, 1610 1582 .unlocked_ioctl = dvb_net_ioctl, 1611 - .open = dvb_generic_open, 1583 + .open = locked_dvb_net_open, 1612 1584 .release = dvb_net_close, 1613 1585 .llseek = noop_llseek, 1614 1586 }; ··· 1627 1599 { 1628 1600 int i; 1629 1601 1602 + mutex_lock(&dvbnet->remove_mutex); 1630 1603 dvbnet->exit = 1; 1604 + mutex_unlock(&dvbnet->remove_mutex); 1605 + 1631 1606 if (dvbnet->dvbdev->users < 1) 1632 1607 wait_event(dvbnet->dvbdev->wait_queue, 1633 - dvbnet->dvbdev->users==1); 1608 + dvbnet->dvbdev->users == 1); 1634 1609 1635 1610 dvb_unregister_device(dvbnet->dvbdev); 1636 1611 ··· 1652 1621 int i; 1653 1622 1654 1623 mutex_init(&dvbnet->ioctl_mutex); 1624 + mutex_init(&dvbnet->remove_mutex); 1655 1625 dvbnet->demux = dmx; 1656 1626 1657 1627 for (i=0; i<DVB_NET_DEVICES_MAX; i++)
+4
include/media/dvb_net.h
··· 39 39 * @exit: flag to indicate when the device is being removed. 40 40 * @demux: pointer to &struct dmx_demux. 41 41 * @ioctl_mutex: protect access to this struct. 42 + * @remove_mutex: mutex that avoids a race condition between a callback 43 + * called when the hardware is disconnected and the 44 + * file_operations of dvb_net. 42 45 * 43 46 * Currently, the core supports up to %DVB_NET_DEVICES_MAX (10) network 44 47 * devices. ··· 54 51 unsigned int exit:1; 55 52 struct dmx_demux *demux; 56 53 struct mutex ioctl_mutex; 54 + struct mutex remove_mutex; 57 55 }; 58 56 59 57 /**