Fuzzing
Oradores: Niklas Gögge
Fecha: April 27, 2023
Traducción Por: Blue Moon
Tags: Bitcoin core, Developer tools
Categoría: Core dev tech
Slides: https://docs.google.com/presentation/d/1NlTw_n60z9bvqziZqU3H3Jw7Xs5slnQoehYXhEKrzOE
Fuzzing
- El fuzzing se realiza de forma continua. Los objetivos del fuzzing pueden dar sus frutos incluso años más tarde encontrando nuevos bugs.
- Ejemplo en la diapositiva sobre libFuzzer fuzzing una función
parse_json
que podría bloquearse en alguna entrada extraña, pero no informará de entradas json no válidas que pasan el análisis. libFuzzer hace la cobertura de bucle de retroalimentación guiada + ayuda con la exploración del flujo de control.
Bug Oracles
- Aserciones - Añadir aserciones es complicado para el código de red. Añadimos
Assume()
cuando continuar no es peor que fallar.Assert()
se bloqueará en producción.Assume()
se bloqueará en depuración. Lanzar todo tipo deAssume()
en el código está bien pero ralentiza la producción. Coloca aserciones en el objetivo fuzz. - Límite de recursos - Ej: No tome más de 10 mb o 5 seg.
- Sanitizadores - ejemplos incluyen comportamiento indefinido, hilo, fuga, memoria, sanitizadores de dirección. Pueden añadir una sobrecarga de rendimiento o memoria. Todo excepto el sanitizador de memoria es fácil de usar. Se recomienda no utilizar varios desinfectantes a la vez.
- Pares inversos de funciones - codificar/decodificar
- Fuzzing diferencial - comparación de la implementación actual con una implementación más simple. Hecho en coinscache y txrequest.
- Transformación de espacio nulo - sólo hacer mutaciones que preserven la semántica.
- Comprobaciones específicas de dominio - por ejemplo, un bloque de bitcoin bajo la regla de la bifurcación suave debería ser válido también cuando no se aplica la bifurcación suave.
Mejores prácticas para los objetivos
- Evitar fallos no bugs - traducir pruebas unitarias a pruebas fuzz (para cartera)
- Verificar la cobertura - es necesario combinar información sobre lo que se supone que debe hacer la función + conocimientos sobre pruebas. escribir objetivo + verificar que llega al código que se quiere probar. Verificar la cobertura usando estadísticas de cobertura, assert False.
- Determinismo - identificar bugs que el fuzz test ingenuo no falló para que sean reproducibles. Idealmente incluye pruebas fuzz en tu PR y discrimina entre diferentes objetivos fuzz. La entrada al fuzzer no es aleatoria, es sólo la mejor entrada posible. Si hay un bug, evita la aleatoriedad real - usa semillas fijas para simular la aleatoriedad.
- Rendimiento - se gasta computación mientras la cobertura se incrementa y si se está llegando al máximo hay que parar explícitamente los fuzz targets. Fuzzing con sanitizadores a veces da cobertura adicional. Sin embargo, los sanitizadores suelen ralentizar el proceso. Así que ejecuta el corpus con sanitizadores como una comprobación de sanidad extra más tarde. Reinicia el estado global al final de cada iteración + intenta evitar el estado global. Evite las E/S costosas. A veces se pierde tiempo en entradas menos interesantes.
- Mantenga el alcance del objetivo pequeño - Piense en lo que quiere probar y enfoque el fuzzer en esa parte del código. Divide las APIs más grandes en múltiples objetivos si es necesario.
- Lee fuzzing docs
- Piensa en escribir objetivos fuzz como si estuvieras escribiendo auditorías como pruebas unitarias, pruebas funcionales.
Procesamiento de redes
- ¿fuzzing de integración? cómo abordar la interacción entre componentes utilizando fuzz target/mejorar algunos de nuestros fuzzing basados en integración.
- Cuando se hace fuzzing de procesamiento de red, realmente no importa el ping pong que entra al mismo tiempo o la lógica de validación. transaction relay por ejemplo. fuzzer se confunde cuando hay demasiada información y hace muchas mutaciones innecesarias.
ProcessMessage()
es una sentencia switch gigante en su mayor parte - libFuzzer no es capaz de producir transacciones válidas, bloques, cabeceras debido a PoW, firmas, hashes, etc. Podríamos/deberíamos simular la validación para permitir un mejor fuzzing del procesamiento de la red. Boundary testing - no es realmente fuzzing - una técnica diferente que está bien tener a una nueva técnica para probar interacciones? Aún es necesario lanzar envoltorios alrededor de las funciones.txrequest
,txorphan
,headerssync
- encapsular en módulos propios y probarlos por separado. Queremos procesamiento neto fuzz en el aislamiento - haciendo más refactorización / mocks. algo de trabajo ya, pero más correcciones necesarias.
División de entrada
- input splitting - divide bytestream en estructuras de datos c++ válidas.
FuzzDataProvider
- utilidad para dividir entradas fuzz en estructuras de datos c++. Por ejemplo, si quieres procesar bloques fuzz, usaFuzzDataProvider
para parsear las entradas fuzz en bloques válidos. podríamos escribir código para producir semántica válida pero demasiado código en la parte de pruebas.
Procesamiento neto
- ver https://github.com/bitcoin/bitcoin/issues/27502
- hecho un problema con todas las cosas que quiero hacer si básicamente queremos fuzz procesamiento de red de forma aislada y burlarse de la parte de red.
- Posición para motivar a todos refactorización - no odiamos refactorización. Podemos convertir estas cosas rojas en el procesamiento de red en verde para que estas regiones de código estén cubiertas. Eso sería bastante bueno. Este código ya ha encontrado algunos bugs.
- Rendimiento - actualmente estos objetivos crean 1 instancia global de chainstate manager etc. pero si quieres hacer el objetivo determinista entonces necesitas crear una nueva instancia de chainstate manager en cada iteración y ese proceso actualmente es muy lento. Hice un objetivo y tomó 10 - 50 exec/sec. En el futuro, el almacenamiento de bloques en memoria se puede utilizar en las pruebas para un mejor rendimiento.
- Separación de módulos - Creo que la separación de módulos entre la red y el procesamiento de la red ha ido bastante bien. Casi terminado - todavía queda algo de trabajo por hacer. se agradecen las revisiones. fuzzing parece ser una gran motivación para la refactorización.
- Separación entre procesamiento de red y validación: Mucho solapamiento con el núcleo - que va a ser un montón de trabajo y duro - no han trazado todas las cosas que hay que hacer.
- Hablando de refactorizaciones de procesamiento de red - se encontró un error en el módulo
ProcessMessage()
. si la única motivación es refactorizar tal vez no deberíamos. Fuzzing encuentra bugs - tan importante también. - Los cambios de refactorización necesitan ir acompañados de fuzzing porque la refactorización a menudo produce bugs.
No tenemos un objetivo fuzz que específicamente fuzzed versión handshake y la rama que tengo lo hace y algunas pruebas adicionales para el mismo. La razón por la que no tenemos fuzzing de version handshake es porque para que funcione
ProcessMessage()
de los peers, necesitan ser inicializados. - Sugerencia de tener un documento que explique todas las interfaces introducidas al refactorizar.
Infraestructura de Fuzzing de Bitcoin Core
- Contributors run their own fuzzing infra (Marco) + OSS-Fuzz (cluster fuzz instance managed by google).
- Google donating CPU to open source projects for fuzzing.
- CPU por objetivo por sanitizador por fuzz engine
¿cómo contribuir con inputs a qa assets? compruebe la opción
--generate
. - formas de ejecutar la prueba fuzz - utilizando corpus vacío o corpus de qa-assets repo Se clona el corpus de qa-assets localmente - la cosa es que se hace muy grande, menos que un blockchain :) Github limita el tamaño del repo de qa-assets es una posibilidad, no una preocupación actualmente.
- A veces es bueno empezar con un corpus vacío que podría tener cobertura adicional.
- A veces es bueno empezar con un corpus vacío que podría tener cobertura adicional. trabajando en algún código que tiene un fuzzer asociado con él - voy a ejecutar ese fuzzer a veces con un corpus vacío estratégicamente - intuición - no sé buena manera.
- ¿borramos entradas del corpus? sí, en el branch off.
- CI fuzz también se hace - se ejecuta en las semillas existentes durante 2 min.
- ¿Ejecutar nuestra propia instancia de cluster fuzzing? no es tan fácil + mantenimiento extra.
- ¿Dependemos de la infraestructura de Google? Tenemos acceso al corpus de OSS fuzz. ¿Qué pasa si Google nos echa/no nos cuenta los bugs? siempre han contado los bugs en incidentes pasados. Recientemente hubo un error de desbordamiento en la lógica del miniscript. En ese momento, yo estaba fuzzing en un núcleo 30. Durante esa misma ventana de tiempo de 24 horas, OSS fuzz lo reveló también - por lo que una indicación de que eran honestos primero.
- Hay un plazo de 90 días para la divulgación de errores establecido por OSS-Fuzz, pero bitcoin core tiene una excepción. Nunca es necesario y es poco probable que encontremos un bug grave. Hasta ahora, normalmente encontramos errores graves mientras escribimos nuevos objetivos y los ejecutamos en nuestra propia infraestructura.
Lo que sigue
- Contribuir al corpus en qa assets repo que añade cobertura adicional.
- Revisar PRs.
- Escribir pruebas fuzz.