
CVE-2022-41218 के लिए भेद्यता विवरण
यह भेद्यता एक रेस कंडीशन भेद्यता है जो drivers/media/dvb-core/dmxdev.c में होती है। इसलिए, इस dmxdev का उपयोग करने वाले usb डिवाइस ड्राइवर में रेस कंडीशन हो सकती है।
इस भेद्यता का उपयोग 3 UAFs को ट्रिगर करने के लिए किया जा सकता है।
सबसे पहले, exploit का क्रम इस प्रकार है(मैंने 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!!
विस्तृत शोषण प्रवाह इस प्रकार है:
1. डिवाइस नोड्स में से `dvr0` को `open()` करें। यह `dvb_dvr_open()` को कॉल करता है जिससे `dvbdev->users++` निष्पादित होता है।
इस स्थिति में, जब usb डिवाइस को हटाने पर `ttusb_dec_disconnect()` निष्पादित होता है, तो `wait_event(dmxdev->dvr_dvbdev->wait_queue, …)` स्थिति पकड़ी जाती है।
2. USB डिवाइस को भौतिक रूप से हटाएँ।
इस स्थिति में, जब `ttusb_dec_disconnect()` फ़ंक्शन निष्पादित हो रहा होता है, यह `wait_event(dmxdev->dvr_dvbdev->wait_queue, …)` पर प्रतीक्षा करता है।
3. `demux0` नोड को `open()` करें ताकि `dvb_demux_open()` फ़ंक्शन कॉल हो सके।
यह फ़ंक्शन भी चरण 1 के `dvb_dvr_open()` फ़ंक्शन की तरह `dvbdev->users++` निष्पादित करता है।
हालाँकि, यह अनुचित रेफ़रेंस काउंटिंग इसलिए होती है क्योंकि वर्तमान में प्रतीक्षा कर रहा `dvb_dmxdev_release()` फ़ंक्शन पहले ही `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);
}
फिर यह demux0 पर ioctl() कॉल करता है। कॉल करते समय, तीसरा तर्क userfaultfd(या FUSE fs) द्वारा सेट किया गया user-space पता प्रस्तुत करता है। अब जब ioctl चल रहा होता है, यह dvb_usercopy() के copy_from_user() पर अटक जाएगा:``` 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() को fd पर कॉल करें जिसने dvr0 नोड को open() किया था।
फिर dvb_dvr_release() कॉल होता है, और `dvbdev->users--` तथा `wake_up(&dvbdev->wait_queue)` निष्पादित होते हैं ताकि dvb_dmxdev_release() फ़ंक्शन जागृत हो सके, जो कि .disconnect फ्लो है।
5. .disconnect फ्लो में, dvb_dmxdev_release() फ़ंक्शन में, `vfree(dmxdev->filter)` निष्पादित होता है।
6. demux0 ioctl थ्रेड से userfaultfd को रिलीज़ करें जिसने userfaultfd सेट किया था।
इससे UAF उत्पन्न होगा, क्योंकि .disconnect से अभी-अभी रिलीज़ किया गया `dmxdev->filter` पता पढ़ा जाएगा।
UAF अब dvb_demux_do_ioctl() के सभी मामलों में उपयोग किया जा सकता है, और eBPF के साथ जोड़ने पर LPE भी संभव है।