
Arnés de auditoría que prueba si la clase de bug de recursión de desempaquetado Any de CVE-2026-0994 afecta al núcleo C de upb en los bindings de protobuf de Ruby y PHP, con payloads de ASan/UBSan.
Estado: Completa. Seis comprobaciones de protección contra recursión en tres implementaciones independientes (núcleo upb compartido de Ruby/PHP, la implementación administrada separada de C#), todas refutadas excepto la CVE de Python ya pública y ya parcheada contra la que se contrastaba esta auditoría. No se encontraron nuevas vulnerabilidades.
Continuación de las CVE confirmadas de recursión por desempaquetado de Any en los bindings de Python y JavaScript de protobuf (la más reciente, CVE-2026-0994, un fallo de Python puro en _ConvertAnyMessage de json_format.py, que recursaba mediante methodcaller() en lugar de ConvertMessage() y omitía silenciosamente el contador de profundidad).
Pregunta: ¿existe la misma clase de fallo en el núcleo C upb incluido en las extensiones nativas de Ruby y PHP?
Respuesta corta: no. La ruta de decodificación JSON, la ruta de decodificación binaria del wire y la implementación administrada independiente de C# aplican correctamente sus límites de recursión en todos los casos probados aquí, incluida una carga de estrés de 200.000 niveles ejecutada con una pila de 1MB. Esta es una hipótesis refutada con evidencia, no una vulnerabilidad — se registra aquí como un resultado negativo limpio, de la misma forma en que se registraría una hipótesis refutada en cualquier otro hilo de investigación.
Las extensiones nativas de Ruby y PHP de protobuf incluyen cada una una amalgamación de un solo archivo del núcleo C de upb (ruby-upb.c, php-upb.c) en lugar de enlazar una biblioteca compartida. Esta auditoría compila ese archivo exacto como un objetivo C independiente — sin intervención del runtime de Ruby ni de PHP —, inicializa un upb_DefPool con descriptores de google.protobuf.Any / Struct / Value / ListValue, y acciona los puntos de entrada reales del decodificador (upb_JsonDecode, upb_Decode) directamente con cargas adversariales, bajo ASan/UBSan.
| Ruta | Protección | Límite por defecto | Resultado |
|---|---|---|---|
Decodificación JSON, jsondec_any (Ruby) | d->depth, comprobado en jsondec_push | 64 | Refutada — error limpio con una carga de 5000 niveles, el desplazamiento en bytes coincide exactamente con ~64 niveles |
Decodificación binaria del wire, upb_Decode (Ruby) | Decode_LimitDepth | 100 | Refutada — kUpb_DecodeStatus_MaxDepthExceeded limpio en una carga límite de 60 niveles y en una carga de estrés de 200.000 niveles, ambas con una pila de 8MB y con una pila de 1MB (aproximando un hilo de Ruby no principal) |
| Binding de PHP | las mismas funciones de protección | el mismo | Refutada por prueba de identidad, no por una ejecución separada — véase más abajo |
Decodificación JSON, JsonParser.MergeAny (C#, Google.Protobuf 3.36.1) | tokenizer.RecursionDepth, comprobado en Merge() | 100 | Refutada — confirmada con una carga de 200 niveles; MergeAny construye un nuevo JsonTokenizer mediante FromReplayedTokens por cada desempaquetado de Any, pero la contabilidad de profundidad se propaga correctamente a través de él |
Decodificación binaria del wire, CodedInputStream/Value.Parser (C#, Google.Protobuf 3.36.1) | recursionDepth/recursionLimit en el flujo | 64 | Refutada — InvalidProtocolBufferException limpia en una carga límite de 60 niveles (los mismos bytes de carga que la prueba del wire de Ruby, reutilizados directamente) |
No se observó ninguna violación de ASan ni de UBSan en ninguna ejecución. Sin caídas, sin bloqueos, sin agotamiento de pila.
php-upb.c y ruby-upb.c difieren en ~1.964 líneas en total, pero jsondec_any, jsondec_push y las constantes del límite de profundidad del wire son idénticas byte a byte entre ambos archivos (verify_php_identical.sh lo demuestra, no lo des por sentado — ejecútalo). Dado que el código de protección en sí es demostrablemente el mismo, el resultado de Ruby se traslada sin necesidad de un arnés redundante específico para PHP.
Cuatro bindings probados (Ruby, PHP, C#, más la CVE ya pública de Python), todos refutados excepto el que ya estaba parcheado en upstream. Hilos abiertos restantes, ninguno de ellos de esta clase de fallo específica:
utf8_range.c incluido.dotnet run -- wire <payload>) aún no está integrada en CI — solo la prueba de C# en modo JSON está automatizada../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
Requiere: gcc, protoc (apt-get install protobuf-compiler), python3,
git. Compilado y verificado con gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 y libprotoc 3.21.12 — otras versiones pueden comportarse
de forma diferente; si tus resultados no coinciden con la tabla anterior, eso son datos, no
un fallo en tu configuración. Averigua por qué antes de asumir que es un desajuste de herramientas.
third_party/ (obtenido por fetch_source.sh) contiene el código fuente de protobuf de Google,
con licencia Apache-2.0, fijado al commit anterior. Nunca se
confirma en este repositorio — fetch_source.sh es el ancla de reproducibilidad
en lugar de incorporar el árbol de código fuente de otro en este.