Scudo 是一种动态用户模式内存分配器,或称 *堆* 分配器,旨在在保持性能的同时,防止与堆相关的漏洞(例如基于堆的缓冲区溢出、释放后使用和双重释放)。它提供标准的 C 分配和释放原语(例如 malloc 和 free),以及 C++ 原语(例如 new 和 delete)。
Scudo 更多的是一种缓解措施,而不是像 AddressSanitizer (ASan) 这样成熟的内存错误检测器。
自 Android 11 版本发布以来,Scudo 已用于所有原生代码(低内存设备除外,这些设备仍使用 jemalloc)。在运行时,所有原生堆分配和释放都由 Scudo 为所有可执行文件及其库依赖项提供服务,并且如果在堆中检测到损坏或可疑行为,则该进程将中止。
Scudo 是 开源 的,并且是 LLVM 的 compiler-rt 项目的一部分。文档可在 https://llvm.net.cn/docs/ScudoHardenedAllocator.html 获取。Scudo 运行时作为 Android 工具链的一部分发布,并且 Soong 和 Make 中添加了支持,以便在二进制文件中轻松启用分配器。
您可以使用下面描述的选项在分配器内启用或禁用额外的缓解措施。
自定义
分配器的某些参数可以通过以下几种方式在每个进程的基础上定义
- 静态方式:在程序中定义一个
__scudo_default_options
函数,该函数返回要解析的选项字符串。此函数必须具有以下原型:extern "C" const char *__scudo_default_options()
。 - 动态方式:使用包含要解析的选项字符串的环境变量
SCUDO_OPTIONS
。以这种方式定义的选项将覆盖通过__scudo_default_options
所做的任何定义。
以下选项可用。
选项 | 64 位默认值 | 32 位默认值 | 描述 |
---|---|---|---|
QuarantineSizeKb |
256 |
64 |
隔离区大小(KB),用于延迟实际释放块的时间。较低的值可能会减少内存使用量,但会降低缓解措施的有效性;负值将回退到默认值。将此选项和 ThreadLocalQuarantineSizeKb 都设置为零将完全禁用隔离区。 |
QuarantineChunksUpToSize |
2048 |
512 |
可以隔离的块的最大大小(字节)。 |
ThreadLocalQuarantineSizeKb |
64 |
16 |
每个线程缓存的大小(KB),用于卸载全局隔离区。较低的值可能会减少内存使用量,但可能会增加全局隔离区的争用。将此选项和 QuarantineSizeKb 都设置为零将完全禁用隔离区。 |
DeallocationTypeMismatch |
false |
false |
启用 malloc/delete、new/free、new/delete[] 的错误报告 |
DeleteSizeMismatch |
true |
true |
启用 new 和 delete 的大小不匹配时的错误报告。 |
ZeroContents |
false |
false |
在分配和释放时启用零块内容。 |
allocator_may_return_null |
false |
false |
指定当发生可恢复错误时,分配器可以返回 null,而不是终止进程。 |
hard_rss_limit_mb |
0 |
0 |
当进程的 RSS 达到此限制时,进程将终止。 |
soft_rss_limit_mb |
0 |
0 |
当进程的 RSS 达到此限制时,后续分配将失败或返回 null (取决于 allocator_may_return_null 的值),直到 RSS 回落以允许新的分配。 |
allocator_release_to_os_interval_ms |
不适用 | 5000 |
仅影响 64 位分配器。如果设置,则尝试将未使用的内存释放到操作系统,但频率不超过此间隔(以毫秒为单位)。如果值为负数,则不会将内存释放到操作系统。 |
abort_on_error |
true |
true |
如果设置,则该工具在打印错误消息后调用 abort() 而不是 _exit() 。 |
验证
目前,没有专门针对 Scudo 的 CTS 测试。相反,请确保 CTS 测试在为给定二进制文件启用或未启用 Scudo 的情况下都能通过,以验证它不会影响设备。
问题排查
如果检测到不可恢复的问题,分配器会将错误消息显示到标准错误描述符,然后终止进程。导致终止的堆栈跟踪将添加到系统日志中。输出通常以 Scudo ERROR:
开头,后跟问题的简短摘要以及任何指针。
以下是当前错误消息及其潜在原因的列表
corrupted chunk header
:块头的校验和验证失败。这可能是由以下两种情况之一引起的:标头被覆盖(部分或全部),或者传递给函数的指针不是块。race on chunk header
:两个不同的线程正尝试同时操作同一个标头。这通常是竞争条件或在对该块执行操作时普遍缺乏锁定的症状。invalid chunk state
:块对于给定的操作不处于预期状态,例如,尝试释放它时未分配,或者尝试回收它时未隔离。双重释放是此错误的典型原因。misaligned pointer
:基本对齐要求得到严格执行:32 位平台为 8 字节,64 位平台为 16 字节。如果传递给我们函数的指针不符合这些要求,则传递给其中一个函数的指针未对齐。allocation type mismatch
:启用此选项后,在块上调用的释放函数必须与调用以分配它的函数类型匹配。这种类型的不匹配可能会引入安全问题。invalid sized delete
:当使用 C++14 大小删除运算符并且启用可选检查时,释放块时传递的大小与分配时请求的大小之间存在不匹配。这通常是编译器问题或要释放的对象上的类型混淆。RSS limit exhausted
:已超出可选指定的最大 RSS。
如果您正在调试操作系统本身的崩溃,则可以使用 HWASan OS 版本。如果您正在调试应用程序中的崩溃,也可以使用 HWASan 应用程序版本。