2022年9月13日,Google安全团队在其安全博客中发布了一篇关于MiraclePtr的文章,介绍了Google Chrome安全团队在缓解UAF漏洞利用上的进展。由于MiraclePtr并不是单指某一种智能指针技术,而是包含了Google安全团队在缓解UAF利用上的多次实验和尝试,本文也仅针对其最新启用的BackupRef方案做介绍,如有疏漏错误,敬请斧正,共同交流探讨。
MiraclePtr
首先需要明确,MiraclePtr与unique_ptr、weak_ptr等C++中的原始智能指针并不是同一概念,它是Google安全团队在缓解由指针引起的内存破坏漏洞过程中,提出的多种方案集合,其本质是希望将原始指针迁移到带缓解方案的智能指针类,通过引用计数、指针标记等方式阻止攻击者对内存破坏漏洞被利用,重点解决的是UAF类型漏洞的悬垂指针可被利用的问题。
如上图,Google安全团队认为攻击者在针对Chrome的攻击过程中,通常需要组合一个渲染器漏洞利用和一个沙箱逃逸漏洞来达到完整利用的目的,MiraclePtr可以通过缓解UAF漏洞利用,有效的阻止攻击者针对浏览器主进程中UAF漏洞的利用(上图蓝色部分),让攻击者无法实现完整的利用链,从而降低漏洞危害。
在对Chrome历史可利用漏洞统计中,UAF类型漏洞占了几乎一半,因此MiraclePtr也尝试了包含BackupRefPtr、BorrowPtr、SafePtr、CheckedPtr、MTECheckedPtr、ViewPtr在内的多种方式来缓解UAF类型的漏洞利用,并在对比了各方案在性能开销、内存开销、安全保护、开发人员便利性上的优缺点后,近期在Windows和Android的Chrome 102稳定版中启用了BackupRefPtr,下文只重点介绍BackupRefPtr,其他方案详细信息查看参考链接中的内容。
BackupRefPtr方案
BackupRefPtr提出了依赖“确定性引用计数”的指针保护方案,主要借鉴了CheckedPtr2、SafePtr和BorrowPtr的思路,并且需要Chrome的堆内存分配器PartitionAlloc支持。在2020年,Google ProjectZero在博客公布的一篇采用CPU漏洞侧信道攻击来泄漏缓存数据,从而实现Chrome沙箱逃逸的文章,证明了依赖指针标记的方案有潜在的被通过侧信道攻击的风险,出于安全性考虑,确定性引用计数的方案成了优先选择。
PartitionAlloc是Chrome中自行实现的堆分配器,主要在分配效率、内存空间利用率和安全性上进行了优化。PartitionAlloc使用2MB大小的超级页面作为普通数据桶,每个超级页面被分割成多个分区。第一个和最后一个分区是永久不可访问的,用来当作保护页面,在第一个分区页中间的一个系统页面保存了元数据(metadata),这些元数据提供了对内存对象的跟踪能力,BackupRefPtr使用到的引用计数就存储在metadata中。
在Chromium的源码实现中,BackupRefPtr是一个线程安全的引用计数指针类,可以非常简单的替换原始指针,Chromium团队在引入BackupRefPtr时也一次性替换了源码之中超过15000个原始指针。BackupRefPtr的引用计数存储在PartitionAlloc元数据中(与CheckedPtr2方案使用同一标志位),如果在销毁一个对象时,它的引用计数不为零,则会将该对象标记为被污染,此时程序不会真正的释放该内存,而是在再次访问被破坏的对象时,程序将发生主动崩溃。
该方案PoC代码如下,具体实现可参考Chromium源码raw_ptr.h中的BackupRefPtrImpl类:
template <typename T>
class BackupRefPtr {
BackupRefPtr(T* ptr) : ptr_(ptr) {
if (!isSupportedAllocation(ptr))
return;
atomicint& ref_count = (cast<atomic_int>(ptr) – 1);
CHECK(++ref_count);
}
~BackupRefPtr() {
if (!isSupportedAllocation(ptr))
return;
atomicint& ref_count = (cast<atomic_int>(ptr) – 1);
if (—ref_count == 0) // needed in case the BackupRefPtr outlives
// its pointee and has to free the slot
PartitionAlloc::ActuallyFree(ptr);
}
T operator->() { return ptr_; }
T ptr_;
};
void Alloc(size_t size) {
void ptr = ActuallyAlloc(size);
if (isSupportedAllocation(ptr)) {
int& ref_count = (cast<int>(ptr) – 1);
ref_count = 1; // We need to set the reference count to one initially
// otherwise |~BackupRefPtr| can trigger deallocation of
// an object that’s still alive.
}
return ptr;
}
void Free(void ptr) {
if (isSupportedAllocation(ptr)) {
atomic_int& ref_count = (cast<atomic_int*>(ptr) – 1);
if (ref_count != 1)
memset(ptr, 0xcc, getAllocationSize(ptr));
if (—ref_count != 0)
return;
}
ActuallyFree(ptr);
}
总结
BackupRefPtr通过上述机制,解决了悬垂指针(Dangling Pointer)被利用的问题,在该方案中,发生释放操作但引用计数不为0的对象并没有被真正释放,攻击者无法使用堆喷射等方式重新分配该对象的内存空间,并且在对象再次被访问时,该内存区域被填充了污染标志或发生主动崩溃,UAF漏洞被缓解为内存泄漏、断言失败或空指针等无法利用的崩溃。
整体而言,该机制的引入进一步降低了Chrome中可利用漏洞的比例,一定程度上提高了Chrome的安全性。
参考链接
1、Use-after-freedom: MiraclePtr
https://security.googleblog.com/2022/09/use-after-freedom-miracleptr.html
2、Pointer Safety Ideas [PUBLIC] – Comparison of Use-After-Free mitigation proposals
https://docs.google.com/document/d/1qsPh8Bcrma7S-5fobbCkBkXWaAijXOnorEqvIIGKzc0/edit
3、BackupRefPtr
https://docs.google.com/document/d/1m0c63vXXLyGtIGBi9v6YFANum7-IRC3-dmiYBCWqkMk/edit#heading=h.jgclb3snutxw
4、PartitionAlloc Design
https://chromium.googlesource.com/chromium/src/+/main/base/allocator/partition_allocator/PartitionAlloc.md
5、Escaping the Chrome Sandbox with RIDL
https://googleprojectzero.blogspot.com/2020/02/escaping-chrome-sandbox-with-ridl.html
6、MDS: Microarchitectural Data Samplin
https://mdsattacks.com/