En pocas palabras
Generar o modificar automáticamente muchas entradas, incluidas inesperadas o inválidas, para descubrir fallos y comportamientos anómalos.
Qué problema resuelve
Casos límite que las pruebas con ejemplos escritos manualmente suelen omitir, como bloqueos, errores de memoria o excepciones inesperadas.
Cuándo utilizarlo
Parsers, APIs, procesamiento de archivos y protocolos. Ejemplo: variar un JSON enviado a una API para encontrar entradas que provocan un fallo interno en lugar de un rechazo controlado.
De la idea a la práctica
Funcionamiento, ejemplos y decisiones para pasar al siguiente nivel.
Cómo funciona
Un fuzzer genera o modifica entradas y observa el programa. Los motores guiados por cobertura conservan casos que recorren código nuevo y los usan como base para nuevas mutaciones.
Ejemplo: probar un importador
Parte de archivos pequeños válidos e introduce campos ausentes, tamaños extremos, codificaciones inesperadas y estructuras incompletas. Observa excepciones, consumo de recursos y consistencia del resultado.
Cuando aparece un fallo, reduce la entrada hasta obtener el caso mínimo que lo reproduce. Corrige la causa y conserva ese caso como prueba de regresión.
Herramientas por lenguaje
| Lenguaje | Herramienta |
|---|---|
| C / C++ | libFuzzer, AFL++, con AddressSanitizer y UBSan |
| Rust | cargo fuzz (libFuzzer) |
| Go | Fuzzing nativo (go test -fuzz) |
| Java / JVM | Jazzer (@FuzzTest con JUnit 5) |
| Python | Atheris; Hypothesis para pruebas basadas en propiedades |
| JavaScript | Jazzer.js |
| APIs HTTP | Schemathesis o RESTler a partir de OpenAPI |
Ejemplo en Go
func FuzzParsearMonto(f *testing.F) {
for _, semilla := range []string{"1500000", "1.500.000,50", "0", "-1", ""} {
f.Add(semilla)
}
f.Fuzz(func(t *testing.T, entrada string) {
monto, err := ParsearMonto(entrada)
if err != nil {
return // rechazar entradas inválidas es correcto
}
// Propiedades que siempre deben cumplirse
if monto.Negativo() {
t.Fatalf("monto negativo aceptado: %q", entrada)
}
if rt, _ := ParsearMonto(monto.String()); !rt.Igual(monto) {
t.Fatalf("ida y vuelta inconsistente: %q → %v", entrada, monto)
}
})
}
go test -fuzz=FuzzParsearMonto -fuzztime=5m ./montos
Las entradas que provocan fallos se guardan en testdata/fuzz/ y pasan a ejecutarse como pruebas normales.
Fuzzing de APIs a partir del contrato
Herramientas como Schemathesis leen la especificación OpenAPI, generan peticiones válidas e inválidas y comprueban que la API no devuelve errores 500, que las respuestas cumplen el esquema y que los códigos son coherentes. Es una forma barata de encontrar validaciones ausentes y excepciones no controladas antes que un atacante.
Oráculos: cómo saber que algo falló
| Oráculo | Ejemplo |
|---|---|
| Fallo del proceso | Pánico, excepción no controlada, error de memoria |
| Propiedad (invariante) | El resultado nunca es negativo; ordenar es idempotente |
| Ida y vuelta | parsear(serializar(x)) == x |
| Diferencial | Dos implementaciones (vieja y nueva) dan el mismo resultado |
| Recursos | Tiempo o memoria por encima de un límite |
El diferencial es especialmente útil en migraciones: compara la librería antigua y la nueva con millones de entradas.
Dónde priorizar
Parsers de archivos y mensajes recibidos de terceros (extractos bancarios, XML de facturación, imágenes de documentos), deserialización, decodificadores, cálculos con aritmética delicada y cualquier código que procese entradas no confiables. Proyectos open source críticos pueden integrarse en OSS-Fuzz, que los ejecuta continuamente.
Criterios de implementación
Define qué comportamiento cuenta como fallo y restablece el estado entre ejecuciones. Limita recursos y tiempos de cada caso. Para parsers nativos, combinar fuzzing con sanitizadores facilita detectar errores de memoria que no siempre provocan un cierre inmediato.
Continúa en las fuentes
Documentación oficial para verificar detalles y explorar más.