Android-R- 中的 heap 新分配器——Scudo
Primary 分配器
这是一个快速分配器,用于处理较小的请求(可在编译时配置)。它是“隔离的”,例如:相同大小的块最终位于相同的存储区域中,并与其他区域分隔开(64 位的分隔更强,其中专门为主要区域保留了存储区域);主节点分配的块被随机分配以避免可预测的地址序列(请注意,大小越大,地址彼此之间的可预测性就越高)。
Secondary 分配器
包装平台内存分配原语,因此速度较慢,用于服务较大的分配。辅助服务器完成的分配被保护页面包围;
本地缓存
这些是线程专用的存储,持有指向空闲块的指针,以减轻对全局空闲列表的争用。有两种模式:独占模式和共享模式。使用排他性模型,每个线程都有一个唯一的缓存,这会占用更多的内存,但几乎没有争用。使用共享模型,线程可以共享一定数量的缓存,这些缓存可以在运行时根据竞争情况进行动态重新分配-与专用模型相比,它使用的内存更少,通常可以更好地满足最终用户平台的需求。
隔离区
可以等同于 heap?范围内的延迟释放列表,在将其释放系统之前,将最近释放的块保留一段时间,直到满足条件(通常达到一定大小)为止。?有线程的隔离区和一个全局隔离区。就内存使用情况和某种程度上的性能而言,这是最有影响力的:即使是较小的隔离区也会对进程 RSS 产生很大影响。因此,默认情况下它是禁用的,并且可以在每个进程的基础上启用(并根据进程的需要调整大小)。
安全性
强制执行最大大小和对齐值,但还要检查提供的指针是否正确对齐;这些是便宜的检查,以避免整数溢出并捕获较低的挂起分配错误(或滥用);
每个块之前都有一个 header,该 header 存储有关分配的基本信息和校验码,并经过校验和以能
够检测到该 memory 是否损坏。
header 的校验和,要处理的指针以及 header 的内容-这并不意味着密码学上很强。至于存储在头文件中的数据,它保存分配的大小,块的状态(可用,已分配,隔离),其来源(malloc,new,new [])和一些内部数据。头是原子操作的,以检测在同一块上运行的线程之间的竞争尝试。
确保释放函数与返回目标块的分配函数一致(例如:free / malloc,delete / new);我们会随机分配一切,以尽可能降低可预测性;线程缓存的附带好处之一是,如果攻击者利用不同线程中的分配原语,它们会使攻击者更难在所需的状态下获取所需的块。
让我们看一下 Google 生产服务的一些典型基准,其中涉及许多异步线程,protobuf,RPC 和其他优点,所有这些都运行在具有 512GB RAM 的 72 核心 Xeon 机器上(并不是要进行最严格的比较,而是让您了解最新情况。)第一个指标是每秒的查询数,第二个指标是程序的 RSS 峰值(由/ usr / bin / time 报告)。
hardened_malloc 主要针对 Android,目前仅支持 4 个竞技场,因此该比较并不重要,因为它会严重影响并发性。增加该数字将导致 mmap()失败。** Guarder 默认情况下最多仅支持 128 个线程,增加该数量会导致 mmap()失败。限制线程数是我发现使其工作的唯一方法,但结果与其他方法不具有可比性。*tcmalloc 和 jemalloc 速度很快,但不能防止 head 堆的漏洞。dlmalloc 有时比其他方法更安全,但速度没有那么快。安全分配器在工作时表现不佳。
Scudo 的想法是“尽可能快地应对基于堆的错误,同时又具有弹性”。Scudo 并不是最安全的分配器,但是(希望)它将通过各种可配置的选项(更安全)来使利用变得更加困难(这些选项可提高安全性(但是,这会带来性能和内存占用的代价,例如隔离))。它也应成为将来缓解风险的良好工作基础(例如内存标记或 GWP-ASan)。
评论