前几天, 我看完了两篇关于时间内存安全的论文, 都是通过修改堆内存分配器来缓解堆上use-after-free漏洞的工作, 特此总结如下。
MineSweeper: A “Clean Sweep” for Drop-In Use-After-Free Prevention (ASPLOS ’22)
这个工作通过避免为程序分配被潜在的dangling pointer指向的对象来缓解UAF漏洞。具体而言, 在分配对象时, 该分配器正常进行分配; 而在释放时, 该分配器首先将被释放的对象放置在隔离区, 然后通过线性地扫描内存, 来检查隔离区中的每个对象是否被某个指针指向, 若没有任何指针指向它了, 则将它真正地释放回底层的分配器。为了优化性能, 只有在隔离区的对象数量超过一定阈值时, 该分配器才统一进行一次内存线性扫描, 而不是每次释放都进行扫描。此外, 内存线性扫描是由一个独立的线程完成的。考虑到线性扫描时隔离区中的对象也会被扫描到, 因此, 若隔离区中的对象存在循环引用, 那么这些对象永远也无法被释放到底层分配器中。为了解决这一问题, 该分配器在释放时(即在将对象放到隔离区时)将对象清零。此外, 线性扫描的方法存在两个问题: 1) 可能将不是指针的数据当成指针, 于是相应的对象将无法从隔离区中释放, 但这个问题不会造成漏报; 2) 某些指针可能被变换(比如移位或异或上某个值)后存储在内存中, 即使它们确实是dangling pointer, 也无法被线性扫描发现, 使得在隔离区中的被指向的对象仍然有可能被释放到底层分配器, 造成漏报。
ViK: Practical Mitigation of Temporal Memory Safety Violations through Object ID Inspection (ASPLOS ’22)
这个工作首先在分配对象时为每个对象随机生成一个ID, 并将这个ID存储在对象的起始, 同时将这个ID附加在返回给程序的指针上。此后, 在每一次指针被使用时, 只需要检查指针携带的ID与对象的ID是否匹配即可。为了快速地从一个指针找到其指向的对象的ID, 即找到该对象的起始地址, 该方案首先对对象的大小和对齐进行限制, 使得一个对象的每个字节的高几位地址是相同的, 且对象起始地址的低几位确定为全零。然后, 该方案将对象的起始地址的中间几位与ID一起附加在指针上, 这样即可在每次检查时快速地通过指针算出对象的起始地址。检查是耗时的, 为了优化性能、减少检查的次数, 该工作还实现了一个数据流分析算法, 计算每个程序点的UAF-safe指针的集合。其中, UAF-safe是指对该指针的使用大概率不会触发UAF漏洞, 例如, 该指针指向的是全局变量或栈变量。
此外, 该工作的检测只是概率性的, 因为对象的ID有可能冲突。例如, 10位的ID只有1024种可能, 漏检概率约为千分之一。我在阅读论文时, 发现论文的实际检测算法(一些位运算)可能导致漏检的概率比理论值高, 需要进一步证实。
发表评论