Scudo es un asignador de memoria dinámico en modo de usuario, o asignador de pila, diseñado para ser resistente contra las vulnerabilidades relacionadas con pilas (como el desbordamiento de búfer basado en pila, el uso después de la liberación y la liberación doble) 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).
Desde el lanzamiento de Android 11, se usa scudo para todo el código nativo (excepto en dispositivos con poca memoria, en los que todavía se usa jemalloc). En el tiempo de ejecución, Scudo controla todas las asignaciones y desasignaciones de memoria dinámica nativa para todos los ejecutables y sus dependencias de biblioteca, y el proceso se anula si se detecta corrupción o comportamiento sospechoso en la memoria dinámica.
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 tiempo de ejecución de Scudo se incluye como parte de la cadena de herramientas de Android, y se agregó compatibilidad con Soong y Make para permitir la habilitación sencilla del asignador en un archivo binario.
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 muestre 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 definidas de esta manera anulan cualquier definición realizada a través de__scudo_default_options
.
Están disponibles las siguientes opciones.
Opción | Configuración predeterminada de 64 bits | Valor predeterminado de 32 bits | Descripción |
---|---|---|---|
QuarantineSizeKb |
256 |
64 |
Es el tamaño (en KB) de la cuarentena que se usa para retrasar la desasignació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 revierte a los valores predeterminados. Si estableces este parámetro y ThreadLocalQuarantineSizeKb en cero, se inhabilitará la cuarentena por completo. |
QuarantineChunksUpToSize |
2048 |
512 |
Es el tamaño (en bytes) hasta el que se pueden poner en cuarentena los fragmentos. |
ThreadLocalQuarantineSizeKb |
64 |
16 |
Tamaño (en KB) del uso de la caché por subproceso 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 global. Si estableces este parámetro y QuarantineSizeKb en cero, se inhabilita la cuarentena por completo. |
DeallocationTypeMismatch |
false |
false |
Habilita los informes de errores en malloc/delete, new/free y new/delete[]. |
DeleteSizeMismatch |
true |
true |
Habilita la generación de informes de errores sobre la discrepancia entre los tamaños de los archivos nuevos y los que se borraron. |
ZeroContents |
false |
false |
Habilita el contenido de fragmentos cero en la asignación y la desasignación. |
allocator_may_return_null |
false |
false |
Especifica que el asignador puede devolver 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 posteriores fallan o devuelven null (según el valor de allocator_may_return_null ), hasta que el RSS vuelve a bajar para permitir nuevas asignaciones. |
allocator_release_to_os_interval_ms |
5000 |
N/A | Solo afecta a un asignador de 64 bits. Si se configura, intenta liberar la memoria no utilizada en el SO, pero no con una frecuencia mayor que este intervalo (en milisegundos). Si el valor es negativo, la memoria no se libera para el 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 del CTS específicas para Scudo. En cambio, asegúrate de que las pruebas de CTS se aprueben con o sin Scudo habilitado para un objeto binario determinado para verificar que no afecte al dispositivo.
Solución de problemas
Si se detecta un problema no recuperable, el asignador muestra un mensaje de error en el descriptor de error 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 cualquier sugerencia.
A continuación, se incluye 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. Esto probablemente se deba a una de las siguientes dos razones: el encabezado se sobrescribió (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 de una falta general de bloqueo cuando se realizan operaciones en ese fragmento.invalid chunk state
: El fragmento no está en el estado esperado para una operación determinada; por ejemplo, no está asignado cuando se intenta liberarlo o no está en cuarentena cuando se intenta reciclarlo. Un doble free es el motivo típico de este error.misaligned pointer
: Los requisitos básicos de alineación se aplican estrictamente: 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 esos requisitos, el puntero pasado a una de las funciones está desalineado.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 que se llamó para asignarlo. Este tipo de discrepancia puede generar problemas de seguridad.invalid sized delete
: Cuando se usa el operador delete con tamaño de 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 de una confusión de tipos en el objeto que se está desasignando.RSS limit exhausted
: Se superó el RSS máximo especificado de forma opcional.
Si depuras 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 con HWASan.