
CVE-2026-0994 Any-unwrapping 재귀 버그 클래스가 Ruby 및 PHP protobuf 바인딩의 upb C 코어에 영향을 미치는지 ASan/UBSan 페이로드로 테스트하는 감사 하네스.
상태: 완료. 세 개의 독립적인 구현(Ruby/PHP가 공유하는 upb 코어, C#의 별도 관리형 구현)에 걸친 6개의 재귀 가드 검사로, 이 감사가 대조하던 이미 공개되고 이미 패치된 Python CVE 하나를 제외하고 모두 반증되었다. 새로운 취약점은 발견되지 않았다.
protobuf의 Python 및 JavaScript 바인딩에서 확인된 Any-언래핑 재귀 CVE(가장 최근 것은 CVE-2026-0994로, json_format.py의 _ConvertAnyMessage에 있는 순수 Python 버그이며, ConvertMessage() 대신 methodcaller()를 통해 재귀하여 깊이 카운터를 조용히 건너뛰었다)에 대한 후속 조사다.
질문: 동일한 버그 클래스가 Ruby 및 PHP 네이티브 확장에 번들된 C upb 코어에도 존재하는가?
짧은 답: 아니다. JSON 디코드 경로, 바이너리 와이어 디코드 경로, 그리고 독립적인 관리형 C# 구현 모두 여기서 테스트된 모든 경우에서 재귀 한도를 올바르게 강제한다. 여기에는 1MB 스택에서 실행된 200,000단계 스트레스 페이로드도 포함된다. 이는 증거를 갖춘 반증된 가설이지 취약점이 아니다 — 다른 어떤 연구 스레드에서든 반증된 가설을 기록하듯이, 깔끔한 부정적 결과로 여기에 기록한다.
protobuf의 Ruby 및 PHP 네이티브 확장은 각각 공유 라이브러리에 링크하는 대신 upb의 C 코어를 단일 파일 아말감(ruby-upb.c, php-upb.c)으로 벤더링한다. 이 감사는 바로 그 파일을 독립형 C 타깃으로 빌드하며 — Ruby나 PHP 런타임은 관여하지 않는다 — google.protobuf.Any / Struct / Value / ListValue 디스크립터로 upb_DefPool을 부트스트랩하고, 실제 디코더 진입점(upb_JsonDecode, upb_Decode)을 적대적 페이로드로 직접 구동하며, ASan/UBSan 하에서 수행한다.
| 경로 | 가드 | 기본 한도 | 결과 |
|---|---|---|---|
JSON 디코드, jsondec_any (Ruby) | d->depth, jsondec_push에서 검사됨 | 64 | 반증됨 — 5000단계 페이로드에서 깔끔한 오류, 바이트 오프셋이 정확히 ~64단계와 일치 |
바이너리 와이어 디코드, upb_Decode (Ruby) | Decode_LimitDepth | 100 | 반증됨 — 60단계 경계 페이로드와 200,000단계 스트레스 페이로드 모두에서 깔끔한 kUpb_DecodeStatus_MaxDepthExceeded, 둘 다 8MB 스택과 1MB 스택(비메인 Ruby 스레드에 근사) 하에서 |
| PHP 바인딩 | 동일한 가드 함수 | 동일 | 동일성 증명으로 반증됨, 별도 실행이 아님 — 아래 참조 |
JSON 디코드, JsonParser.MergeAny (C#, Google.Protobuf 3.36.1) | tokenizer.RecursionDepth, Merge()에서 검사됨 | 100 | 반증됨 — 200단계 페이로드에서 확인됨; MergeAny는 Any-언래핑마다 FromReplayedTokens를 통해 새 JsonTokenizer를 생성하지만, 깊이 계산이 이를 통해 올바르게 전파됨 |
바이너리 와이어 디코드, CodedInputStream/Value.Parser (C#, Google.Protobuf 3.36.1) | 스트림의 recursionDepth/recursionLimit | 64 | 반증됨 — 60단계 경계 페이로드(Ruby 와이어 테스트와 동일한 페이로드 바이트, 직접 재사용)에서 깔끔한 InvalidProtocolBufferException |
어떤 실행에서도 ASan 또는 UBSan 위반은 관찰되지 않았다. 크래시도, 행(hang)도, 스택 고갈도 없었다.
php-upb.c와 ruby-upb.c는 전체적으로 약 1,964줄 차이가 나지만, jsondec_any, jsondec_push, 그리고 와이어 깊이 한도 상수는 두 파일 간에 바이트 단위로 동일하다(verify_php_identical.sh가 이를 증명한다 — 믿지 말고 실행해 보라). 가드 코드 자체가 증명 가능하게 동일하므로, Ruby 결과는 중복된 PHP 전용 하네스 없이도 전이된다.
네 개의 바인딩(Ruby, PHP, C#, 그리고 Python의 이미 공개된 CVE)을 테스트했으며, 업스트림에서 이미 패치된 하나를 제외하고 모두 반증되었다. 남은 열린 스레드는 이 특정 버그 클래스에 해당하지 않는다:
utf8_range.c 검증.dotnet run -- wire <payload>)는 아직 CI에 연결되지 않았다 — JSON 모드 C# 테스트만 자동화되어 있다../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로 가져옴)에는 위 커밋에 고정된,
Apache-2.0 라이선스의 Google protobuf 소스가 포함되어 있습니다. 이
디렉터리는 이 저장소에 절대 커밋되지 않습니다 — 다른 사람의 소스 트리를
이 저장소에 벤더링하는 대신, fetch_source.sh가 재현성의 기준점 역할을
합니다.