Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
gosentry — Toolchain de Go orientado a la seguridad, centrado en capacidades de fuzzing de última generación. | Kitploit
Herramientas/GitHubGitHub/trailofbits/gosentry
Análisis EstáticoAnálisis Dinámico (Sandboxing)Análisis de VulnerabilidadesAnálisis de CódigoFuzzingDevSecOpsAnálisis de BinariosAprendizaje y Educación
GitHubtrailofbits/gosentry

gosentry

Toolchain de Go orientado a la seguridad, centrado en capacidades de fuzzing de última generación.

Ver Repositorio
1173hace 9 díasRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

gosentry

integration tests

gosentry es un fork centrado en la seguridad de la cadena de herramientas de Go, que integra numerosas funciones para campañas de fuzzing de vanguardia en bases de código Go. Si antes usabas go test -fuzz, deberías usar gosentry como reemplazo. Incluye varias mejoras de fuzzing y detectores de errores que no están presentes de forma nativa en la cadena de herramientas de Go. Consulta el TLDR; a continuación. También puedes leer el artículo de blog asociado aquí.

TLDR (funcionalidades y opciones):

  • Fuzzear entradas de tipo struct directamente (sin necesidad de un parser personalizado). Añadir semillas con f.Add(Input{N: 7, S: "hi"}) y luego f.Fuzz(func(t *testing.T, in Input) { ... }).
  • Entrar en pánico ante el desbordamiento de enteros y detectar problemas aritméticos
  • Fuzzear con LibAFL para técnicas de fuzzing de vanguardia como la resolución de restricciones de ruta
  • Generar/mutar entradas a partir de una gramática para evitar mutaciones inútiles. La mutación genera operaciones matemáticas válidas, por ejemplo X + Y - Z puede convertirse en X / U + Z - 14 en lugar de X + Yè - Z
  • Entrar en pánico en funciones seleccionadas (como registradores de errores críticos) y abortar cuando se llama
  • Enfocar el fuzzer en líneas modificadas recientemente Y en nueva cobertura para apuntar principalmente a commits nuevos
  • Detectar condiciones de carrera en tiempo de fuzzing
  • Detectar fugas de Go en tiempo de fuzzing
  • Detectar ejecuciones bloqueadas con timeouts en tiempo de fuzzing
  • Generar un informe de cobertura HTML a partir del corpus de una campaña de fuzzing con una única CLI

Tabla de contenidos

  • Compilación
  • Funcionalidad 1: Fuzzing orientado a structs (fuzz de structs como entradas)
  • Funcionalidad 2: Detección de problemas de desbordamiento y truncamiento de enteros
  • Funcionalidad 3: Pánico en funciones seleccionadas
  • Funcionalidad 4: Fuzzing de vanguardia con LibAFL
  • Funcionalidad 5: Fuzzing orientado a git blame (experimental)
  • Funcionalidad 6: Detectar condiciones de carrera, fugas de goroutines y bloqueos en tiempo de fuzzing
  • Funcionalidad 7: Fuzzing basado en gramáticas (Nautilus)
  • Funcionalidad 8: Generar informes de cobertura de fuzzing a partir de la campaña
  • Trofeos
  • Créditos

Compilación```bash

cd src && ./make.bash # Produces ../bin/go. See GOFLAGS below.

root@kitploit:~
> [!TIP]
> Documentación para contribuidores:  lee `docs/gosentry/index.md` para ver un mapa del código, el bucle de desarrollo recomendado, los puntos de entrada de CI y los scripts de benchmark.
> Este fork utiliza la Pull GitHub App para abrir y fusionar automáticamente los PR desde `golang/go:master` hacia `master`, asegurando que nunca nos quedemos atrás de las últimas actualizaciones de la cadena de herramientas de Go.

## Característica 1: Fuzzing consciente de structs (fuzzing de structs como entradas)

#### Resumen

El fuzzing nativo de Go (`go test -fuzz=...`) solo admite un pequeño conjunto de tipos escalares como parámetros de fuzzing (`[]byte`, `string`, números, ...). En gosentry, también puedes someter a fuzzing **tipos compuestos** construidos a partir de esos escalares: structs, arrays, slices y punteros.
Esto es útil cuando tu código acepta de forma natural entradas estructuradas y no quieres construir un codificador/decodificador personalizado solo para sembrar y mutar el corpus. 
Consulta `test/gosentry/examples/multiargs` y `test/gosentry/examples/composite` para ver ejemplos.

#### Ejemplo simple```go
type Input struct {
	Data []byte
	S    string
	N    int
	OK   bool
}

func FuzzStructInput(f *testing.F) {
	// Seed the initial corpus with a Go struct (gosentry feature).
	f.Add(Input{Data: []byte("A"), S: "B", N: 7, OK: true})

	f.Fuzz(func(t *testing.T, in Input) {
		if in.OK && in.N == 1337 && in.S == "BOOMMOOB" && bytes.Equal(in.Data, []byte("A")) {
			t.Fatalf("boom")
		}
	})
}
Cómo funcionan las semillas de structs (f.Add) y el fuzzing de structs (el pegamento creado)

