
Тестовый стенд для аудита, проверяющий, влияет ли класс ошибок рекурсии Any-unwrapping CVE-2026-0994 на ядро C upb в привязках protobuf для Ruby и PHP, с полезными нагрузками ASan/UBSan.
Статус: завершён. Шесть проверок защиты от рекурсии в трёх независимых реализациях (общее ядро upb для Ruby/PHP, отдельная управляемая реализация для C#), все опровергнуты, кроме уже публичной, уже исправленной уязвимости Python CVE, против которой проводился этот аудит. Новых уязвимостей не обнаружено.
Продолжение работы по подтверждённым CVE рекурсии при распаковке Any в привязках protobuf для Python
и JavaScript (последняя — CVE-2026-0994, чисто питоновский баг в
json_format.py в _ConvertAnyMessage, который рекурсивно вызывался через methodcaller()
вместо ConvertMessage() и молча пропускал счётчик глубины).
Вопрос: существует ли тот же класс багов в ядре C upb, встроенном
в нативные расширения Ruby и PHP?
Краткий ответ: нет. Путь JSON-декодирования, путь бинарного wire-декодирования и независимая управляемая реализация на C# корректно соблюдают свои ограничения рекурсии во всех протестированных здесь случаях, включая стрессовую нагрузку глубиной 200 000 уровней, запущенную при стеке 1 МБ. Это опровергнутая гипотеза с доказательствами, а не уязвимость — она зафиксирована здесь как чистый отрицательный результат, так же как вы бы зафиксировали опровергнутую гипотезу в любой другой исследовательской ветке.
Нативные расширения protobuf для Ruby и PHP каждое включают в себя однофайловую
амальгамацию ядра C upb (ruby-upb.c, php-upb.c), а не линкуются
с разделяемой библиотекой. Этот аудит собирает именно этот файл как автономную C-цель
— без участия среды выполнения Ruby или PHP — инициализирует upb_DefPool с
дескрипторами google.protobuf.Any / Struct / Value / ListValue и
напрямую прогоняет реальные точки входа декодера (upb_JsonDecode, upb_Decode)
с состязательными нагрузками под ASan/UBSan.
| Путь | Защита | Ограничение по умолчанию | Результат |
|---|---|---|---|
JSON-декодирование, jsondec_any (Ruby) | d->depth, проверяется в jsondec_push | 64 | Опровергнуто — чистая ошибка при нагрузке в 5000 уровней, смещение в байтах точно соответствует ~64 уровням |
Бинарное wire-декодирование, upb_Decode (Ruby) | Decode_LimitDepth | 100 | Опровергнуто — чистый kUpb_DecodeStatus_MaxDepthExceeded на граничной нагрузке в 60 уровней и на стрессовой нагрузке в 200 000 уровней, обе при стеке 8 МБ и стеке 1 МБ (имитация неглавного потока Ruby) |
| Привязка PHP | те же функции защиты | те же | Опровергнуто доказательством идентичности, а не отдельным прогоном — см. ниже |
JSON-декодирование, JsonParser.MergeAny (C#, Google.Protobuf 3.36.1) | tokenizer.RecursionDepth, проверяется в Merge() | 100 | Опровергнуто — подтверждено на нагрузке в 200 уровней; MergeAny создаёт новый JsonTokenizer через FromReplayedTokens при каждой распаковке Any, но учёт глубины корректно передаётся через него |
Бинарное wire-декодирование, CodedInputStream/Value.Parser (C#, Google.Protobuf 3.36.1) | recursionDepth/recursionLimit в потоке | 64 | Опровергнуто — чистый InvalidProtocolBufferException на граничной нагрузке в 60 уровней (те же байты нагрузки, что и в wire-тесте Ruby, переиспользованы напрямую) |
Ни в одном прогоне не наблюдалось нарушений ASan или UBSan. Ни падений, ни зависаний, ни исчерпания стека.
php-upb.c и ruby-upb.c различаются в целом примерно на 1 964 строки, но
jsondec_any, jsondec_push и константы ограничения глубины wire-декодирования
побайтово идентичны в обоих файлах (verify_php_identical.sh доказывает
это, не принимайте на веру — запустите). Поскольку сам код защиты
доказуемо одинаков, результат Ruby переносится без необходимости в избыточном
специфичном для PHP стенде.
Протестировано четыре привязки (Ruby, PHP, C#, плюс уже публичная CVE в Python), все опровергнуты, кроме той, что уже была исправлена выше по потоку. Оставшиеся открытые ветки, ни одна из которых не относится к этому конкретному классу багов:
utf8_range.c.dotnet run -- wire <payload>) ещё не подключён к
CI — автоматизирован только C#-тест в режиме JSON../fetch_source.sh # pins & clones protobuf @ ead3f0029facc43da13588132e9091bf9bd7a26f
./build.sh # compiles both harnesses w/ ASan+UBSan against the fetched source
./run_tests.sh # generates payloads, runs the full matrix, prints results
./verify_php_identical.sh # confirms the PHP claim above instead of asserting it
Требуется: gcc, protoc (apt-get install protobuf-compiler), python3,
git. Собрано и проверено на gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 и libprotoc 3.21.12 — другие версии могут вести себя
иначе; если ваши результаты не совпадают с таблицей выше, это данные, а не
ошибка в вашей конфигурации. Выясните причину, прежде чем предполагать несоответствие инструментов.
third_party/ (загружается с помощью fetch_source.sh) содержит исходный код
protobuf от Google под лицензией Apache-2.0, закреплённый на указанном выше коммите. Он
никогда не коммитится в этот репозиторий — fetch_source.sh служит якорем воспроизводимости
вместо включения чужого дерева исходников в это.