Реклама
Перетяжка // Коробка 3.0

libheif 1.23.4 закрыла уязвимости при чтении и декодировании HEIF

Drop-in замена 1.23.3 с совместимым ABI. Что именно чинили и кому обновляться в первую очередь.

Обложка: libheif 1.23.4 закрыла уязвимости при чтении и декодировании HEIF

Вышла libheif 1.23.4, библиотека для чтения и записи HEIF и AVIF, которую используют для работы с изображениями. Выпуск закрывает шесть уязвимостей, три из них с высокой оценкой, и объявлен ABI- и API-совместимой заменой 1.23.3: релиз сохраняет интерфейсы предыдущей версии. Это уже третий выпуск безопасности подряд после 1.23.2 и 1.23.3, все шесть проблем нашёл один исследователь под ником hackerman70000.

Уязвимости затрагивают разные этапы обработки файла. Для ошибки iinf/max_items достаточно чтения контейнера через heif_context_read_from_file, без декодирования изображения. Отдельная проблема возникает при обходе ссылок на этапе декодирования, а взаимная блокировка потоков связана с параллельным декодированием grid-тайлов. Поэтому проверка только загрузки метаданных не воспроизводит все три проблемы с высокой оценкой.

Ключевые выводы
  • Лимит max_items не применялся к дочерним боксам iinf: файл объявляет сколько угодно элементов, разбор становится квадратичным. Замер: 1,7, 6 и 23 секунды процессора на файлы 1, 2 и 4,1 МБ; файл 6,9 МБ держит 480 МБ памяти, 23 МБ файл 1,5 ГБ, и max_total_memory этого не видит. В сборке под Emscripten тот же счётчик задавал размер alloca на 64-килобайтном стеке WebAssembly: 16 000 элементов проходят, 17 000 роняют вызов, с 18 000 модуль ломается необратимо.
  • Неограниченная рекурсия при обходе ссылок могла исчерпать стек; исправленный обход на этапе декодирования использует явный список в куче.
  • Инверсия порядка блокировок при параллельном декодировании тайлов grid (включено по умолчанию) давала вечный дедлок; циклические графы ссылок отклоняются до начала декодирования.
  • Три проблемы средней важности: чтение за границей буфера в плагинах кодировщиков aom и x265, неисправимая утечка памяти в heif_track_get_next_raw_sequence_sample и чтение за границей в экспериментальном плагине WebCodecs.
  • Уязвимы версии от 1.16.0 до 1.23.3 включительно (в зависимости от проблемы), исправлено в 1.23.4; номера CVE будут присвоены позже.

Как одна неучтённая ветка отключила лимит на число элементов

В отчёте описана классическая ошибка перегруженного параметра. Функция Box::read_children принимает max_number, у которого два смысла: со значением-сентинелом READ_CHILDREN_ALL она читает до конца бокса и проверяет лимит max_items, а с любым другим числом читает ровно столько детей и ничего не проверяет. Box_iinf::parse передавала туда счётчик из самого файла, поэтому проверка, написанная специально для iinf, никогда не выполнялась.

Дальше цена растёт квадратично: для каждого элемента вызывается полный проход по списку ссылок iref без раннего выхода. Исследователь измерил на одном ядре 1,7, 6,0 и 23 секунды для файлов в 1,0, 2,0 и 4,1 МБ; абсолютные цифры различаются вдвое между машинами, но рост в 3–4 раза на каждое удвоение стабилен против 2 раз у линейного парсера. Всё это происходит внутри вызова, который затем возвращает heif_error_Ok, без коллбэка отмены и прогресса. В 1.23.4 лимит проверяется заранее, объявленное число ограничено оставшимся входом, а запросы к iref индексируются по идентификатору элемента: на файле с 1 000 элементов и 160 000 ссылок dimg время чтения упало с 1,83 до 0,26 секунды.

Сборка под WebAssembly страдала отдельно. Два вспомогательных метода в Emscripten-обвязке выделяли массив идентификаторов через alloca с размером из того же счётчика. Скрипт сборки не задаёт ни размер стека, ни проверку его переполнения, поэтому использовался стек по умолчанию в 64 КБ: 16 000 элементов занимают 64 000 байт и декодируются, 17 000 прерывают вызов, а с 18 000 переполнение уходит в сегмент статических данных и экземпляр модуля перестаёт работать до перезагрузки страницы. Хелпер вызывается при каждом HeifDecoder.decode().

Рекурсия и дедлок: две другие высокие уязвимости

Вторая проблема: проверка на циклы ссылок между производными элементами была рекурсивной, и длинная цепочка исчерпывала стек. Без лимита на число элементов глубина ничем не ограничена. Исправленный обход на этапе декодирования использует явный рабочий список в куче вместо рекурсии.

Третья проблема затрагивает серверы с параллельным декодированием: параллельное декодирование тайлов grid включено по умолчанию, а ImageItem::decode_image() держит нерекурсивный мьютекс элемента на время вложенных декодирований. Два рабочих потока, чьи элементы ссылаются друг на друга, берут два мьютекса в противоположном порядке и зависают навсегда; процесс не падает и не освобождает ресурсы. Исправление отклоняет циклические графы декодирования, включая рёбра dimg и alpha auxl, до старта.

Кому обновляться в первую очередь

Всем, кто принимает HEIC и AVIF от пользователей: это серверные конвертеры, бэкенды фотохостингов и мессенджеров, CI-пайплайны с ImageMagick, а также веб-приложения, которые декодируют HEIC на клиенте через libheif-js или собственную Emscripten-сборку. Для последних важно, что обрушение WebAssembly-модуля необратимо в рамках страницы. Мейнтейнеры советуют обновиться всем пользователям; в дистрибутивах смотрите на пакет libheif1 или libheif в вашем менеджере. Уязвимость средней важности в плагине WebCodecs касается только Emscripten-сборок с WITH_WEBCODECS=ON, по умолчанию выключенной опции, и только без libde265.

Отдельный отчёт о расходе ресурсов опубликован для Go-библиотеки Excelize: файл на 3 КБ заставляет библиотеку выполнять произвольное число итераций хеширования, и патча пока нет (исправление в сохранённом отчёте не указано). Из недавних выпусков безопасности того же масштаба мы разбирали curl 8.22.0 с девятью уязвимостями и внеочередные релизы PostgreSQL от Postgres Pro.

Источники: libheif v1.23.4 – security maintenance, release notes, GHSA-vg7w-rp49-4fc2: The max_items security limit is not enforced for iinf child boxes, GHSA-xrp2-63fq-jm8q: Unbounded iref entry list and unbounded recursion, GHSA-prgh-72vc-3xmc: Lock-order inversion in parallel grid tile decoding