El fuzzer nativo de Go no puede fuzzear un valor struct directamente (solo sabe mutar una pequeña lista de tipos escalares). gosentry añade una pequeña capa de pegamento: cuando tu objetivo de fuzzing usa tipos compuestos (como Input), gosentry fuzzea un único []byte entre bastidores. En cada ejecución, decodifica esos bytes en tu struct (campo por campo, recursivamente para slices/arrays/punteros) y luego llama a tu callback f.Fuzz con el valor decodificado. La misma codificación se usa para las semillas, por lo que f.Add(Input{...}) se convierte en una entrada de corpus []byte codificada que el fuzzer puede reutilizar y mutar como cualquier otra semilla.

Los fuzzers (incluido LibAFL) mutan bytes crudos, por lo que queremos un decodificador que pueda convertir cualquier slice de bytes en "algún" valor de struct y seguir adelante. JSON/gob rechazarían la mayoría de las entradas aleatorias (malo para la cobertura), y además no pueblan campos no exportados, mientras que el fuzzing a menudo se beneficia de romper invariantes. Este formato personalizado es pequeño, rápido, determinista y tolerante a datos malformados.

Bajo el capó, esto usa el propio formato binario simple de gosentry (ni gob, ni JSON). El código vive en :

Feature 2: Detección de problemas de desbordamiento de enteros y truncamiento

Descripción general

Este trabajo está inspirado en el go-panikint desarrollado anteriormente. Añade detección de desbordamiento/subdesbordamiento para operaciones aritméticas con enteros y (opcionalmente) detección de truncamiento de tipos para conversiones de enteros. Cuando se detecta un desbordamiento o truncamiento, se lanza un panic con un mensaje de error detallado, incluyendo el tipo de operación específico y los tipos de enteros implicados.

Operaciones aritméticas: Gestiona la suma +, la resta -, la multiplicación * y la división / para tipos de enteros tanto con signo como sin signo. Para enteros con signo, cubre int8, int16, int32. Para enteros sin signo, cubre uint8, uint16, uint32, uint64. El caso de la división detecta específicamente la condición de desbordamiento MIN_INT / -1 para enteros con signo. int64 y uintptr no se comprueban para operaciones aritméticas.

Detección de truncamiento de tipos: Detecta conversiones de tipos enteros potencialmente con pérdida de datos. Cubre todos los tipos enteros: int8, int16, int32, int64, uint8, uint16, uint32, uint64. Excluye uintptr debido a su uso dependiente de la plataforma. Esta opción está deshabilitada por defecto.

La detección de desbordamiento está habilitada por defecto. Para deshabilitarla, añade GOFLAGS='-gcflags=-overflowdetect=false' antes de tu ./make.bash. También puedes habilitar el comprobador de problemas de truncamiento con: -gcflags=-truncationdetect=true

Cómo funciona

Esta característica parchea la generación de SSA del compilador para que las operaciones aritméticas con enteros y las conversiones de enteros reciban comprobaciones adicionales en tiempo de ejecución que llaman al runtime para lanzar un panic con un mensaje de error detallado cuando se detecta un error. Las comprobaciones se aplican mediante un filtrado basado en la ubicación del código fuente, de modo que el código del usuario se instrumenta mientras que los archivos de la biblioteca estándar y las dependencias (caché de módulos y vendor/) se omiten.

Puedes leer la entrada de blog asociada aquí.

Supresión de falsos positivos

Añade un marcador en la misma línea de la operación o en la línea inmediatamente superior para suprimir un informe específico:

  • Desbordamiento/subdesbordamiento: overflow_false_positive
  • Truncamiento: truncation_false_positive

Ejemplo:```go // overflow_false_positive intentionalOverflow := a + b // truncation_false_positive x := uint8(big) sum2 := a + b // overflow_false_positive x2 := uint8(big) // truncation_false_positive

root@kitploit:~
A veces esto puede no funcionar, porque Go está integrando la función en línea. Si `// overflow_false_positive` no es suficiente, añade `//go:noinline` antes de la firma de tu función.

## Característica 3: Pánico en funciones seleccionadas

Al hacer fuzzing de objetivos, puede interesarnos provocar un pánico cuando se llama a ciertas funciones. Por ejemplo, algún software puede emitir mensajes `log.error` en lugar de entrar en pánico, aunque tales condiciones suelen indicar estados que los investigadores de seguridad querrían detectar durante el fuzzing.
Sin embargo, estos errores normalmente se manejan internamente (por ejemplo, mediante mecanismos de reintento o pausa, o imprimiendo mensajes en los registros), lo que los hace en gran medida invisibles para los fuzzers. El objetivo de esta característica es abordar este problema.

#### Cómo usarlo

