
Détails de la vulnérabilité pour CVE-2022-41218
Cette vulnérabilité est une vulnérabilité de type course (race condition) qui se produit dans drivers/media/dvb-core/dmxdev.c. Par conséquent, une condition de course peut survenir dans le pilote de périphérique USB utilisant ce dmxdev.
Cette vulnérabilité pourrait être utilisée pour déclencher 3 UAFs.
Tout d'abord, l'ordre de l'exploit est le suivant(j'ai débogué en utilisant drivers/media/usb/ttusb-dec/ttusb_dec.c) :```
cpu0 cpu1 cpu 2
1. dvb_dvr_open()
2. ttusb_dec_disconnect()
ttusb_dec_exit_dvb()
dvb_dmxdev_release()
wait_event(dmxdev->dvr_dvbdev->wait_queue, …)
3. dvb_demux_open()
dvb_demux_ioctl()
dvb_usercopy()
copy_from_user() <- userfaultfd stuck
4. dvb_dvr_release()
5. vfree(dmxdev->filter)
6. copy_from_user() <- userfaultfd release
dvb_demux_do_ioctl()
struct dmxdev *dmxdev = dmxdevfilter->dev; <- UAF!!
Le flux d'exploitation détaillé est le suivant :
1. open() dvr0 parmi les nœuds de périphérique. Cela appelle dvb_dvr_open() pour provoquer l'exécution de `dvbdev->users++`.
Dans ce cas, lorsque ttusb_dec_disconnect() est exécutée en retirant le périphérique USB, la condition `wait_event(dmxdev->dvr_dvbdev->wait_queue, …)` est capturée.
2. Retirez physiquement le périphérique USB.
Dans ce cas, pendant que la fonction ttusb_dec_disconnect() est en cours d'exécution, elle attend au niveau de `wait_event(dmxdev->dvr_dvbdev->wait_queue, …)`.
3. open() le nœud demux0 pour que la fonction dvb_demux_open() soit appelée.
Cette fonction exécute également `dvbdev->users++` de la même manière que la fonction dvb_dvr_open() à l'étape 1.
Cependant, ce comptage de références incorrect se produit parce que la fonction dvb_dmxdev_release() qui est actuellement en attente a déjà passé la vérification de `dvbdev->users` :```
void dvb_dmxdev_release(struct dmxdev *dmxdev)
{
dmxdev->exit = 1;
if (dmxdev->dvbdev->users > 1) { // This check is bypassed. improper reference counting.
wait_event(dmxdev->dvbdev->wait_queue,
dmxdev->dvbdev->users == 1);
}
if (dmxdev->dvr_dvbdev->users > 1) {
wait_event(dmxdev->dvr_dvbdev->wait_queue, // Currently wait()ing here.
dmxdev->dvr_dvbdev->users == 1);
}
dvb_unregister_device(dmxdev->dvbdev);
dvb_unregister_device(dmxdev->dvr_dvbdev);
vfree(dmxdev->filter);
dmxdev->filter = NULL;
dmxdev->demux->close(dmxdev->demux);
}
Il appelle ensuite ioctl() sur demux0. Lors de l'appel, le 3ème argument soumet l'adresse en espace utilisateur définie par userfaultfd(or FUSE fs). Maintenant, pendant que ioctl est en cours d'exécution, il se bloquera sur copy_from_user() de dvb_usercopy():``` int dvb_usercopy(struct file *file, unsigned int cmd, unsigned long arg, int (*func)(struct file *file, unsigned int cmd, void *arg)) { char sbuf[128]; void *mbuf = NULL; void *parg = NULL; int err = -EINVAL;
/* Copy arguments into temp kernel buffer */
switch (_IOC_DIR(cmd)) {
case _IOC_NONE:
/*
* For this command, the pointer is actually an integer
* argument.
*/
parg = (void *) arg;
break;
case _IOC_READ: /* some v4l ioctls are marked wrong ... */
case _IOC_WRITE:
case (_IOC_WRITE | _IOC_READ):
if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
parg = sbuf;
} else {
/* too big to allocate from stack */
mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
if (NULL == mbuf)
return -ENOMEM;
parg = mbuf;
}
err = -EFAULT;
if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) // here
goto out;
break;
}
4. close() the fd in the thread that open()ed the dvr0 node.
Ensuite, `dvb_dvr_release()` est appelée, et `dvbdev->users--` ainsi que `wake_up(&dvbdev->wait_queue)` sont exécutés pour réveiller la fonction `dvb_dmxdev_release()`, qui correspond au flux .disconnect.
5. Dans le flux .disconnect, la fonction `dvb_dmxdev_release()` exécute `vfree(dmxdev->filter)`.
6. Relâchez l’userfaultfd depuis le thread ioctl demux0 qui a configuré l’userfaultfd.
Cela provoquera une UAF en lisant l’adresse `dmxdev->filter` que vous venez de libérer depuis .disconnect.
L’UAF peut désormais être utilisée dans tous les cas de `dvb_demux_do_ioctl()`, et même une escalade de privilèges (LPE) est possible lorsqu’elle est associée à eBPF.