前言
系统级别语言以其高性能高效率而广泛应用于各类软件的编写,但同时也饱受内存时间安全问题所扰。 近些年来,像释放后重用漏洞这类的内存时间安全漏洞数量逐渐增多,对软件安全造成了很大的威胁。因而,这些年来涌现了许多关于保障内存时间安全的研究。本文通过归纳总结不同内存时间安全防御的研究,并探讨内存时间安全的未来研究方向。
引言
内存安全问题一直都是计算机安全领域所关注的重点问题。据MITRE统计[1],2019年最高危的前十个CWE中,内存安全类漏洞占了约50%。而导致内存安全漏洞泛滥的原因是大量的软件都是基于不安全的语言,如C和C++。这类语言具有对内存直接操作的特性,从而提高了编程效率和程序的性能,但也正因为能对内存操作,所以引入了许多内存安全问题。而且这类语言编写的代码存在于各类系统级软件中,如操作系统、虚拟机等。用其他相对安全的语言是很难替代这类语言完成这些系统级软件的编写的,因为缺乏对于计算机底层操作的支持。此外,现存的大量软件都存在这类安全问题,重写这些软件也是不太现实的。
内存安全可以分为内存空间安全和内存时间安全。由于内存空间漏洞具有危害大,容易利用等特点,现阶段关于内存空间安全的研究较为成熟。相比之下,关于内存时间安全的研究相对较少。内存时间安全问题主要是由Use After Free(UAF)和Double Free等指针状态类漏洞导致的。攻击者可以利用这样的UAF漏洞操作数据输入来获取整个程序的控制权甚至是整个系统[2]。对于内存空间安全漏洞,使用边界检查的机制能很大程度上缓解,但边界检查对于UAF漏洞无效。UAF漏洞存在因此,未来的计算机系统不仅要考虑内存空间安全,也要考虑到内存时间安全,才能构建完整的安全体系结构。
随着这些年关于内存时间安全的研究增多,要区分不同研究的优劣成了一个相对比较难的问题。因此,本文旨在系统化地整理和评估近些年提出的解决方案。本文首先描述UAF漏洞利用的原理,分析UAF漏洞利用的条件,并根据该条件划分防御方法并对其进行评估。防御方法的评估基于健壮性,兼容性和性能开销。最后本文基于该评估,总结各方法的优劣并提出未来内存时间安全防御的可能研究方向。
背景
悬垂指针与UAF漏洞的关系
悬垂指针指的是指向某对象的指针,当对象释放后,该指针指向的是内存是不确定的。所以称该指针为悬垂指针。
图1 Use After Free 漏洞示例代码
UAF漏洞本质上是悬垂指针的重引用。图1展示了一个UAF漏洞代码。其可简化为三步:第一,程序先是在堆上为buf1分配了size大小的空间,之后释放buf1,使得buf1成为悬垂指针,即其指向的内存数据是不可预测的。这块内存有可能被堆管理器回收,也可能被其他数据占用,存在着很大的不可预测性。第二,程序为buf2在堆上分配与buf1相等大小的空间,这里由于buf1的释放和buf2的分配内存的时间间隔较近,且分配的内存大小一致,根据内存分配的原则,有很大可能使得buf2分配到已释放的buf1的内存位置上去。第三,重引用悬垂指针buf1。这里为其赋值为“hack”字符串,由于buf2和buf1指向同一块内存,将会导致buf2的值也被篡改为“hack”。
内存时间安全漏洞利用方式
内存时间安全威胁主要由UAF漏洞导致,但也包括双重释放漏洞[3]。
图2 Use After Free 攻击图解
利用UAF漏洞实施攻击的核心思路是攻击者在已释放区域放置精心构造的数据,当程序重引用悬垂指针时,就会触发攻击。所以如果攻击者在释放区域设置一个虚拟函数表指针,当内存重引用悬垂指针时,就会跳转到攻击者想要执行的代码位置处执行代码,从而使攻击者劫持程序的控制流,如图2所示。另一个利用UAF漏洞的例子是攻击者将用于检查访问合法的root权限标志放在已释放区域,就可以实现权限提升攻击。而且如果漏洞存在于内核中,攻击者将能控制整个系统。此外,还可以利用这类漏洞篡改关键数据,也可以实现非控制数据攻击。因此,UAF漏洞可以作为多种攻击的突破口。
双重释放漏洞,则是UAF漏洞的一个特例,只是用悬垂指针来调用free 函数。在这种案例下,由攻击者控制内容的新对象会被误认为是堆的元数据,从而可以写任意内存[3]。
总之,成功利用UAF漏洞实施攻击需要三个必不可少的元素:一是悬垂指针的产生,二是分配到了悬垂指针所指向的内存空间的对象,三是重引用悬垂指针的指令(读或写)。
内存时间安全的挑战
图1显示的代码只是一种简单的UAF漏洞利用方式,现实生活中的UAF漏洞要更复杂的多。首先,UAF漏洞利用需要的三个元素可以存在于不同的函数和模块中。其次,实际应用运行时,这些操作可能会在不同的线程中发生。比如浏览器需要处理来自JavaScript或者DOM树的各种事件,UI应用需要处理用户产生的事件,然后服务器端还要处理大量的用户请求。基于如此复杂的场景,导致程序员很难避免这类漏洞。最后,指针还可以传播复制到程序的各种地方,加大了悬垂指针发现的难度。
而要放弃使用C或C++这种语言也是不现实的,因为许多底层的系统的实现需要这类语言操作内存的特性。况且,现存的许多软件都是基于这类语言编写的。因此,内存时间安全保障的机制必不可少。
内存时间安全防御方法
要成功利用UAF漏洞实现攻击需要三个要素:悬垂指针、复用已释放的内存,重引用悬垂指针的指令。因此,只要消除其中的某个元素就能达到内存时间安全防御的目的,于是主要方法可分为以下三类:基于消除悬垂指针的方法,基于内存分配的方法,基于防止重引用的方法。实际上有些方法消除了其中的两个或所有元素,这里按时间顺序归类,即某方法消除了第二,三个元素,则将其归为第二类方法。
基于消除悬垂指针的方法
基于指针的方法的核心思路是两步:第一,找到悬垂指针;第二,悬垂指针置空。这类方法有DangNull[4]、FreeSentry[5]、DangSan[6]和PSweeper[7]。DangNull针对内存时间安全的根本原因——悬垂指针,通过指针追踪对象的信息,当对象被释放时,将其指针置空,从而避免后续的潜在威胁。但是,DangNull只能追踪堆上对象的指针,而无视了栈和全局内存上的。FreeSentry改善了这点,可以追踪所有的指针,并降低了运行开销,但是其不支持多线程程序。而多线程程序如服务器,浏览器是UAF漏洞存在的主要地方。因此,研究者基于前面两个的研究,提出了DangSan,使得其可以支持多线程应用。但以上三种方法因为需要维护指针和对象的关系,而导致运行开销很大,而且需要在许多地方加锁来避免应用多线程的竞争。基于这些缺点,PSweeper诞生了,其在并发线程中去迭代地搜索悬垂指针,并使用对象源追踪技术来无效化悬垂指针。PSweeper使用空闲的CPU核来减少安全检查的延迟,相比上面三种方法消耗的CPU资源会更多,但会更加有效。
以上方法都是基于编译器来实现的方法,此外也有基于硬件来清洗悬垂指针的方法,比如BOGO[8]、CHERIvoke[9]。BOGO基于Intel的MPX改进,通过复用MPX的元数据来验证已释放区域的悬垂指针,使之能保护内存时间安全。但是MPX存在开销大、不支持多线程、与ISA组件存在冲突等诸多问题,使得BOGO能否应用实际成了问题。不过这种基于已有硬件来扩展的思路启发了CHERIvoke。其基于CHERI[10]架构,利用内存标记的技术,仅使用1bit的标签元数据,就能在运行时清扫内存将悬垂指针无效化。
基于内存分配的方法
通过避免对象分配复用已被释放对象的内存,来防止UAF漏洞的利用。所以这类方法有Cling[11]、DieHarder[12]和Address Sanitizer[13],其通过修改计算机内存分配的机制,来避免恶意对象复用已释放对象内存空间。
防止对象复用已释放对象内存的一种简单思路是从不使用已释放对象的内存,但如果遇到频繁释放对象内存的程序,就会造成内存的严重浪费。而Cling通过限制内存分配,只允许相同类型的对象之间重用地址空间,因此降低了性能和内存开销,并且保证了类型安全内存复用,防止了大部分的悬垂指针攻击,但不能防止攻击者重用本地堆栈分配的对象来实施攻击。DieHarder和Address Sanitizer都使用了一种延迟-复用技术,防止分配的新对象的内存空间是刚释放的旧对象的空间。但DieHarder与Address Sanitizer的目的不同,DieHarder的目的是为了提供系统运行时的防御,而Address Sanitizer更多是在系统运行前作为调试工具,检测出漏洞。这些系统能够发现非人为的UAF操作,但不适合检测蓄意的攻击[4],比如通过堆喷射来绕过这种防御机制。
此外,也有使用基于页表的技术进行分配内存,如Oscar[14]。其将每个分配的对象放在不同的虚拟页中,当一个对象被释放了,就修改相应的虚拟页的权限,使得悬垂指针无法访问被释放后的内存地址。这种基于页表的方式当分配内存大时,性能开销比较小。但是遇到频繁的小内存分配就会加大性能和内存开销,这是因为每次分配都会赋予一个虚拟页,这就导致TLB的压力,从而造成性能的下降。
除了以上使用软件方式实现的内存分配,使用硬件方法的有REST[15]和Califorms[16]。
REST用8-64B的令牌填充所有释放的内存,并将其置于独立的隔离池中。直到空闲的内存池消耗殆尽,这些隔离的内存才用于重新分配。因此,由于已释放的内存处于黑名单中,此时通过悬垂指针访问都是无效的,从而保证了内存时间安全。Califorms也是使用相同的方式,只不过元数据的粒度处于字节级别,是基于REST方法的改进,整体开销更小,保护面更广,能保护对象内安全(intra-object security)。
基于检测重引用的方法
这类方法聚焦于检测UAF利用的第三步,也是实质上对内存时间安全产生危害的UAF操作,如图1的第11行。这类方法的思路类似锁和钥匙,每次分配的内存都会赋予一个锁,并且每个有效的指向该内存的指针也会赋予一个匹配的钥匙。只有相互匹配的锁与钥匙才能进行合法的操作。而当内存重分配以后,对应的锁也就变了。因此,如果重引用悬空指针的话,就可以视为使用一把旧钥匙去开一把新锁,从而被系统检测出来重引用悬空指针这一操作。
基于软件方法实现这一思路的有CETS[17]和Undangle[18]。
CETS,使用基于身份的方案,为每一个指针分配一个标签,并在指针被重引用时检查标签和其分配的区域,若不匹配则内存访问失败,从而避免悬空指针的重引用。为了应对指针运算的情况,CETS使用了污点传播,使得传播的指针继承了原有的指针元数据,但是事实上传播的指针不一定和原来指针指向相同的对象,这就导致这种方法的假阳性比较高。因此,研究者提出了基于污点追踪的方法Undangle,其从指针分配的位置开始跟踪,避免了当指针以类型不安全的形式复制时丢失元数据的情况,从而达到比较好的保护效果。
然而基于软件方法实现这一思路需要在每次内存访问时都要进行一次检测,从而导致性能开销很大,更适合作为调试工具,而非运行时系统的防御。因此研究者开始着力于减小性能开销,从而诞生了基于硬件的实现方法,诸如Watchdog[19]和WatchdogLite[20]。
Watchdog的检测重引用悬空指针操作和CETS相似,主要是在内存访问检测做了很多优化,包括使用微指令注入、元数据编码、ISA辅助识别和寄存器重命名技术。虽然极大地降低了运行性能开销,然而付出的代价是硬件的复杂性过高。于是Watchdog的研究者提出了WatchdogLite这一改进版本。WatchdogLite通过硬件优化来利用编译器检测指针,从而不需要添加任何新的硬件来保存元数据的状态,降低了硬件复杂性同时也保证较低的运行开销。
未来研究方向
表1总结了上述的方法,其内存开销和运行开销都是基于SPEC 2006测试的。下面将对这三类方法进行讨论。
在消除悬垂指针方法中,又可按照具体实现分为基于硬件和基于软件。基于软件的方法通常是维护指针和对象的关系来置空指针,而要维护指针和对象的关系,就需要存储比较复杂的元数据,从而导致了内存开销过大。文中提到的基于硬件的方法都是基于已提出的硬件结构稍加扩展来保护内存时间安全的。虽然性能开销和内存开销都要相对软件方法优越,但能否应用实际存在比较大的考量。
内存分配方法中,实现方式也可以分为硬件和软件。早期的该类方法主要目的是以低开销的方式增加漏洞利用难度。除了Address Sanitizer是作为检测工具,所以牺牲了运行开销。近期研究使用基于页表的技术,兼容性好,但开销稍微大一些。基于硬件的方法主要使用的隔离缓冲区的技术,本质上也是延迟复用已释放内存的区域。这类方法的优势是可以结合其他技术,构成更强的内存安全防护体系。
检测重引用的方法中,通常需要跟踪指针动态。而指针存在很多不确定性,因为其可能被复制多份传播到程序各处。要提高跟踪准确率,就需要相应的开销来维持,否则只能降低开销,来保证部分安全。该类方法和消除悬垂指针的方法也有些相似,都需要追踪指针动态,不同的是两种方法在指针重引用时做的操作不同,前者是做检测,后者是消除悬空指针。从而导致检测重引用的方法主要花费开销在检测上。
总体来看,基于硬件的方法和基于软件的方法在安全上效果相差不大,但硬件方法在减小开销上更胜一筹,而软件方法在兼容性上具有优势。所以未来的研究可以从软硬件协同防御入手,基于兼容性较好的硬件扩展,来完善硬件缺失的安全功能,使得研究能够应用实际。此外,由于UAF漏洞广泛存在于多线程应用中,而这些研究中只有少部分支持多线程应用。因此,今后的内存时间安全研究也应考虑支持多线程。
总结
本文针对悬垂指针引发的UAF漏洞,综述了内存时间安全的研究现状。首先简述如何利用UAF漏洞实施攻击,突出了UAF漏洞利用的三要素。并以此为据,将内存时间安全研究分为三类,论述了这三类方法的基本思路。接着,基于安全、运行开销、兼容性这三点对各方法进行评估,并提出未来内存时间安全可能的研究方向。
参考文献
[1] “2019 CWE Top25 Most Dangerous Software Errors” MITRE, https://cwe.mitre.org/top25/archive/2019/2019_cwe_top25.html, Sept. 2019.
[2] Xu, Wen, et al. “From collision to exploitation: Unleashing use-after-free vulnerabilities in linux kernel.” Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security. 2015.
[3] Szekeres, Laszlo, et al. “Sok: Eternal war in memory.” 2013 IEEE Symposium on Security and Privacy. IEEE, 2013.
[4] Lee, Byoungyoung, et al. “Preventing Use-after-free with Dangling Pointers Nullification.” NDSS. 2015.
[5] Younan, Yves. “FreeSentry: protecting against use-after-free vulnerabilities due to dangling pointers.” NDSS. 2015.
[6] Van Der Kouwe, Erik, Vinod Nigade, and Cristiano Giuffrida. “Dangsan: Scalable use-after-free detection.” Proceedings of the Twelfth European Conference on Computer Systems. 2017.
[7] Liu, Daiping, Mingwei Zhang, and Haining Wang. “A robust and efficient defense against use-after-free exploits via concurrent pointer sweeping.” Proceedings of the 2018 ACM SIGSAC Conference on Computer and Communications Security. 2018.
[8] Zhang, Tong, Dongyoon Lee, and Changhee Jung. “BOGO: buy spatial memory safety, get temporal memory safety (almost) free.” Proceedings of the Twenty-Fourth International Conference on Architectural Support for Programming Languages and Operating Systems. 2019.
[9] Xia, Hongyan, et al. “CHERIvoke: Characterising Pointer Revocation using CHERI Capabilities for Temporal Memory Safety.” Proceedings of the 52nd Annual IEEE/ACM International Symposium on Microarchitecture. 2019.
[10] Watson, Robert NM, et al. “Cheri: A hybrid capability-system architecture for scalable software compartmentalization.” 2015 IEEE Symposium on Security and Privacy. IEEE, 2015.
[11] Akritidis, Periklis. “Cling: A Memory Allocator to Mitigate Dangling Pointers.” USENIX Security Symposium. 2010.
[12] Novark, Gene, and Emery D. Berger. “DieHarder: securing the heap.” Proceedings of the 17th ACM conference on Computer and communications security. 2010.
[13] Serebryany, Konstantin, et al. “AddressSanitizer: A fast address sanity checker.” Presented as part of the 2012 {USENIX} Annual Technical Conference ({USENIX}{ATC} 12). 2012.
[14] Dang, Thurston HY, Petros Maniatis, and David Wagner. “Oscar: A practical page-permissions-based scheme for thwarting dangling pointers.” 26th {USENIX} Security Symposium ({USENIX} Security 17). 2017.
[15] Sinha, Kanad, and Simha Sethumadhavan. “Practical memory safety with REST.” 2018 ACM/IEEE 45th Annual International Symposium on Computer Architecture (ISCA). IEEE, 2018.
[16] Sasaki, Hiroshi, et al. “Practical byte-granular memory blacklisting using califorms.” Proceedings of the 52nd Annual IEEE/ACM International Symposium on Microarchitecture. 2019.
[17] Nagarakatte, Santosh, et al. “CETS: compiler enforced temporal safety for C.” Proceedings of the 2010 international symposium on Memory management. 2010.
[18] Caballero, Juan, et al. “Undangle: early detection of dangling pointers in use-after-free and double-free vulnerabilities.” Proceedings of the 2012 International Symposium on Software Testing and Analysis. 2012.
[19] Nagarakatte, Santosh, Milo MK Martin, and Steve Zdancewic. “Watchdog: Hardware for safe and secure manual memory management and full memory safety.” 2012 39th Annual International Symposium on Computer Architecture (ISCA). IEEE, 2012.
[20] Nagarakatte, Santosh, Milo MK Martin, and Steve Zdancewic. “Watchdoglite: Hardware-accelerated compiler-based pointer checking.” Proceedings of Annual IEEE/ACM International Symposium on Code Generation and Optimization. 2014.
发表评论
您还未登录,请先登录。
登录