Compila gosentry y luego usa el indicador `--panic-on`.```bash
./bin/go test -fuzz=FuzzHarness --use-libafl --focus-on-new-code=false --catch-races=false --catch-leaks=false --panic-on="test_go_panicon.(*Logger).Warning,test_go_panicon.(*Logger).Error"

El ejemplo anterior provocaría un panic cuando se llame a (*Logger).Warning o (*Logger).Error (lista separada por comas).

Cómo funciona la característica de panic en funciones seleccionadas```text ┌───────────────────────────────────────────────────────────────────────────┐ │ 1) gosentry `go test` │ │ - parses + validates `-panic-on=...` against packages being built │ │ - forwards patterns to the compiler via `-panic-on-call=...` │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 2) `cmd/compile` │ │ - prevents inlining of matching calls so the call stays visible │ │ - SSA pass inserts a call to `runtime.panicOnCall(...)` │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 3) `runtime.panicOnCall` │ │ - panics with: "panic-on-call: func-name" │ └───────────────────────────────────────────────────────────────────────────┘ ``` En la práctica, esto hace que cualquier punto de llamada coincidente se comporte como un crash/panic para los fuzzers (nota: solo los puntos de llamada estáticos pueden ser atrapados).

Característica 4: Fuzzing de vanguardia con LibAFL

LibAFL rinde mucho mejor que el fuzzer tradicional de Go. Al realizar fuzzing (go test -fuzz=...), gosentry usa LibAFL por defecto (runner en golibafl/).

Nota de estabilidad: en modo LibAFL, gosentry fuerza GODEBUG=updatemaxprocs=0 (desactiva las actualizaciones automáticas de GOMAXPROCS en tiempo de ejecución) para evitar un fallo intermitente del CI de Linux ("sync: inconsistent mutex state"). Los detalles están en misc/gosentry/USE_LIBAFL.md.

Al usar LibAFL (por defecto), debes elegir explícitamente si habilitar la planificación consciente de git: --focus-on-new-code=true|false. Más documentación en este archivo Markdown. También puedes pasar un archivo de configuración JSONC opcional para LibAFL (incluidas opciones de fuzzing de gramática), consúltalo aquí. Con "stop_all_fuzzers_on_panic": false, LibAFL guarda cada crash y reinicia su cliente para seguir fuzzeando.```bash ./bin/go test -fuzz=FuzzHarness --focus-on-new-code=false --catch-races=false --catch-leaks=false --libafl-config=path/to/libafl.jsonc # optional --libafl-config

root@kitploit:~
Usa `-fuzztime=1m` para detener una campaña de LibAFL después de un minuto.

