
CVE-2026-74943, использование после освобождения в Firefox RasterImage (sec-high)
Этот каталог содержит честный каркас воспроизведения для межпоточного
use-after-free в mozilla::image::RasterImage::OnSurfaceDiscarded. Он предназначен
для запуска против локально загруженной уязвимой сборки Firefox с использованием только
синтетических данных изображений. Он никогда не взаимодействует с какой-либо живой или сторонней системой.
Соответствующий репозиторий PoC: https://github.com/defineid/Revenant
Описание: writeup-rasterimage-uaf.html в этом портфолио.
Они были подтверждены дизассемблированием уязвимой сборки (libxul.so, Firefox
140.9.0esr) в ходе первоначального исследования; зафиксированный скриншот
assets/writeup-uaf-disasm.png — это тот самый анализ. Имена функций взяты из
официального файла символов Mozilla Breakpad для этой точной сборки; поставляемый
libxul.so лишён символов. Они не были повторно дизассемблированы в окружении,
в котором собирался этот пакет PoC (см. Статус проверки).
| Факт | Доказательство | Источник |
|---|---|---|
Уязвимая функция — RasterImage::OnSurfaceDiscarded(SurfaceKey const&) | FUNC 3eba200 16c 0 mozilla::image::RasterImage::OnSurfaceDiscarded(...) | .sym с сервера символов Mozilla |
Раннее чтение байта по адресу this+0xC0 (в описании: mIsBeingDestroyed) | 3eba20a: cmp BYTE PTR [rdi+0xc0],0x0, где rdi = this | objdump -d libxul.so |
AddRef счётчика ссылок RasterImage по адресу this+0x40 | 3eba25a: lock inc QWORD PTR [rbx+0x40] (выполняется дважды), rbx = this | objdump -d libxul.so |
Выделяется runnable размером 0x20 байт, который захватывает this | 3eba264: mov edi,0x20 → call moz_xmalloc@plt; 3eba280: mov QWORD PTR [rax+0x10],rbx | objdump -d libxul.so |
Runnable отправляется на выполнение, затем this освобождается через Release | 3eba298: call QWORD PTR [rcx+0x28] (виртуальная диспетчеризация); 3eba29b: lock dec QWORD PTR [rbx+0x40] | objdump -d libxul.so |
Достижение счётчиком ссылок 0 запускает ~RasterImage, затем free | je 3eba345 → call 3ebbe90 <~RasterImage> → call free@plt | objdump + .sym |
Отправленный runnable захватывает лямбду OnSurfaceDiscarded::$_0 | FUNC 5279d90 ... RunnableFunction<...OnSurfaceDiscarded(...)::$_0>::Run() | .sym с сервера символов Mozilla |
| Поле | Значение |
|---|---|
| CVE | CVE-2026-74943 |
| Компонент | Core / Graphics: ImageLib |
| Класс | Use-After-Free (CWE-416) |
| Серьёзность | sec-high (достижимая из веба проблема безопасности памяти) |
| Исправлено в | Firefox 154 · ESR 140.14 |
| Баг | bugzilla.mozilla.org/show_bug.cgi?id=2057308 (доступ ограничен по соображениям безопасности) |
SurfaceCacheImpl::Remove уведомляет владеющее изображение о том, что декодированная поверхность была
отброшена, вызывая OnSurfaceDiscarded через сырой, невладеющий
указатель ImageKey, в потоке декодера / ImageIO, удерживая мьютекс кэша
поверхностей. В уязвимой сборке этот обработчик:
this+0xC0 (cmp BYTE PTR [rdi+0xc0],0x0).
В описании он идентифицируется как mIsBeingDestroyed — более ранняя мера защиты.this, инкрементируя счётчик ссылок RasterImage по
адресу this+0x40 (lock inc QWORD PTR [rbx+0x40]). Это обычный
add-ref, а не атомарный add-ref-if-nonzero.moz_xmalloc(0x20) выделяет RunnableFunction, который захватывает this
(mov QWORD PTR [rax+0x10],rbx), отправляет его глобальной цели событий
(call QWORD PTR [rcx+0x28]), затем освобождает локальную ссылку
(lock dec QWORD PTR [rbx+0x40]).Поскольку чтение флага и инкремент счётчика — это две отдельные
операции над неблокирующим счётчиком ссылок, они могут охватить момент, когда главный
поток сбрасывает последнюю ссылку. Затем поток декодера выполняет AddRef с нуля,
воскрешая объект, деструктор которого уже отработал. Когда
деструктор завершается и вызывает free для RasterImage, поставленный в очередь runnable позже выполняется
в главном потоке и читает освобождённую память.
Исправление прекращает прямое обращение к изображению: OnSurfaceDiscarded захватывает
сильную ссылку на отдельно подсчитываемый ProgressTracker и восстанавливает
изображение в главном потоке через слабую обратную ссылку трекера, повышая её
атомарным add-ref-if-live; флаг mIsBeingDestroyed удалён.
Обычная веб-страница в процессе контента создаёт давление на кэш поверхностей (множество отбрасываемых декодированных поверхностей), одновременно освобождая фрейм, владеющий запросами изображений. Не требуется ни клик, ни разрешение, ни расширение, ни изменение настроек. Гонка происходит между уведомлением об отбрасывании из потока декодера и финальным освобождением того же изображения в главном потоке.
https://ftp.mozilla.org/pub/firefox/releases/140.9.0esr/.
Сборка с ASAN (Nightly asan или локальная сборка с --enable-address-sanitizer)
делает use-after-free наблюдаемым в виде чистого отчёта вместо
случайного молчаливого падения.<canvas> во время выполнения; он ничего не загружает из сети.Откройте trigger.html в уязвимой сборке, желательно по пути file:// или через
локальный статический сервер, который вы контролируете:
/path/to/firefox --new-instance --profile ./poc-profile ./trigger.html
По умолчанию драйвер выполняет плотный цикл микрозадач для максимального перекрытия декодирования и освобождения. Добавление ?tick=N к URL переключает его на уступающий цикл setTimeout(N), который нагружает так же сильно, но даёт событийному циклу «дышать», что позволяет автоматизированному драйверу делать шаги и снимки экрана во время работы. Приложенный trigger-running.png был получен именно так — прогоном локальной сборки 140.11.0esr через Marionette с ?tick=4: на нём виден работающий стенд (счётчики циклов и teardown растут). Эта сборка пропатчена, поэтому падения не происходит; изображение лишь доказывает, что драйвер работает. В headless-режиме закадровые изображения не отрисовываются, поэтому счётчик «images decoded» там остаётся на 0; в headed-режиме уязвимая сборка их декодирует, и именно там воспроизводится гонка.
Дайте странице поработать. Она многократно декодирует множество различных крупных изображений (чтобы заполнить кэш поверхностей и вызвать вытеснение), одновременно разрушая поддерево DOM, которому они принадлежат (чтобы вызвать финальное освобождение владеющего изображения). Это максимизирует перекрытие «surface discarded» в потоке декодера с «last release» в главном потоке. Гонки вероятностны; оставьте её работать.
heap-use-after-free, где кадр READ находится внутри
RasterImage::OnSurfaceDiscardedInternal / отправленного runnable, а кадр
freed — в teardown RasterImage::Release. Без ASAN это может проявляться
как периодическое падение контент-процесса.ProgressTracker и отбрасывает
уведомление, если уничтожение уже зафиксировано.