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
failpoint — Biblioteca de inyección de fallos para Go que añade puntos de fallo controlables en tiempo de ejecución para provocar pánicos, suspensiones, retornos y rutas de error condicionales mediante variables de entorno. | Kitploit
Herramientas/GitHubGitHub/pingcap/failpoint
Utilidades de Propósito GeneralDepuradoresIngeniería del Caos
GitHubpingcap/failpoint

failpoint

Biblioteca de inyección de fallos para Go que añade puntos de fallo controlables en tiempo de ejecución para provocar pánicos, suspensiones, retornos y rutas de error condicionales mediante variables de entorno.

Ver Repositorio
89467hace 12 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

failpoint

LICENSE Language Go Report Card Build Status Coverage Status Mentioned in Awesome Go

Una implementación de failpoints para Golang. Los failpoints se utilizan para añadir puntos de código donde se pueden inyectar errores de forma controlada por el usuario. Un failpoint es un fragmento de código que solo se ejecuta cuando el failpoint correspondiente está activo.

Inicio rápido (usando failpoint-ctl)

  1. Compila failpoint-ctl desde el código fuente

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-ctl
    
  2. Inyecta failpoints en tu programa, por ejemplo:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Transforma tu código con failpoint-ctl enable

  4. Compila con go build

  5. Activa los failpoints con la variable de entorno GO_FAILPOINTS

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    

    Nota: GO_FAILPOINTS no funciona con el tipo de marcador InjectCall.

  6. Si usas go run para ejecutar el test, no olvides añadir el archivo generado a tu comando, así:

Inicio rápido (usando failpoint-toolexec)

  1. Compila failpoint-toolexec desde el código fuente

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-toolexec
    
  2. Inyecta failpoints en tu programa, por ejemplo:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Usa un caché de compilación separado para evitar mezclar cachés sin failpoint-toolexec, y compila

    GOCACHE=/tmp/failpoint-cache go build -toolexec path/to/failpoint-toolexec

  4. Activa los failpoints con la variable de entorno GO_FAILPOINTS

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    
  5. También puedes usar go run o go test, por ejemplo:

    root@kitploit:~
    GOCACHE=/tmp/failpoint-cache GO_FAILPOINTS="main/testPanic=return(true)" go run -toolexec path/to/failpoint-toolexec your-program.go
    

Principios de diseño

  • Define el failpoint en código Golang válido, no en comentarios ni en nada más

  • El failpoint no tiene ningún costo adicional

    • No afecta a la lógica normal
    • No causa regresiones de rendimiento en el código normal
    • El código del failpoint no aparecerá en el binario final
  • La rutina del failpoint es escribible/legible y debe ser verificada por un compilador

  • El código generado por la definición del failpoint es fácil de leer

  • Mantiene los mismos números de línea que los códigos de inyección (más fácil de depurar)

  • Soporta pruebas paralelas con context.Context

Conceptos clave

  • Failpoint

    Un failpoint es un fragmento de código que solo se ejecuta cuando el failpoint correspondiente está activo. El cierre nunca se ejecutará si se ejecuta failpoint.Disable("failpoint-name-for-demo").

    root@kitploit:~
    var outerVar = "declare in outer scope"
    failpoint.Inject("failpoint-name-for-demo", func(val failpoint.Value) {
        fmt.Println("unit-test", val, outerVar)
    })
    
  • Funciones marcadoras

    • Son solo funciones vacías

      • Para indicar al reescritor que reescriba con una sentencia de igualdad
      • Para recibir algunos parámetros como regla de reescritura
      • Se alinearán en tiempo de compilación y no emitirán nada al binario (costo cero)
      • Las variables del ámbito externo pueden ser accedidas en el cierre mediante captura, y el código convertido sigue siendo legal porque todas las variables capturadas están ubicadas en el ámbito externo de la sentencia IF.
    • Son fáciles de escribir/leer

    • Introducen una verificación del compilador para los failpoints que no se pueden compilar en el modo normal si el código del failpoint es inválido

  • Lista de funciones marcadoras

    • func Inject(fpname string, fpblock func(val Value)) {}
    • func InjectContext(fpname string, ctx context.Context, fpblock func(val Value)) {}
    • func InjectCall(fpname string, args ...any) {}
    • func Break(label ...string) {}