La generación de informes de cobertura a partir del corpus de una campaña de LibAFL se documenta en [Característica 8](#feature-8-generate-go-coverage-reports-from-fuzzing-campaign).

El fuzzing basado en gramáticas (Nautilus) se documenta en [Característica 7](#feature-7-grammar-based-fuzzing-nautilus).

<details>
<summary><strong>Cómo se conectan Go y LibAFL</strong></summary>```text
┌───────────────────────────────────────────────────────────────────────────┐
│ 1) gosentry `go test`                                                      │
│    - captures  `testing.F.Fuzz(...)` callback                             │
│    - generates  extra source file: `_libaflmain.go`                       │
└───────────────┬───────────────────────────────────────────────────────────┘
                v
┌───────────────────────────────────────────────────────────────────────────┐
│ 2) Generated bridge: `_libaflmain.go`                                     │
│    - provides libFuzzer-style C ABI entrypoints:                          │
│        LLVMFuzzerInitialize                                               │
│        LLVMFuzzerTestOneInput                                             │
│    - adapts bytes -> Go types -> calls the captured fuzz callback         │
└───────────────┬───────────────────────────────────────────────────────────┘ 
                v
┌───────────────────────────────────────────────────────────────────────────┐
│ 3) `libharness.a` (static archive on disk) contains:                      │
│      - compiled objects for all test package (+ dependencies)             │
│      - generated `_testmain.go` + `_libaflmain.go`                        │
│      - LLVMFuzzerInitialize                                               │
│      - LLVMFuzzerTestOneInput                                             │
└───────────────┬───────────────────────────────────────────────────────────┘
                v
┌───────────────────────────────────────────────────────────────────────────┐
│ 4) `golibafl/` (Rust + LibAFL)                                            │
│    env: HARNESS_LIB=/path/to/libharness.a                                 │
│    fuzz loop: mutate input -> LLVMFuzzerTestOneInput(data) -> observe     │
└───────────────────────────────────────────────────────────────────────────┘

En el modo --use-libafl, gosentry construye libharness.a y el ejecutor Rust golibafl lo controla en el mismo proceso mediante los puntos de entrada de libFuzzer. Nota: HARNESS_LIB puede apuntar a cualquier nombre de archivo de harness (por ejemplo libharness_race.a usado por --catch-races).

Cómo funciona la instrumentación de cobertura (LibAFL + objetivo Go)

En el modo --use-libafl, gosentry compila el harness Go con la instrumentación de cobertura habilitada. Esto añade pequeños contadores al código que cambian cuando se ejecutan diferentes partes del programa. Cuando el harness se inicia dentro de golibafl, el runtime de Go expone estos contadores a LibAFL. LibAFL los lee después de cada entrada para ver qué código se ejecutó y utiliza esa cobertura para guiar las siguientes mutaciones.

Limitaciones

Hablemos de la motivación detrás del uso de LibAFL. Fuzzing con go test -fuzz está muy por detrás de las técnicas de fuzzing de última generación. Un buen ejemplo de esto es CMPLOG/Redqueen de AFL++. Estas funcionalidades permiten a los fuzzers resolver ciertas restricciones. Supongamos el siguiente fragmento```go if input == "IMARANDOMSTRINGJUSTCMPLOGMEMAN" { panic("this string is illegal") }

root@kitploit:~
Los fuzzers de última generación como AFL++ o LibAFL encontrarían el panic al instante en ese caso. Sin embargo, el fuzzer nativo de Go no lo haría. Esa es una brecha enorme que restringe la exploración de cobertura en **gran medida**.

El benchmark a continuación muestra esos límites. Ten en cuenta que esos benchmarks pueden **reproducirse** y mejorarse mediante el [repositorio gosentry-bench-libafl](https://github.com/kevin-valerio/gosentry-bench-libafl/tree/main).

##### Benchmark 1:

El gráfico a continuación muestra la evolución del número de líneas cubiertas al fuzzear el [UUID](https://github.com/google/uuid) de Google usando LibAFL frente al fuzzer nativo de Go.
![BENCH1](https://assets.kitploit.com/production/public/readmes/41918/7b1ea005996846c9ee4d7c0d42fee26731050d10b6b21d59c551e4b1b956923e.png "BENCH1")

##### Benchmark 2:

El gráfico a continuación muestra la evolución del número de líneas cubiertas al fuzzear [go-ethereum](https://github.com/ethereum/go-ethereum) usando LibAFL frente al fuzzer nativo de Go.
![BENCH2](https://assets.kitploit.com/production/public/readmes/41918/61aab8540ab8d339307854de6737a405056c3c8ad83a822df40f74e362b93021.png "BENCH2")



#### Ejemplo
Puedes probarlo en algunos harnesses de fuzzing en `test/gosentry/examples/`.```bash
cd test/gosentry/examples/reverse
../../../../bin/go test -fuzz=FuzzReverse --focus-on-new-code=false --catch-races=false --catch-leaks=false

Detén la campaña de fuzzing con Ctrl+C.

Directorio de salida de LibAFL (identidad de campaña / reutilización de corpus)

gosentry almacena el estado de la campaña de LibAFL (corpus, crashes, etc.) bajo la raíz de caché de fuzzing de Go (aproximadamente $(go env GOCACHE)/fuzz), en un directorio determinista derivado del mismo paquete + mismo objetivo de fuzzing (y la misma raíz del proyecto).

Esto significa que detener (Ctrl+C) y reiniciar la misma campaña de fuzzing continuará, por defecto, desde el corpus queue/ anterior de LibAFL.

La ruta se imprime al final de la ejecución:```text libafl output dir: /full/path/to/.../fuzz//libafl//

root@kitploit:~
Notes:
- `<harness>` es el nombre del objetivo de fuzzing cuando `-fuzz` es un identificador simple como `FuzzXxx` (o `^FuzzXxx$`); de lo contrario, es `pattern-<hash>`.
- La generación de cobertura (`--generate-coverage`) usa la misma regla para encontrar el corpus `queue/` correcto, por lo que debe ejecutarse desde el mismo paquete con el mismo `-fuzz=...`.

## Característica 5: Fuzzing orientado a git-blame (experimental)

#### Resumen

El fuzzing guiado por cobertura es excelente para explorar nuevas rutas, pero trata todo el código cubierto como igualmente interesante. Al hacer fuzzing en bases de código grandes, es posible que desees sesgar el fuzzer hacia el código modificado recientemente, donde es más probable que se introduzcan regresiones y errores. En el modo LibAFL, gosentry puede usar `git blame` para preferir entradas que ejecuten líneas modificadas recientemente (manteniendo la guía de cobertura como señal principal).

Este trabajo se basa en trabajos previos de [LibAFL-git-aware](https://github.com/kevin-valerio/LibAFL-git-aware). Todos los detalles técnicos en profundidad están documentados allí.

#### Cómo usarlo

Habilita la planificación consciente de git con `--focus-on-new-code=true`:```bash
./bin/go test -fuzz=FuzzHarness --use-libafl --focus-on-new-code=true --catch-races=false --catch-leaks=false

Este modo necesita git (para ejecutar git blame) y go tool addr2line para mapear los contadores de cobertura de vuelta al file:line de origen.

Cómo funciona el fuzzing orientado a git-blame```text ┌───────────────────────────────────────────────────────────────────────────┐ │ 1) gosentry `go test -fuzz` │ │ - builds `libharness.a` (contains `go.o` + `.go.fuzzcntrs`) │ │ - runs `golibafl` with `GOLIBAFL_FOCUS_ON_NEW_CODE=1` │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 2) `golibafl` generates a cached "git recency map" │ │ - maps coverage counters -> (file:line) via `go tool addr2line` │ │ - runs `git blame` to get a timestamp per line │ │ - stores timestamps in `git_recency_map.bin` │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 3) LibAFL scheduler uses the recency map │ │ - coverage decides what enters the corpus │ │ - among the corpus, prioritize inputs that hit newer lines │ └───────────────────────────────────────────────────────────────────────────┘ ```
Cómo gosentry construye git_recency_map.bin

.go.fuzzcntrs es la sección del enlazador que contiene los contadores de cobertura de 8 bits estilo libFuzzer de Go (habilitados mediante -gcflags=all=-d=libfuzzer); cada byte indica "cuántas veces se ha alcanzado este punto instrumentado". Cuando --focus-on-new-code=true, golibafl genera git_recency_map.bin de la siguiente manera:

  1. Extrayendo go.o de libharness.a.
  2. Leyendo el tamaño de la sección .go.fuzzcntrs para obtener el número de contadores N.
  3. Escaneando las reubicaciones de .text que hacen referencia a los símbolos de .go.fuzzcntrs para recuperar la dirección de cada índice de contador.
Benchmark 1 (go-ethereum / geth): línea base vs. git-aware

Ejecutado con misc/gosentry/bench_focus_on_new_code_geth.sh --trials 5 --warmup 600 --timeout 200.```text gitaware_5: crash (7122ms) baseline results: trial 1: crash (107747ms) trial 2: crash (146415ms) trial 3: crash (37902ms) trial 4: crash (154034ms) trial 5: timeout (200000ms) baseline crashes: 4/5 (timeouts=1, errors=0) baseline median (capped to timeout): 146.415s

git-aware results: trial 1: timeout (200000ms) trial 2: crash (87432ms) trial 3: crash (61733ms) trial 4: crash (157540ms) trial 5: crash (7122ms) git-aware crashes: 4/5 (timeouts=1, errors=0) git-aware median (capped to timeout): 87.432s

root@kitploit:~
</details>

## Característica 6: Detectar condiciones de carrera, fugas de goroutines y cuelgues (timeouts) en tiempo de fuzzing

##### Detección de cuelgues confirmados (timeouts de LibAFL)

Al hacer fuzzing con LibAFL, una ejecución del harness puede **agotar el tiempo** (por ejemplo, debido a un deadlock / goroutines bloqueadas esperando, o una ruta extremadamente lenta).

Para reducir falsos positivos, gosentry trata un timeout como un candidato a cuelgue y lo confirma reproduciendo la entrada que agotó el tiempo unas cuantas veces con un timeout mayor. Ante un cuelgue confirmado, gosentry escribe la entrada en `<libafl output dir>/hangs/` y detiene la campaña de fuzzing (lo trata como un bug/crash).

Antes de salir, `golibafl` intenta minimizar la entrada que causa el crash/cuelgue (best-effort; los cuelgues están limitados a ~60s en total).

Nota: la confirmación de cuelgues también se ejecuta durante la importación/generación inicial del corpus, por lo que los objetivos que agotan el tiempo en cada entrada también pueden detectarse de forma determinista.

Esto se configura mediante `--libafl-config`:
- `catch_hangs` (por defecto: `true`)
- `hang_timeout_ms` (por defecto: `10000`)
- `hang_confirm_runs` (por defecto: `3`)

##### Detección de condiciones de carrera (`--catch-races`)

gosentry puede ejecutar un bucle de reproducción `-race` separado que vigila el directorio `queue/` de LibAFL y reproduce las semillas recién descubiertas con `GORACE=halt_on_error=1`.

El bucle de reproducción compila un archivo de harness `-race` separado solo para reproducción (sin instrumentación de cobertura de fuzzing).

Cuando se detecta una condición de carrera durante la reproducción, gosentry imprime el informe completo del detector de carreras antes del resumen `catch-races:` y el comando de reproducción.

Nota: el detector de carreras de Go solo detecta condiciones de carrera **dentro de una única ejecución del harness** (carreras entre goroutines en el mismo proceso que acceden a la misma memoria sin la sincronización adecuada). `--catch-races` omitirá carreras si la semilla no desencadena la concurrencia conflictiva, y no detecta carreras entre procesos.

<details>
<summary><strong>Cómo funciona el modo de detección de condiciones de carrera</strong></summary>

Este modo inicia un pequeño monitor dentro de `go test` (el mismo proceso padre) y se ejecuta durante toda la campaña de fuzzing.

- Cuándo: antes de que se inicie el proceso principal de fuzzing de LibAFL, gosentry compila el harness de reproducción y el runner.
- Supervisión: antes de que comience el fuzzing, gosentry captura el contenido inicial de `<libafl output dir>/queue/` en un conjunto `seen`. Luego, una goroutine consulta `<libafl output dir>/queue/` cada ~1s y solo reproduce las semillas recién creadas (omite los dotfiles y `*.metadata`).```text
Legend: output/... = <libafl output dir>/...

┌───────────────────────────────────────────────────────────────────────────┐
│ 1) Main LibAFL fuzzing run                                                 │
│    - `golibafl` writes new seeds to `output/queue/`                        │
└───────────────┬───────────────────────────────────────────────────────────┘
                v
┌───────────────────────────────────────────────────────────────────────────┐
│ 2) `--catch-races` sidecar setup                                           │
│    - builds replay harness: `libharness_race.a` (`go test -race ...`)      │
│    - builds replay runner: `golibafl-race` (linked against race harness)   │
└───────────────┬───────────────────────────────────────────────────────────┘
                v
┌───────────────────────────────────────────────────────────────────────────┐
│ 3) Replay loop                                                             │
│    - polls `output/queue/` for new seeds                                   │
│    - runs: `GORACE=halt_on_error=1 golibafl-race run --input <seed>`       │
│      (2 workers × 3 repeats per seed)                                      │
└───────────────┬───────────────────────────────────────────────────────────┘
                v
┌───────────────────────────────────────────────────────────────────────────┐
│ 4) On "DATA RACE"                                                          │
│    - prints the race detector report                                       │
│    - copies seed to `output/races/`                                        │
│    - stops the fuzz campaign (treat as bug/crash)                          │
└───────────────────────────────────────────────────────────────────────────┘
Detección de fugas de goroutines (--catch-leaks)

gosentry también puede ejecutar un bucle de reproducción de goleak que vigila el directorio queue/ de LibAFL y reproduce las semillas recién descubiertas con go.uber.org/goleak habilitado.

Cuando se detecta una fuga de goroutine, gosentry imprime la ruta exacta de la semilla y la copia en <libafl output dir>/leaks/.

Nota: goleak es para fugas de goroutines, no fugas de memoria.

Cómo funciona el modo de fugas de goroutines

Este modo también inicia un pequeño monitor dentro de go test (el mismo proceso padre) y se ejecuta durante toda la campaña de fuzzing.

  • Monitoreo: una goroutine consulta <libafl output dir>/queue/ cada ~1s y reproduce cada nueva semilla con GOSENTRY_LIBAFL_CATCH_LEAKS=1 (habilita go.uber.org/goleak después de cada ejecución).```text Legend: output/... = /...

┌───────────────────────────────────────────────────────────────────────────┐ │ 1) Main LibAFL fuzzing run │ │ - golibafl writes new seeds to output/queue/ │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 2) --catch-leaks sidecar setup │ │ - builds replay runner: golibafl-leak (linked against the harness) │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 3) Replay loop │ │ - polls output/queue/ for new seeds │ │ - runs: │ │ (enables checks after each execution) │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 4) On "catch-leaks: detected goroutine leak" │ │ - copies seed to │ │ - stops the fuzz campaign (treat as bug/crash) │ └───────────────────────────────────────────────────────────────────────────┘

Cómo funciona el fuzzing gramatical en gosentry```text Legend: output/... = /...

┌───────────────────────────────────────────────────────────────────────────┐ │ 0) gosentry go test -fuzz=FuzzXxx (LibAFL + --use-grammar) │ │ - captures your testing.F.Fuzz callback + its parameter types │ │ - builds libharness.a (libFuzzer-style entrypoints for LibAFL) │ │ - runs golibafl fuzz ... --use-grammar --grammar ... │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 1) golibafl (Rust + LibAFL) fuzzes the Go harness in-process │ │ - loads libharness.a via HARNESS_LIB=... │ │ - observers: edges + time (+ cmplog for comparisons) │ │ - feedback/objective: coverage/time/crash (and optional hang handling) │ │ - scheduler selects a corpus seed (coverage-guided) │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 2) Nautilus (in-process, per client) │ │ - loads the JSON grammar into a Nautilus context │ │ - fuzz loop stage: parse seed -> mutate tree -> unparse to bytes │ │ - if the seed is not parseable: fall back to generation-from-scratch │ └───────────────┬───────────────────────────────────────────────────────────┘ v ┌───────────────────────────────────────────────────────────────────────────┐ │ 3) Grammar mode stages │ │ - initial corpus: if input dir empty, call N times │ │ - fuzz loop: corpus seed -> grammar mutate -> exec harness │ │ - new coverage inputs are added to the on-disk corpus () │ └───────────────────────────────────────────────────────────────────────────┘

Descargar herramienta
src/testing/libafl.go
  • Codificar: libaflMarshalInputs / libaflAppendValue
  • Decodificar: libaflUnmarshalArgs / libaflDecodeValue

Reglas de codificación (alto nivel):

  • bool: 1 byte (0 o 1)
  • Enteros: bytes little-endian (int/uint son 8 bytes)
  • Flotantes: bits IEEE-754 en little-endian (float32 = 4 bytes, float64 = 8 bytes)
  • string: uvarint(len) y luego bytes crudos de la cadena
  • []byte: uvarint(len) y luego bytes crudos
  • Otros slices: uvarint(len) y luego cada elemento codificado
  • Structs: campos codificados en orden de declaración
  • Punteros: 1 byte (0 = nil, 1 = presente) y luego el valor apuntado
  • Resolviendo cada dirección a file:line usando go tool addr2line.
  • Ejecutando git blame --line-porcelain para obtener committer-time por línea.
  • Escribiendo git_recency_map.bin como u64 head_time + u64 N + N * u64 timestamps (little-endian). Las entradas no mapeadas usan la marca de tiempo 0.
  • GOSENTRY_LIBAFL_CATCH_LEAKS=1 golibafl-leak run --input <seed>
    go.uber.org/goleak
    output/leaks/
    root@kitploit:~
    </details>
    
    
    #### Cómo usar
    
    Habilita la detección de fugas de goroutines con `--catch-leaks=true` o la detección de carreras de datos con `--catch-races=true````bash
    ./bin/go test -fuzz=FuzzHarness --use-libafl --focus-on-new-code=false --catch-races=true --catch-leaks=true
    

    Feature 7: Fuzzing basado en gramáticas (Nautilus)

    Descripción general

    El fuzzing a nivel de bytes es excelente, pero los parsers y los formatos de archivo a menudo necesitan entradas estructuradas. Con --use-grammar, gosentry usa el mutador de gramáticas Nautilus de LibAFL para generar y mutar entradas que se ajustan a una gramática proporcionada por el usuario (formato JSON), y las alimenta a tu harness de fuzzing habitual de Go (testing.F.Fuzz).

    En el modo de gramática, LibAFL sigue ejecutando el bucle normal guiado por cobertura (elegir una semilla del corpus → mutar → ejecutar → conservar las entradas que aumentan la cobertura). El runner añade la mutación Nautilus (semilla → árbol de gramática → mutar → deserializar) además de (por defecto) una etapa guiada por CMPLOG, similar a I2S, que reescribe los terminales hoja de Nautilus basándose en comparaciones en tiempo de ejecución. Esto mantiene las entradas válidas según la gramática (no ejecuta las etapas brutas de havoc/token a nivel de bytes en el modo de gramática). Puedes desactivar la etapa CMPLOG/I2S en --libafl-config mediante nautilus_cmplog_i2s=false (el fuzzing a nivel de bytes mantiene CMPLOG/I2S siempre activado).

    [!NOTE] El modo de gramática suele ser más lento que el fuzzing a nivel de bytes. Es un equilibrio: más estructura frente a menos ejecuciones por segundo.

    Para obtener los mejores resultados, usa un callback de fuzzing con un solo argumento que acepte una porción de bytes ([]byte) o un string:```go f.Fuzz(func(t testing.T, data []byte) { / parse data */ }) // or: f.Fuzz(func(t testing.T, s string) { / parse s */ })

    root@kitploit:~
    El modo gramática funciona mejor con un único argumento de entrada (`[]byte` o `string`). Los callbacks de fuzzing con múltiples argumentos hacen que gosentry decodifique el búfer de bytes subyacente en valores separados, por lo que el texto generado por la gramática original no permanecerá intacto.
    
    > [!NOTE]
    > El modo gramática sigue generando **bytes/strings**. Si necesitas entradas estructuradas (o estás haciendo fuzzing diferencial), es en tu harness donde conviertes `data` en valores de dominio (parsear/unmarshal). (Fuera del modo gramática, gosentry también puede fuzzear tipos compuestos de Go > decodificándolos desde bytes; consulta [Feature 1](#feature-1-struct-aware-fuzzing-fuzz-structs-as-inputs).)
    
    Puedes ajustar Nautilus mediante `--libafl-config` (solo se usa con `--use-grammar`): `nautilus_max_len` y `nautilus_cmplog_i2s` (consulta `misc/gosentry/libafl.config.jsonc`).
    
    <details>
    <summary><strong>Benchmark: etapa CMPLOG/I2S de gramática Nautilus (activada vs desactivada)</strong></summary>
    
    Ejecutado el 17 de febrero de 2026 con el ejemplo de gramática JSON del repositorio (`test/gosentry/examples/grammar_json`, `FuzzGrammarJSON`, gramática `testdata/JSON.json`).
    
    Resultados (LibAFL `UserStats`):
    
    | modo | `nautilus_cmplog_i2s` | tiempo de ejecución | ejecuciones | ejec/seg | edges |
    |---|---:|---:|---:|---:|---:|
    | activado | `true` | 1m-5s | 103818 | 1.586k | 388/8008 (4%) |
    | desactivado | `false` | 1m-0s | 256659 | 4.251k | 388/8008 (4%) |
    
    Nota: `edges` son los bordes del mapa de cobertura de LibAFL, no líneas de código fuente de Go.
    
    </details>
    
    Establece `GOSENTRY_VERBOSE_AFL=1` para imprimir algunas entradas generadas. Establece `GOSENTRY_VERBOSE_AFL_ALL_INPUTS=1` para imprimir **cada** ejecución del modo gramática como `GOLIBAFL_MUTATED_INPUT "..."` (muy ruidoso).
    
    #### Utilidades para la creación de gramáticas
    
    Si necesitas crear una nueva gramática JSON de Nautilus para tu propio formato/protocolo, gosentry incluye:
    
    - Un prompt listo para LLM: [misc/gosentry/nautilus/prompt.md](https://github.com/trailofbits/gosentry/blob/HEAD/misc/gosentry/nautilus/prompt.md)
    - Un pequeño conjunto de gramáticas de ejemplo: [misc/gosentry/nautilus/examples/](https://github.com/trailofbits/gosentry/blob/HEAD/misc/gosentry/nautilus/examples/)
    
    <details>
    <summary><strong>Ejemplo de harness de fuzzing en Go (JSON)</strong></summary>```go
    func FuzzGrammarJSON(f *testing.F) {
    	f.Fuzz(func(t *testing.T, data []byte) {
    		dec := json.NewDecoder(bytes.NewReader(data))
    		dec.UseNumber()
    
    		var v any
    		if err := dec.Decode(&v); err != nil {
    			t.Fatalf("invalid JSON: %v", err)
    		}
    		if err := dec.Decode(&struct{}{}); err != io.EOF {
    			t.Fatalf("invalid JSON: trailing data")
    		}
    	})
    }
    

    Esquema de arnés de fuzzing diferencial (dos analizadores):```go f.Fuzz(func(t *testing.T, data []byte) { gotA, errA := ParseA(data) gotB, errB := ParseB(data) if (errA == nil) != (errB == nil) { t.Fatalf("parser disagreement: A=%v B=%v", errA, errB) } _ = gotA _ = gotB })

    root@kitploit:~
    </details>
    
    <details>
    <summary><strong>Ejemplo: fuzzing de gramática de un "lenguaje de entrada real" (sin codificador personalizado)</strong></summary>
    
    Este ejemplo realiza fuzzing de un pequeño evaluador de expresiones aritméticas generando **expresiones válidas** a partir de una gramática. No hay codificación ad-hoc de “struct a bytes”: el fuzzer produce el mismo tipo de entrada que tu código normalmente analizaría.
    
    Harness (la entrada `string` de 1 argumento funciona mejor en modo de gramática):```go
    func FuzzExprEval(f *testing.F) {
    	f.Add("1+2")
    	f.Add("(3*4)-5")
    
    	f.Fuzz(func(t *testing.T, expr string) {
    		// Parse+eval your language/protocol.
        // You can be **sure** that `expr` will always be a valid math operation. Just decode/parse/unmarshall it afterwards. 
    		_, _ = Eval(expr)
    	})
    }
    

    Esbozo de gramática (formato JSON de Nautilus):```json [ ["Expr", "{Term}"], ["Expr", "{Term}+{Expr}"], ["Expr", "{Term}-{Expr}"], ["Term", "{Factor}"], ["Term", "{Factor}*{Term}"], ["Factor", "{Num}"], ["Factor", "({Expr})"], ["Num", "0"], ["Num", "1"], ["Num", "2"], ["Num", "3"] ]

    root@kitploit:~
    </details>
    
    <details>
    <summary><strong>Ejemplo de gramática JSON de Nautilus (pequeño subconjunto JSON)</strong></summary>
    
    Este es el formato de archivo que espera `--grammar=...`:
    - La gramática es un array JSON de reglas: `["NonTerm", "RHS"]`.
    - Los nombres de los no terminales deben comenzar con una letra mayúscula (`Value`, `Object`, ...).
    - Usa `{NonTerm}` en el RHS para hacer referencia a otra regla.
    - `{` y `}` están reservados para referencias a no terminales; para emitir llaves literales, usa `\\{` y `\\}` en la cadena RHS.```json
    [
      ["Json", "{Value}"],
      ["Value", "null"],
      ["Value", "{String}"],
      ["String", "\"{Chars}\""],
      ["Chars", ""],
      ["Chars", "{Char}{Chars}"],
      ["Char", "a"],
      ["Char", "b"]
    ]
    
    generate
    output/queue/
    root@kitploit:~
    </details>
    
    Limitaciones (código glue actual):
    - El modo de gramática funciona mejor con un único argumento de entrada; los objetivos de fuzzing con múltiples argumentos decodificarán el búfer de bytes subyacente en valores separados.
    - Aún no hay recombinación/cruce de gramática entre dos semillas de corpus (la mutación es de semilla única).
    
    ## Característica 8: Generar informes de cobertura Go a partir de una campaña de fuzzing
    
    Después (o mientras) de ejecutar una campaña de fuzzing de LibAFL, gosentry puede generar un informe de cobertura de Go reproduciendo el **corpus de cola** actual de LibAFL (sin fuzzing).```bash
    # Same package + same fuzz target as your fuzz campaign:
    ./bin/go test -fuzz=FuzzHarness --generate-coverage .
    

    Esto reproduce entradas desde <libafl output dir>/queue/ y escribe cover.out y cover.html.

    Trofeos

    Esos bugs se encontraron mediante una campaña de fuzzing diferencial usando la funcionalidad de fuzzing por gramática de gosentry.

    Optimism

    • Kona y op-node pueden discrepar sobre los canales brotli
    • El tipo de lote desconocido provoca pánico y causa denegación de servicio en kona-protocol
    • Desajuste en el análisis de tramas de Kona frente a op-node y las especificaciones de OP Stack

    REVM

    • Un depósito fallido en op-revm que se detiene con OutOfFunds no incrementa el nonce, lo que lleva a una discrepancia en la raíz del estado frente a otros clientes

    Créditos

    • golibafl
    • Nautilus
    • LibAFL
    • goleak
    • go
    • go-panikint