🔬
Seguridad · Fuzzing

Superando Obstáculos en Fuzzing — libdpx

Guía de parches condicionales para exploración profunda del parser DataPulse (.dpx)
Cliente
DataPulse Analytics SL
libdpx v1.9 → v2.0
Obstáculos detectados
4
CRC32, HMAC-SHA256, PRNG, validación anidada
Cobertura inicial
11%
Fuzzer bloqueado en header parsing
Cobertura estimada
73%
Tras aplicar todos los parches
Fuzzers compatibles
4
libFuzzer · AFL++ · honggfuzz · LibAFL
FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION libFuzzer AFL++ honggfuzz LibAFL clang / clang++ ASan / UBSan
1 Obstáculos identificados en libdpx
Crítico Verificación CRC32 de header

El checksum CRC32 se comprueba en las primeras líneas de dpx_parse_header(). Cualquier input mutado por el fuzzer falla aquí al 100%.

Impacto en coberturaCrítico — bloquea 89% del código
Antes (producción)
C++
dpx_parser.cc : dpx_parse_header()
uint32_t crc = crc32(data, hdr_size); if (crc != hdr->expected_crc) { return DPX_ERR_CHECKSUM; // ← fuzzer siempre aquí } dpx_process_payload(data + hdr_size);
Después (fuzzing-safe)
C++
dpx_parser.cc : dpx_parse_header()
uint32_t crc = crc32(data, hdr_size); if (crc != hdr->expected_crc) { #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return DPX_ERR_CHECKSUM; #endif } dpx_process_payload(data + hdr_size);
Crítico Firma HMAC-SHA256

Verificación criptográfica con clave secreta en dpx_verify_signature(). Imposible que el fuzzer genere inputs válidos.

Impacto en coberturaCrítico — desactiva deserializador
Antes
C++
dpx_crypto.cc
if (!dpx_verify_hmac( data, size, _secret_key, _key_len)) { return DPX_ERR_SIGNATURE; }
Después
C++
dpx_crypto.cc
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION if (!dpx_verify_hmac( data, size, _secret_key, _key_len)) { return DPX_ERR_SIGNATURE; } #endif // fuzzing: skip HMAC
Alto PRNG con semilla time(NULL)

Inicialización no determinista en dpx_init(). El mismo input genera comportamientos distintos entre ejecuciones; rompe la reproducibilidad de crashes.

Impacto en coberturaAlto — no determinismo
Antes
C++
dpx_init.cc
void dpx_init() { srand(time(NULL)); // no determinista _init_tables(); }
Después
C++
dpx_init.cc
void dpx_init() { #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION srand(0xDEADC0DE); // semilla fija #else srand(time(NULL)); #endif _init_tables(); }
Alto Validación anidada en 4 etapas

Cuatro capas de validación en dpx_deserialize() bloquean al deserializador principal. Solo se conservan magic bytes (baratos para el fuzzer).

Impacto en coberturaAlto — deserializador inaccesible
Antes
C++
dpx_deserialize.cc
validate_magic(data); // OK validate_structure(data); // ← bloqueo validate_crc_fields(data);// ← bloqueo validate_crypto(data); // ← bloqueo dpx_do_deserialize(data);
Después
C++
dpx_deserialize.cc
validate_magic(data); // conservar #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION validate_structure(data); validate_crc_fields(data); validate_crypto(data); #endif dpx_do_deserialize(data);
Riesgo de falsos positivos: dpx_do_deserialize() asume que la estructura tiene campos no-nulos tras la validación. Se debe añadir verificación defensiva de field.size > 0 antes de cualquier acceso a punteros para evitar crashes espurios.
2 Comandos de compilación por fuzzer
libFuzzer
automático — macro predefinida
# La macro se define sola con -fsanitize=fuzzer clang++ -g \ -fsanitize=fuzzer,address,undefined \ harness_dpx.cc \ libdpx/src/*.cc \ -I libdpx/include \ -o fuzz_dpx # Ejecutar ./fuzz_dpx corpus/ -max_total_time=3600
AFL++
afl-clang-fast — macro automática
# afl-clang-fast++ define la macro automáticamente afl-clang-fast++ -g \ -fsanitize=address \ -AFL_LLVM_LAF_ALL=1 \ harness_dpx.cc \ libdpx/src/*.cc \ -I libdpx/include \ -o fuzz_dpx_afl # Ejecutar en modo persistente afl-fuzz -i corpus/ -o findings/ \ -- ./fuzz_dpx_afl @@
honggfuzz
hfuzz-clang++
# hfuzz-clang++ también define la macro hfuzz-clang++ -g \ -fsanitize=address \ harness_dpx.cc \ libdpx/src/*.cc \ -I libdpx/include \ -o fuzz_dpx_hfuzz honggfuzz -i corpus/ -- ./fuzz_dpx_hfuzz
LibAFL (define manualmente)
Makefile target
# LibAFL requiere definir la macro manualmente clang++ -g \ -fsanitize=address \ -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \ -c libdpx/src/*.cc \ -I libdpx/include \ -o libdpx_fuzz.o # Linkar con el harness LibAFL clang++ libdpx_fuzz.o libafl_cc.a \ harness_libafl.cc -o fuzz_dpx_libafl
3 Mejora de cobertura estimada
📊 Cobertura por módulo — antes vs. después de parches
dpx_parse_header() 8% → 94%
dpx_verify_signature() 0% → 88%
dpx_do_deserialize() 2% → 71%
dpx_decompress_block() 0% → 63%
dpx_process_payload() 5% → 57%
dpx_init() / PRNG 100% → 100% + determinista
Cobertura después de parches Límite bloqueado sin parches
4 Tabla de parches aplicados
Obstáculo Archivo Técnica Riesgo FP Cobertura ganada Acción defensiva
CRC32 header dpx_parser.cc #ifndef FUZZING_… Bajo +86 pp Ninguna — datos post-checksum no asumen integridad
HMAC-SHA256 dpx_crypto.cc #ifndef FUZZING_… Bajo +88 pp Ninguna — deserializador no usa clave
PRNG time(NULL) dpx_init.cc Semilla fija 0xDEADC0DE Bajo Determinismo Ninguna — semilla solo afecta offsets de jitter
Validación 4 etapas dpx_deserialize.cc Skip etapas 2-4 en fuzzing Medio +69 pp Añadir guard field.size > 0 antes de punteros
5 Checklist de implementación
Consejo de Trail of Bits: Aplicar los parches incrementalmente. Tras cada parche, ejecutar el fuzzer 15 min y medir el delta de cobertura. Así se identifica qué obstáculo aportó más valor y se puede priorizar si el tiempo es limitado.