Fuzzing del compilador de Gleam: búsqueda de bugs con programas aleatorios estructurados
Un desarrollador independiente ha emprendido una ambiciosa iniciativa para someter al compilador de Gleam, el lenguaje funcional que compila tanto a JavaScript como a Erlang, a un proceso de fuzzing estructura-aware o "consciente de la estructura". El objetivo: generar programas aleatorios pero sintácticamente válidos de forma masiva, compilar y comparar los resultados para descubrir errores ocultos que los tests convencionales no detectan.
La motivación, según explica el autor en su blog, surgió de una observación persistente: cada vez que revisaba el changelog y el rastreador de issues del proyecto Gleam, encontraba problemas relacionados con la generación de código o con diferencias de salida entre los backends de Erlang y JavaScript. "No había forma de calcular todos los programas Gleam posibles, ejecutarlos y ver si había algún problema", señala. La idea es similar a contar posiciones en un tablero de ajedrez: un espacio cuasi-infinito de combinaciones donde pueden esconderse casos límite no probados.
Antes de optar por el fuzzing estructura-aware, el autor experimentó con un enfoque basado en modelos de inteligencia artificial. Instruyó a un LLM para que analizara issues históricos de Gleam y propusiera nuevos casos límite: combinaciones exóticas de bit arrays, funciones anidadas, patrones use anidados. Tras gastar alrededor de 20 dólares en tokens, el resultado fue magro: un único hallazgo, que fue reportado y corregido de inmediato (issue #5613 en el repositorio oficial). El propio autor reconoce las limitaciones de esta aproximación: resulta cara, no es determinista y se asemeja "a tirar de la palanca de una máquina tragaperras".
El verdadero proyecto adoptó una estrategia diferente. En lugar de alimentar al compilador con bytes aleatorios, técnica habitual en el fuzzing de archivos binarios, imágenes o protocolos de red —y que ha destapado fallos de seguridad históricos como el CVE-2007-6715 en Firefox—, el fuzzer genera programas válidos respetando la gramática de Gleam. Para ello se apoya en el crate rust-fuzz/cargo-fuzz y en la API pública del compilador, decisión clave que garantiza compatibilidad con futuras versiones y evita generar falsos positivos por manipuler detalles internos de implementación.
Gleam reúne varias propiedades que lo convierten en un candidato ideal para esta técnica. En primer lugar, su doble destino —Erlang y JavaScript— permite comparar el output de un mismo programa en ambos backends y señalar discrepancias automáticamente. Su sintaxis minimalista facilita generar código válido que cubra prácticamente todos los conceptos del lenguaje con relativamente poco código de generación. El sistema de tipos estáticos elimina la posibilidad de crashes en tiempo de ejecución, aunque no descarta bugs en la inferencia de tipos, un área con historial de issues en el proyecto. Además, su naturaleza funcional y el hecho de que todo sea una expresión simplifican la composición y estructuración de los programas sintéticos. Por último, que el compilador esté escrito en Rust facilita enormemente la integración con las herramientas de fuzzing existentes.
El proyecto se inspira en trabajos previos de Nick Fitzgerald sobre generadores de casos de prueba y en recursos académicos como fuzzingbook.org. Cabe destacar que el autor apuesta por un enfoque generation-based (basado en generación), aunque reconoce que, según investigaciones recientes del propio Fitzgerald aplicadas a WebAssembly, el enfoque mutation-based (basado en mutación de corpus existentes) tiende a descubrir más problemas. La implementación de mutaciones queda como trabajo futuro.
En su fase inicial, el fuzzer trabaja sobre el parser del compilador. Los ejemplos muestran cómo se clasifican los outputs: un programa válido como "pub fn main() { 1 }" se compila produciendo 39 bytes en JavaScript, 32 en TypeScript y 238 en Erlang; otros inputs generan errores de parseo o son rechazados en la fase de análisis para algún backend concreto. Las primeras ejecuciones con libFuzzer, limitadas a un segundo por la verbosidad del output, ya muestran el mecanismo clásico de estos sistemas: el corpus se reduce progresivamente, se identifican nuevas coberturas y se conservan los inputs que aportan valor exploratorio.
El código completo del fuzzer está disponible en una rama del fork del autor en GitHub (daniellionel01/gleam, rama "fuzzing"), permitiendo a la comunidad replicar, extender o auditar el trabajo. De momento no se reportan bugs críticos adicionales encontrados, pero la infraestructura queda montada para iterar: las siguientes fases previsiblemente abordarán el type checker, la generación de código y la comparación cruzada entre backends, las áreas donde residen los problemas más sutiles del compilador.
La iniciativa llega en un momento de creciente madurez del lenguaje Gleam, que ha ganado tracción como alternativa amigable sobre la BEAM y como puente hacia el ecosistema JavaScript. Iniciativas de este tipo, aunque artesanales, ilustran cómo las comunidades de lenguajes relativamente jóvenes pueden aplicar técnicas de verificación industrial —el fuzzing estructura-aware es habitual en proyectos integrados en Google OSS-Fuzz— para reforzar la confianza en compiladores que cada vez más empresas consideran para producción.
