Scudo

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 应用程序版本。