
PoC لتشغيل CVE-2023-5217 من واجهة WebCodecs أو MediaRecorder في المتصفح.
CVE-2023-5217 هي ثغرة في libvpx تم استغلالها في البرية وتم العثور عليها بواسطة Clément Lecigne من فريق تحليل التهديدات في Google لتستهدف Chrome.
يُظهر هذا المستودع كيفية تشغيل CVE-2023-5217 في المتصفح باستخدام واجهات برمجة التطبيقات WebCodecs و MediaRecorder. تسمح CVE-2023-5217 بتجاوز سعة المخزن المؤقت للكومة بطول تجاوز مسيطر عليه وكتابة فوقية لقيمة صغيرة مكررة من 4 بايتات. ليس معروفًا حاليًا كيف تم استغلال CVE-2023-5217 في البرية.
في وقت الإفصاح العام، كان هناك تصحيحان في libvpx وواحد في Chromium عالجوا CVE-2023-5217. تضمنت تصحيحات libvpx تعطيل تغييرات عدد خيوط VP8 و اختبار للترميز متعدد الخيوط. قام تصحيح Chromium بتعطيل تعديل عدد الخيوط في WebCodecs.
libvpx هي مكتبة تتعامل مع ترميز وفك ترميز VP8/VP9.
المشكلة الرئيسية في CVE-2023-5217 هي أن تقليل عدد الخيوط مع زيادة ارتفاع الإطار في جلسة ترميز VP8 في libvpx يؤدي إلى تجاوز سعة كومة خطي بطول مسيطر عليه وكتابة فوقية مسيطر عليها لقيمة صغيرة مكررة من 4 بايتات. الفرق في ارتفاع الإطار يتحكم في طول الكتابة الفوقية، وعرض الإطار الجديد يتحكم في قيمة الـ 4 بايتات التي تُكتب بشكل متكرر. يمكن استغلال هذه الثغرة عدة مرات لكتابة قيم مختلفة من 4 بايتات بشكل مستمر عن طريق تقليل الارتفاع في كل إعداد لاحق.
يحافظ مُشفر VP8 في libvpx على مصفوفة تسمى mt_current_mb_col تخزن العمود الحالي الذي يعمل عليه خيط الترميز. يتم تخصيص هذه المصفوفة فقط إذا كان هناك أكثر من خيط واحد، وحجمها هو دالة في mb_rows، حيث mb_rows = height_of_frame >> 4 ويتم تقريب frame_height لأعلى إلى أقرب مضاعف للعدد 16.
// https://github.com/webmproject/libvpx/blob/6512f994da13e2f27e6a7bd449efee0a374b55b7/vp8/common/alloccommon.c#L85
// The width and height are rounded up to a multiple of 16 and then assigned to `mb_rows` and `mb_cols`
int vp8_alloc_frame_buffers(VP8_COMMON *oci, int width, int height) {
...
// Round up the width/height up to the nearest multiple of 16
if ((width & 0xf) != 0) width += 16 - (width & 0xf);
if ((height & 0xf) != 0) height += 16 - (height & 0xf);
...
oci->mb_rows = height >> 4;
oci->mb_cols = width >> 4;
...
}
// https://github.com/webmproject/libvpx/blob/6512f994da13e2f27e6a7bd449efee0a374b55b7/vp8/encoder/onyx_if.c#L1232
// This snippet shows the allocation of `mt_current_mb_col`
void vp8_alloc_compressor_data(VP8_COMP *cpi) {
...
// Only allocate if we have more than 1 thread
if (cpi->oxcf.multi_threaded > 1) {
int i;
vpx_free(cpi->mt_current_mb_col);
// sizeof(*cpi->mt_current_mb_col) is 4
CHECK_MEM_ERROR(&cpi->common.error, cpi->mt_current_mb_col,
vpx_malloc(sizeof(*cpi->mt_current_mb_col) * cm->mb_rows));
for (i = 0; i < cm->mb_rows; ++i)
vpx_atomic_init(&cpi->mt_current_mb_col[i], 0);
}
...
}
بمجرد أن ينتهي libvpx من ترميز إطار، فإنه يخزن عدد الأعمدة المُرمزة بالإضافة إلى mt_sync_range في mt_current_mb_col.
// https://github.com/webmproject/libvpx/blob/6512f994da13e2f27e6a7bd449efee0a374b55b7/vp8/encoder/onyx_if.c#L1212
// Snippet where the mt_sync_range value is set, based on the width.
void vp8_alloc_compressor_data(VP8_COMP *cpi) {
...
#if CONFIG_MULTITHREAD
if (width < 640) {
cpi->mt_sync_range = 1;
} else if (width <= 1280) {
cpi->mt_sync_range = 4;
} else if (width <= 2560) {
cpi->mt_sync_range = 8;
} else {
cpi->mt_sync_range = 16;
}
#endif
...
}
// https://github.com/webmproject/libvpx/blob/6512f994da13e2f27e6a7bd449efee0a374b55b7/vp8/encoder/encodeframe.c#L560
// Function where the attacker chosen value is written
static void encode_mb_row(...) {
...
const int nsync = cpi->mt_sync_range; // This value is set in vp8_alloc_compressor_data
vpx_atomic_int rightmost_col = VPX_ATOMIC_INIT(cm->mb_cols + nsync);
...
if (vpx_atomic_load_acquire(&cpi->b_multi_threaded) != 0) {
current_mb_col = &cpi->mt_current_mb_col[mb_row];
}
...
if (vpx_atomic_load_acquire(&cpi->b_multi_threaded) != 0) {
// current_mb_col is a reference to mt_current_mb_col
vpx_atomic_store_release(current_mb_col,
vpx_atomic_load_acquire(&rightmost_col));
}
...
}
لتجاوز mt_current_mb_col، نحتاج إلى ثلاثة إعدادات ترميز:
mt_current_mb_col الضعيف بناءً على configvuln.height. يجب أن يكون هذا الارتفاع الجديد أصغر من configinit.initial_height وإلا سنحصل على خطأ.mt_current_mb_col مما يتركه في حالة ضعيفة. سيقوم libvpx بكتابة القيمة (configattack.width >> 4) + 1 (حيث 1 هو المتغير mt_sync_range ويتم تقريب العرض لأعلى إلى أقرب مضاعف للعدد 16) خارج الحدود المخصصة سابقًا عندما يتحقق الشرط التالي:\text{ceil}(\text{config}_{\text{init}}.\text{height}/16) \geq \text{ceil}(\text{config}_{\text{attack}}.\text{height}/16) \gt \text{ceil}(\text{config}_{\text{vuln}}.\text{height}/16)$$

