
PoC для уязвимости Use-After-Free (UAF) в компоненте RTCEncodedFrameBase браузера Firefox, эксплуатируемой через API WebRTC Encoded Transforms.
CVE-2025-14321: Деструктор RTCEncodedFrameBase не вызывает DetachArrayBuffer() при освобождении нативной памяти кадра. В результате JavaScript-ArrayBuffers сохраняют указатели на уже освобожденную память (dangling pointers), что позволяет произвольное чтение и запись в heap content-процесса Firefox.
Этот PoC протестирован в Mozilla Firefox 145.0.1 и Windows 10.
Firefox предоставляет закодированные видео/аудио кадры в виде JavaScript-ArrayBuffers через NewArrayBufferWithUserOwnedContents(). Когда нативный кадр (C++) уничтожается, резервная память ArrayBuffer освобождается, но ArrayBuffer в JavaScript не отсоединяется (detach). Это нарушает инвариант владения (ownership) и создает dangling pointer.
Уязвимый код (dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp):
// ❌ Destructor no desvincula el ArrayBuffer
RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
Исправленный код:
// ✅ Desvincula el ArrayBuffer antes de liberar memoria nativa
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
}
new Uint8Array(buf) читает данные из памяти, перераспределённой для других объектов в heapnew Uint8Array(buf).fill(0x41) повреждает соседние объекты в heap (vtables, DOM-структуры и т.д.)Этих примитивов достаточно для демонстрации уязвимости. С дополнительным heap shaping их можно расширить до удалённого выполнения кода (RCE).
media.peerconnection.scripttransform.enabled = true
media.peerconnection.ice.loopback = true
media.peerconnection.ice.no_host = false
media.peerconnection.ice.relay_only = false
media.peerconnection.ice.proxy_only = false
⚠ Перезапустите Firefox после изменения предпочтений.
PoC пытается запросить доступ к камере через getUserMedia(). Это не обязательно, если настройки ICE сконфигурированы правильно. Его задача — перевести Firefox в RFC IP handling mode 1, который гарантирует генерацию ICE host-кандидатов в сценариях, где одних только настроек недостаточно.
На практике:
getUserMedia() молча завершается с ошибкой (NotFoundError) без показа диалога. ICE всё равно подключается благодаря media.peerconnection.ice.loopback = true.Web Workers не работают через file://. Обслуживайте по HTTP:
python3 -m http.server 8080
Откройте в уязвимом Firefox: http://localhost:8080/poc-cve-2025-14321.html
PoC пытается запросить доступ к камере. Если камера доступна и пользователь соглашается, Firefox переводит сессию в RFC IP handling mode 1. Если getUserMedia() не срабатывает (ВМ без камеры, отказ в разрешении), PoC продолжает работу с canvas в качестве источника кадров — ICE всё равно подключается, если активно media.peerconnection.ice.loopback = true.
Создаётся <canvas> размером 320×240 с анимацией 30fps через captureStream(30). Это генерирует предсказуемо закодированные видеокадры. После того как ICE подключается и камера была использована, PoC заменяет camera track на canvas через replaceTrack() и освобождает камеру.
Создаются два локальных RTCPeerConnection (pc1 ↔ pc2) со стандартной SDP-негодиацией и trickle ICE. Кандидаты обмениваются напрямую между пирами (peers).
RTCRtpScriptTransform назначается sender'у pc1 до SDP-негодиации. Worker перехватывает каждый закодированный кадр из отправляющего конвейера.
Для каждого из первых 200 кадров worker:
frame.data (ArrayBuffer, указывающий на нативную память)leaks[])new Uint8Array(buf).fill(0x41)Из-за отсутствия пересылки GC уничтожает C++ wrapper и освобождает нативную память. Но JavaScript-ArrayBuffer сохраняет указатель — создан dangling pointer.
Начиная с кадра 201, каждые 3 кадра worker:
0x41, аллокатор переиспользовал эту память → UAF подтверждён0x41 (write-примитив по чужому heap)Этот цикл чтения/записи по освобождённой памяти вызывает прогрессирующее повреждение heap до тех пор, пока Firefox не крашит вкладку (эквивалент SIGSEGV, указанного в advisory).
[0.4s] ✓ Track inicial: canvas (fallback)
[0.4s] ✓ ¡CONEXIÓN ICE ESTABLECIDA!
[0.5s] ▶▶▶ ¡PRIMER FRAME ENCODED RECIBIDO! ◀◀◀
[0.5s] Leak #0: 1219B → 41414141414141414141414141414141
[2.7s] Leak #49: 1497B → 41414141414141414141414141414141
[9.8s] Leak #199: 1870B → 41414141414141414141414141414141
[9.9s] 200 ArrayBuffers retenidos → memoria nativa YA liberada
[9.9s] → Dangling pointers activos, esperando reuso…
[ CRASH DE PESTAÑA — heap corruption]
Примечание: Если камера доступна и разрешение предоставлено, в логе отобразится
✓ getUserMedia concedido — ICE modo 1 activo, а затем✓ Track reemplazado: cámara → canvasпосле установки ICE-подключения. На ВМ без камеры PoC использует canvas напрямую без запроса.
Краш вкладки подтверждает повреждение heap: запись 0x41 в память, перераспределённую для других объектов (vtables, DOM-узлов), вызывает недопустимый доступ к памяти в content-процессе.
✓ Buffer[0] detached correctamente — fix activo
ArrayBuffer detached — versión parcheada ✓
DetachData() в деструкторе делает ArrayBuffer недействительным перед освобождением нативной памяти, устраняя dangling pointer.
├── poc-cve-2025-14321.html # PoC funcional (código fuente legible)
└── README.md # Este archivo
Mozilla исправила уязвимость в Firefox 146, добавив DetachData() во все деструкторы и пути очистки (teardown paths) RTCEncodedFrameBase.
Коммит: https://hg-edge.mozilla.org/mozilla-central/rev/1051067f6e83
⚠ ТОЛЬКО ДЛЯ ОБРАЗОВАТЕЛЬНЫХ И ИССЛЕДОВАТЕЛЬСКИХ ЦЕЛЕЙ
Этот PoC демонстрирует существование уязвимости вплоть до повреждения heap (краш). Он не реализует heap shaping, обход ASLR или техники hijack, необходимые для выполнения кода. Его цель — исключительно проверка и понимание CVE в контролируемых средах.