El problema fundamental que aborda este artículo es la dificultad de lograr builds reproducibles en entornos de desarrollo de C/C++ y WebAssembly, un requisito crítico para la seguridad y la confianza en la cadena de suministro de software. En un mundo donde la verificación de binarios es cada vez más importante, la no-determinismo en el proceso de compilación introduce vectores de ataque y dificulta la auditoría. La necesidad de transcompilar WebAssembly a JavaScript para asegurar la compatibilidad con clientes que tienen WASM deshabilitado, mientras se mantiene una lógica de negocio unificada, expone las complejidades inherentes a la portabilidad y la reproducibilidad en sistemas distribuidos.
La tesis central es que, incluso con las mismas entradas, los compiladores modernos pueden producir salidas binarias diferentes debido a factores ambientales o de configuración sutiles. Esto contradice la expectativa ingenua de que la compilación es un proceso puramente determinista y subraya la importancia de controlar cada aspecto del toolchain para garantizar la integridad del software. La solución propuesta, aunque pragmática, resalta las limitaciones actuales de las herramientas de compilación y la necesidad de un mayor control sobre su comportamiento.
Arquitectura del Sistema
El sistema en cuestión es Anubis, que busca implementar pruebas de trabajo basadas en WebAssembly. La arquitectura requiere que la lógica de verificación se defina una sola vez y se ejecute tanto en el cliente como en el servidor. Para los clientes sin soporte de WebAssembly, se utiliza wasm2js del proyecto Binaryen para transcompilar el código WASM a JavaScript. Este enfoque busca mantener la coherencia de la lógica de negocio a través de diferentes entornos de ejecución.
La implementación de wasm2js se compila a WebAssembly utilizando wasi-sdk, lo que permite empaquetar una versión específica de la herramienta para garantizar la consistencia. El proceso de compilación de C/C++ a WebAssembly con clang y wasi-sdk es central. Se identifican varios puntos de no-determinismo: el uso de macros __DATE__ y __TIME__, la invocación silenciosa de wasm-opt por parte de clang (que puede variar entre versiones y arquitecturas), y la sensibilidad del orden de las tablas de excepciones (try_table) a la disposición de direcciones de memoria durante la compilación. Para mitigar estos problemas, se desactiva wasm-opt explícitamente con --no-wasm-opt y se utiliza setarch --addr-no-randomize para controlar la aleatorización del espacio de direcciones. La verificación de la reproducibilidad se realiza mediante checksums SHA256 precalculados para diferentes arquitecturas (x86_64 y arm64) en un pipeline de CI.
Flujo de Compilación y Verificación de wasm2js
- 1 Fuente C/C++ Código fuente de wasm2js
- 2 wasi-sdk/clang++ Compilación a WebAssembly con opciones específicas (--no-wasm-opt, -fwasm-exc...
- 3 setarch Deshabilitar aleatorización de espacio de direcciones (--addr-no-randomize)
- 4 Generación WASM Binario wasm2js.wasm
- 5 Cálculo SHA256 Generación de checksum del binario resultante
- 6 Verificación CI Comparación con checksums pre-registrados (x86_64, arm64)
| Capa | Tecnología | Justificación |
|---|---|---|
| compute | WebAssembly | Formato de bytecode para la lógica de prueba de trabajo y para la herramienta wasm2js. |
| compute | JavaScript | Target de transcompilación para clientes sin soporte de WebAssembly, asegurando compatibilidad. |
| orchestration | wasi-sdk | SDK para compilar aplicaciones C/C++ a WebAssembly System Interface (WASI). |
| orchestration | clang++ | Compilador C++ utilizado para generar binarios WebAssembly. -O3, -fwasm-exceptions, -mllvm -wasm-use-legacy-eh=false, --no-wasm-opt |
| data-processing | wasm2js (Binaryen) | Herramienta para transcompilar WebAssembly a JavaScript, asegurando la ejecución en entornos sin WASM. |
| security | sha256sum | Herramienta para calcular y verificar checksums SHA256, esencial para la reproducibilidad de builds. |
#include <iostream>
int main() {
std::cout << __DATE__ << " " << __TIME__ << std::endl;
return 0;
}cd ./utils/wasm/wasm2js
./build.sh
if sha256sum -c --status shasums.x86_64; then
echo "OK: rebuilt modules match the recorded x86_64 checksums"
elif sha256sum -c --status shasums.arm64; then
echo "OK: rebuilt modules match the recorded arm64 checksums"
else
echo "::error::rebuilt wasm2js/wasm-opt match neither recorded checksum set on ${{ matrix.runner }}" >&2
sha256sum wasm-opt_130.wasm wasm2js_130.wasm
exit 1
fiFundamentos Teóricos
El problema de la reproducibilidad de builds se conecta directamente con los principios de la verificación de software y la seguridad de la cadena de suministro, campos de estudio activos en la informática teórica y aplicada. Aunque no se cita un paper específico, el concepto de 'reproducible builds' ha sido un tema de investigación en proyectos como Debian y GNU Guix, buscando garantizar que el mismo código fuente siempre produzca el mismo binario. Esto se relaciona con la criptografía y la integridad de los sistemas, donde la capacidad de verificar que un binario no ha sido alterado o que corresponde exactamente a un código fuente conocido es fundamental.
La sensibilidad del compilador a factores ambientales como la disposición de direcciones de memoria o la versión de herramientas externas evoca los desafíos de la 'compilación cruzada' y la 'portabilidad de software', donde las diferencias en los entornos de build pueden llevar a comportamientos inesperados. Los trabajos sobre la formalización de los procesos de compilación y la verificación de la corrección de los compiladores (como el proyecto CompCert de Xavier Leroy) buscan precisamente eliminar estas fuentes de no-determinismo y garantizar la equivalencia semántica entre el código fuente y el binario generado, independientemente del entorno de compilación.