بشكل أكثر تحديدًا، لنفترض أننا قمنا بتهيئة إعداد ترميز VP8 باستخدام configinit مع width = 1200، height = 1200، threads = 4. الهجوم كالتالي:
mb_rows إلى 704/16 = 44، ويتم تخصيص المصفوفة mt_current_mb_col إلى (44)*4 = 176 بايت. القيمة المخزنة في mt_current_mb_col هي 512/16 + 1 = 33.mt_current_mb_col يتم إعادة تخصيصها فقط عندما يكون هناك أكثر من خيط واحد، فإنها تبقى بنفس الحجم، ومع ذلك فإن mb_rows الآن مضبوط على 1008/16 = 63. عندما يستدعي libvpx encode_mb_row، فإنه سيكتب فوق (63-44)*4 = 68 بايت بعد تخصيص mt_current_mb_col، ويكتب بشكل متكرر القيمة 32/16 + 1 = 3، حيث 32 هو العرض بعد التقريب لأعلى، و 1 هو قيمة mt_sync_range.mb_rows = 992/16 = 62 و mb_cols = 48/16 = 3، وبذلك يكتب فقط (62-44)*4 = 64 بايت بعد التخصيص الأصلي بالقيمة 4.لاستغلال هذه الثغرة، يحتاج المهاجم إلى أن يكون قادرًا على التحكم في ارتفاع الترميز وعرضه وعدد الخيوط. الأولين مباشران، لكن الأخير يتطلب العثور على أماكن يتم فيها إعادة تكوين عدد خيوط الترميز.
// https://github.com/webmproject/libvpx/blob/67bfb41ed8598edfb25bd6f245f9c39a68808548/vp8/vp8_cx_iface.c#L301
static vpx_codec_err_t set_vp8e_config(VP8_CONFIG *oxcf,
...
oxcf->multi_threaded = cfg.g_threads;