Scudo

Scudo es un asignador de memoria dinámico en modo de usuario, o asignador heap, diseñado para ser resistente contra vulnerabilidades relacionadas con el montón (como el desbordamiento de búfer basado en montón, usar después de liberar y doble liberación) y, al mismo tiempo, mantener el rendimiento. Proporciona las primitivas de asignación y desasignación estándar de C (como malloc y free), así como las primitivas de C++ (como new y delete).

Scudo es más una mitigación que un detector de errores de memoria completo, como AddressSanitizer (ASan).

A partir de la versión de Android 11, Scudo se usa para todo el código nativo (excepto en dispositivos con poca memoria, en los que todavía se usa jemalloc). Durante el tiempo de ejecución, Scudo se encarga de todas las asignaciones y desasignaciones de montón nativo para todos los ejecutables y sus dependencias de bibliotecas, y el proceso se aborta si se detecta una corrupción o un comportamiento sospechoso en el montón.

Scudo es de código abierto y forma parte del proyecto compiler-rt de LLVM. La documentación está disponible en https://llvm.org/docs/ScudoHardenedAllocator.html. El entorno de ejecución de Scudo se envía como parte de la cadena de herramientas de Android, y se agregó compatibilidad con Soong y Make para permitir que el asignador se habilite fácilmente en un ejecutable.

Puedes habilitar o inhabilitar la mitigación adicional dentro del asignador con las opciones que se describen a continuación.

Personalización

Algunos parámetros del asignador se pueden definir por proceso de varias maneras:

  • De forma estática: Define una función __scudo_default_options en el programa que devuelva la cadena de opciones que se analizará. Esta función debe tener el siguiente prototipo: extern "C" const char *__scudo_default_options().
  • De forma dinámica: Usa la variable de entorno SCUDO_OPTIONS que contiene la cadena de opciones que se analizará. Las opciones que se definan de esta manera anulan cualquier definición creada a través de __scudo_default_options.

Las siguientes opciones están disponibles.

Opción Configuración predeterminada de 64 bits Configuración predeterminada de 32 bits Descripción
QuarantineSizeKb 256 64 Es el tamaño (en KB) de la cuarentena que se usa para retrasar la reasignación real de fragmentos. Un valor más bajo puede reducir el uso de memoria, pero disminuir la eficacia de la mitigación; un valor negativo recurre a los valores predeterminados. Si estableces esto y ThreadLocalQuarantineSizeKb en cero, se inhabilita por completo la cuarentena.
QuarantineChunksUpToSize 2048 512 El tamaño (en bytes) hasta el que se pueden poner los fragmentos en cuarentena.
ThreadLocalQuarantineSizeKb 64 16 Es el tamaño (en KB) de la caché por subproceso que se usa para descargar la cuarentena global. Un valor más bajo puede reducir el uso de memoria, pero podría aumentar la contención en la cuarentena mundial. Si estableces este valor y QuarantineSizeKb en cero, se inhabilita por completo la cuarentena.
DeallocationTypeMismatch false false Habilita los informes de errores en malloc/delete, new/free, new/delete[].
DeleteSizeMismatch true true Habilita Error Reporting sobre la falta de coincidencia entre los tamaños de los elementos nuevos y borrados.
ZeroContents false false Habilita contenido sin fragmentos en la asignación y la desasignación.
allocator_may_return_null false false Especifica que el asignador puede mostrar un valor nulo cuando se produce un error recuperable, en lugar de finalizar el proceso.
hard_rss_limit_mb 0 0 Cuando el RSS del proceso alcanza este límite, el proceso finaliza.
soft_rss_limit_mb 0 0 Cuando el RSS del proceso alcanza este límite, las asignaciones adicionales fallan o muestran null (según el valor de allocator_may_return_null), hasta que el RSS vuelve a bajar para permitir asignaciones nuevas.
allocator_release_to_os_interval_ms N/A 5000 Solo afecta a un asignador de 64 bits. Si se establece, intenta liberar la memoria no utilizada al SO, pero no con más frecuencia que este intervalo (en milisegundos). Si el valor es negativo, la memoria no se libera al SO.
abort_on_error true true Si se configura, la herramienta llama a abort() en lugar de _exit() después de imprimir el mensaje de error.

Validación

Actualmente, no hay pruebas de CTS específicas para Scudo. En su lugar, asegúrate de que las pruebas de CTS aprueben con o sin Scudo habilitado para un binario determinado para verificar que no afecte el dispositivo.

Solución de problemas

Si se detecta un problema irrecuperable, el asignador muestra un mensaje de error al descriptor de errores estándar y, luego, finaliza el proceso. Los seguimientos de pila que conducen a la finalización se agregan al registro del sistema. Por lo general, el resultado comienza con Scudo ERROR: seguido de un breve resumen del problema junto con algún puntero.

A continuación, se muestra una lista de los mensajes de error actuales y sus posibles causas:

  • corrupted chunk header: Falló la verificación de la suma de verificación del encabezado del fragmento. Es probable que esto se deba a una de las siguientes opciones: el encabezado se reemplazó (parcial o totalmente) o el puntero que se pasó a la función no es un fragmento.
  • race on chunk header: Dos subprocesos diferentes intentan manipular el mismo encabezado al mismo tiempo. Por lo general, esto es un síntoma de una condición de carrera o una falta general de bloqueo cuando se realizan operaciones en ese fragmento.
  • invalid chunk state: El fragmento no se encuentra en el estado esperado para una operación determinada, por ejemplo, no se asigna cuando se intenta liberarlo o no se pone en cuarentena cuando se intenta reciclar. Una liberación doble es el motivo habitual de este error.
  • misaligned pointer: Los requisitos de alineación básica se aplican con firmeza: 8 bytes en plataformas de 32 bits y 16 bytes en plataformas de 64 bits. Si un puntero pasado a nuestras funciones no se ajusta a ellas, el puntero pasado a una de las funciones no está alineado.
  • allocation type mismatch: Cuando esta opción está habilitada, una función de desasignación llamada en un fragmento debe coincidir con el tipo de función a la que se llamó para asignarla. Este tipo de discrepancia puede generar problemas de seguridad.
  • invalid sized delete: Cuando se usa el operador delete de tamaño C++14 y se habilita la verificación opcional, hay una discrepancia entre el tamaño que se pasó cuando se desasignó un fragmento y el tamaño que se solicitó cuando se asignó. Por lo general, se trata de un problema del compilador o una confusión de tipos en el objeto que se desasignará.
  • RSS limit exhausted: Se superó el RSS máximo especificado de forma opcional.

Si estás depurando una falla en el SO, puedes usar una compilación del SO de HWASan. Si estás depurando una falla en una app, también puedes usar una compilación de la app de HWASan.