
تفاصيل الثغرة الأمنية لـ CVE-2022-41218
هذه الثغرة هي ثغرة حالة سباق (race condition) تحدث في drivers/media/dvb-core/dmxdev.c. لذلك، قد تحدث حالة سباق في برنامج تشغيل جهاز USB الذي يستخدم هذا dmxdev.
يمكن استخدام هذه الثغرة لتفعيل 3 عمليات UAF.
أولاً، ترتيب الاستغلال هو كما يلي (قمت بالتصحيح باستخدام 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 من بين عُقد الأجهزة. يؤدي هذا إلى استدعاء الدالة dvb_dvr_open() لتنفيذ `dvbdev->users++`.
في هذه الحالة، عند تنفيذ ttusb_dec_disconnect() عن طريق إزالة جهاز usb، يتم تعليق التنفيذ عند شرط `wait_event(dmxdev->dvr_dvbdev->wait_queue, …)`.
2. قم بإزالة جهاز USB فعليًا.
في هذه الحالة، وأثناء تنفيذ الدالة ttusb_dec_disconnect()، فإنها تنتظر عند `wait_event(dmxdev->dvr_dvbdev->wait_queue, …)`.
3. استدعِ open() على العقدة demux0 بحيث يتم استدعاء الدالة dvb_demux_open().
تنفذ هذه الدالة أيضًا `dvbdev->users++` بشكل مشابه للدالة dvb_dvr_open() في الخطوة 1.
ومع ذلك، يحدث هذا العد غير الصحيح للمراجع لأن الدالة 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);
}
ثم يستدعي ioctl() على demux0. عند الاستدعاء، تُرسل الوسيطة الثالثة عنوان مساحة المستخدم الذي تم تعيينه بواسطة userfaultfd (أو FUSE fs). الآن أثناء تشغيل ioctl، سيتوقف عند copy_from_user() الخاصة بـ 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()` على واصف الملف (fd) في الخيط الذي قام بفتح عقدة dvr0.
ثم يتم استدعاء `dvb_dvr_release()`، ويتم تنفيذ `dvbdev->users--` و `wake_up(&dvbdev->wait_queue)` لتنبيه دالة `dvb_dmxdev_release()`، وهي تدفق .disconnect.
5. في تدفق .disconnect، دالة `dvb_dmxdev_release()` تنفذ `vfree(dmxdev->filter)`.
6. حرّر userfaultfd من خيط ioctl الخاص بـ demux0 الذي قام بضبط userfaultfd.
سيؤدي هذا إلى حدوث UAF عند قراءة عنوان `dmxdev->filter` الذي تم تحريره للتو من .disconnect.
يمكن الآن استخدام UAF في جميع حالات `dvb_demux_do_ioctl()`، وحتى LPE ممكن عند ربطه مع eBPF.