Cómo inyectar un failpoint en tu programa

  • Puedes llamar a failpoint.Inject para inyectar un failpoint en el punto de llamada, donde failpoint-name se usa para disparar el failpoint y failpoint-closure se expandirá como el cuerpo de la sentencia IF.

    root@kitploit:~
    failpoint.Inject("failpoint-name", func(val failpoint.Value) {
        failpoint.Return("unit-test", val)
    })
    

    El código convertido se ve así:

    root@kitploit:~
    if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        return "unit-test", val
    }
    
  • failpoint.Value es el valor que se pasa mediante failpoint.Enable("failpoint-name", "return(5)"), el cual se puede ignorar.

    root@kitploit:~
    failpoint.Inject("failpoint-name", func(_ failpoint.Value) {
        fmt.Println("unit-test")
    })
    

    O

    root@kitploit:~
    failpoint.Inject("failpoint-name", func() {
        fmt.Println("unit-test")
    })
    

    Y el código convertido se ve así:

Algunos ejemplos de failpoints complicados

  • Inyectar un failpoint en la sentencia IF INITIAL o en la expresión CONDICIONAL

    root@kitploit:~
    if a, b := func() {
        failpoint.Inject("failpoint-name", func(val failpoint.Value) {
            fmt.Println("unit-test", val)
        })
    }, func() int { return rand.Intn(200) }(); b > func() int {
        failpoint.Inject("failpoint-name", func(val failpoint.Value) int {
            return val.(int)
        })
        return rand.Intn(3000)
    }() && b < func() int {
        failpoint.Inject("failpoint-name-2", func(val failpoint.Value) {
            return rand.Intn(val.(int))
        })
        return rand.Intn(6000)
    }() {
        a()
        failpoint.Inject("failpoint-name-3", func(val failpoint.Value) {
            fmt.Println("unit-test", val)
        })
    }
    

    El bloque de código anterior generará algo como esto:

    root@kitploit:~
    if a, b := func() {
        if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
            fmt.Println("unit-test", val)
        }
    }, func() int { return rand.Intn(200) }(); b > func() int {
        if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
            return val.(int)
        }
        return rand.Intn(3000)
    }() && b < func() int {
        if val, ok := failpoint.Eval(_curpkg_("failpoint-name-2")); ok {
            return rand.Intn(val.(int))
        }
        return rand.Intn(6000)
    }() {
        a()
        if val, ok := failpoint.Eval(_curpkg_("failpoint-name-3")); ok {
            fmt.Println("unit-test", val)
        }
    }
    
  • Inyectar un failpoint en la sentencia SELECT para bloquear un CASE si el failpoint está activo

Buenas prácticas para el nombre del failpoint

Como viste arriba, _curpkg_ envolverá automáticamente el nombre original del failpoint en la llamada a failpoint.Eval. Puedes pensar en _curpkg_ como una macro que automáticamente antepone la ruta del paquete actual al nombre del failpoint. Por ejemplo:

root@kitploit:~
package ddl // which parent package is `github.com/pingcap/tidb`

func demo() {
	// _curpkg_("the-original-failpoint-name") will be expanded as `github.com/pingcap/tidb/ddl/the-original-failpoint-name`
	if val, ok := failpoint.Eval(_curpkg_("the-original-failpoint-name")); ok {...}
}

No necesitas preocuparte por _curpkg_ en tu aplicación. Se genera automáticamente después de ejecutar failpoint-ctl enable y se elimina con failpoint-ctl disable.

Debido a que todos los failpoints en un paquete comparten el mismo espacio de nombres, debemos tener cuidado de evitar conflictos de nombres. Hay algunas reglas de nomenclatura recomendadas para mejorar esta situación.

  • Mantén el nombre único dentro del subpaquete actual

  • Usa un nombre autoexplicativo para el failpoint

    Puedes habilitar failpoints mediante variables de entorno

    root@kitploit:~
    GO_FAILPOINTS="github.com/pingcap/tidb/ddl/renameTableErr=return(100);github.com/pingcap/tidb/planner/core/illegalPushDown=return(true);github.com/pingcap/pd/server/schedulers/balanceLeaderFailed=return(true)"
    

Detalles de implementación

  1. Define un grupo de funciones marcadoras
  2. Analiza los imports y descarta un archivo fuente que no importe un failpoint
  3. Recorre el AST para encontrar llamadas a funciones marcadoras
  4. Las llamadas a funciones marcadoras se reescribirán con una sentencia IF, que llama a failpoint.Eval para determinar si un failpoint está activo y ejecuta el código del failpoint si está habilitado

rewrite-demo

Agradecimientos

  • Gracias a gofail por proporcionar la implementación inicial.
Descargar herramienta
binding__failpoint_binding__.go
root@kitploit:~
GO_FAILPOINTS="main/testPanic=return(true)" go run your-program.go binding__failpoint_binding__.go
  • func Goto(label string) {}
  • func Continue(label ...string) {}
  • func Fallthrough() {}
  • func Return(results ...interface{}) {}
  • func Label(label string) {}
  • Variables de entorno de failpoint soportadas

    Los failpoints se pueden habilitar exportando variables de entorno con el siguiente patrón, que es bastante similar a las VARIABLES SYSCTL de failpoints en FreeBSD

    Nota: InjectCall no se puede habilitar mediante variables de entorno.

    root@kitploit:~
    [<percent>%][<count>*]<type>[(args...)][-><more terms>]
    

    El argumento especifica qué acción tomar; puede ser uno de:

    • off: No realizar ninguna acción (no dispara el código del failpoint)
    • return: Disparar el failpoint con el argumento especificado
    • sleep: Dormir el número de milisegundos especificado
    • panic: Pánico
    • break: Ejecutar gdb y entrar en el depurador
    • print: Imprimir la ruta del failpoint para inyectar la variable
    • pause: Pause pausará hasta que el failpoint se desactive
  • root@kitploit:~
    if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test")
    }
    
  • Además, el cierre del failpoint puede ser una función que reciba context.Context. Puedes hacer cosas personalizadas con context.Context, como controlar si un failpoint está activo en pruebas paralelas u otros casos. Por ejemplo:

    root@kitploit:~
    failpoint.InjectContext(ctx, "failpoint-name", func(val failpoint.Value) {
        fmt.Println("unit-test", val)
    })
    

    El código convertido se ve así:

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Puedes ignorar context.Context, y esto generará el mismo código que la versión sin contexto anterior. Por ejemplo:

    root@kitploit:~
    failpoint.InjectContext(nil, "failpoint-name", func(val failpoint.Value) {
        fmt.Println("unit-test", val)
    })
    

    Se convierte en

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(nil, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Puedes usar failpoint.InjectCall para inyectar una llamada a función. Este tipo de marcador solo se puede habilitar usando failpoint.EnableCall y debe llamarse en el mismo proceso que el punto de llamada de InjectCall. Con este marcador, puedes evitar que el código del failpoint contamine tu código fuente. Ver ejemplos.

  • Puedes controlar un failpoint con failpoint.WithHook

    root@kitploit:~
    func (s *dmlSuite) TestCRUDParallel() {
        sctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
            return ctx.Value(fpname) != nil // Determine by ctx key
        })
        insertFailpoints = map[string]struct{} {
            "insert-record-fp": {},
            "insert-index-fp": {},
            "on-duplicate-fp": {},
        }
        ictx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
            _, found := insertFailpoints[fpname] // Only enables some failpoints.
            return found
        })
        deleteFailpoints = map[string]struct{} {
            "tikv-is-busy-fp": {},
            "fetch-tso-timeout": {},
        }
        dctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
            _, found := deleteFailpoints[fpname] // Only disables failpoints. 
            return !found
        })
        // other DML parallel test cases.
        s.RunParallel(buildSelectTests(sctx))
        s.RunParallel(buildInsertTests(ictx))
        s.RunParallel(buildDeleteTests(dctx))
    }
    
  • Si usas un failpoint en el contexto de un bucle, quizás necesites usar otras funciones marcadoras.

    root@kitploit:~
    failpoint.Label("outer")
    for i := 0; i < 100; i++ {
        inner:
            for j := 0; j < 1000; j++ {
                switch rand.Intn(j) + i {
                case j / 5:
                    failpoint.Break()
                case j / 7:
                    failpoint.Continue("outer")
                case j / 9:
                    failpoint.Fallthrough()
                case j / 10:
                    failpoint.Goto("outer")
                default:
                    failpoint.Inject("failpoint-name", func(val failpoint.Value) {
                        fmt.Println("unit-test", val.(int))
                        if val == j/11 {
                            failpoint.Break("inner")
                        } else {
                            failpoint.Goto("outer")
                        }
                    })
            }
        }
    }
    

    El bloque de código anterior generará el siguiente código:

    root@kitploit:~
    outer:
        for i := 0; i < 100; i++ {
        inner:
            for j := 0; j < 1000; j++ {
                switch rand.Intn(j) + i {
                case j / 5:
                    break
                case j / 7:
                    continue outer
                case j / 9:
                    fallthrough
                case j / 10:
                    goto outer
                default:
                    if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
                        fmt.Println("unit-test", val.(int))
                        if val == j/11 {
                            break inner
                        } else {
                            goto outer
                        }
                    }
                }
            }
        }
    
  • Quizás te preguntes por qué no usamos label, break, continue y fallthrough directamente en lugar de usar funciones marcadoras de failpoint.

    • Cualquier símbolo no utilizado, como un identificador o una etiqueta, no está permitido en Golang. Será inválido si alguna etiqueta solo se usa en el cierre del failpoint. Por ejemplo:

      root@kitploit:~
      label1: // compiler error: unused label1
          failpoint.Inject("failpoint-name", func(val failpoint.Value) {
              if val.(int) == 1000 {
                  goto label1 // illegal to use goto here
              }
              fmt.Println("unit-test", val)
          })
      
    • break y continue solo se pueden usar en el contexto de un bucle, lo cual no es legal en código Golang si los usamos directamente en un cierre.

  • root@kitploit:~
    func (s *StoreService) ExecuteStoreTask() {
        select {
        case <-func() chan *StoreTask {
            failpoint.Inject("priority-fp", func(_ failpoint.Value) {
                return make(chan *StoreTask)
            })
            return s.priorityHighCh
        }():
            fmt.Println("execute high priority task")
    
        case <- s.priorityNormalCh:
            fmt.Println("execute normal priority task")
    
        case <- s.priorityLowCh:
            fmt.Println("execute normal low task")
        }
    }
    

    El bloque de código anterior generará algo como esto:

    root@kitploit:~
    func (s *StoreService) ExecuteStoreTask() {
        select {
        case <-func() chan *StoreTask {
            if _, ok := failpoint.Eval(_curpkg_("priority-fp")); ok {
                return make(chan *StoreTask)
            })
            return s.priorityHighCh
        }():
            fmt.Println("execute high priority task")
    
        case <- s.priorityNormalCh:
            fmt.Println("execute normal priority task")
    
        case <- s.priorityLowCh:
            fmt.Println("execute normal low task")
        }
    }
    
  • Inyectar un failpoint para extender dinámicamente los brazos de un SWITCH CASE

    root@kitploit:~
    switch opType := operator.Type(); {
    case opType == "balance-leader":
        fmt.Println("create balance leader steps")
    
    case opType == "balance-region":
        fmt.Println("create balance region steps")
    
    case opType == "scatter-region":
        fmt.Println("create scatter region steps")
    
    case func() bool {
        failpoint.Inject("dynamic-op-type", func(val failpoint.Value) bool {
            return strings.Contains(val.(string), opType)
        })
        return false
    }():
        fmt.Println("do something")
    
    default:
        panic("unsupported operator type")
    }
    

    El bloque de código anterior generará algo como esto:

    root@kitploit:~
    switch opType := operator.Type(); {
    case opType == "balance-leader":
        fmt.Println("create balance leader steps")
    
    case opType == "balance-region":
        fmt.Println("create balance region steps")
    
    case opType == "scatter-region":
        fmt.Println("create scatter region steps")
    
    case func() bool {
        if val, ok := failpoint.Eval(_curpkg_("dynamic-op-type")); ok {
            return strings.Contains(val.(string), opType)
        }
        return false
    }():
        fmt.Println("do something")
    
    default:
        panic("unsupported operator type")
    }
    
  • Failpoints más complicados

    • Hay sitios de failpoints más complicados a los que se puede inyectar
      • en la sentencia INITIAL del bucle, la expresión CONDICIONAL y la sentencia POST
      • en la sentencia RANGE
      • en la sentencia INITIAL del SWITCH
      • …
    • En cualquier lugar donde puedas llamar a una función