内存安全
内存安全(Memory safety)是在存取存储器时,不会出现像是缓冲区溢出或是迷途指针等,和存储器有关的程序错误或漏洞[1]。像Java语言的执行时期错误检测,会检查数组存取时的索引范围,以及指针的解引用(dereference),因此是内存安全的语言[1]。而C语言和C++的指针可以进行许多的指针运算,存取存储器时也不会进行边界检查,因此是存储器不安全的语言[2]。
历史
内存安全一开始是在资源管理及分时系统下考量,目的是为了避免像是Fork炸弹之类的问题[3]。最初的研究大部分都是纯理论的,直到后来莫里斯蠕虫出现,此蠕虫在finger协议中造成了缓冲区溢出[4]。此后电脑安全的领域快速发展,之后像是Return-to-libc攻击等大量新的网络攻击手法不断的升级,而防御机制也持续升级,例如非执行堆栈[5]及地址空间配置随机加载(ASLR)。随机化避免了大部分缓冲区溢出攻击,攻击者需要用heap spraying或是其他和应用程式有关的方式才能获取地址,只是其采用的速度还不快[4]。而这类防御机制的应用只限在函数库位置以及堆栈位置的随机化。
影响
微软的安全工程师曾在2019年提出,安全漏洞中有70%是因为内存安全的问题所造成[6]。2020年时,有一个Google的团队也提出Google Chromium的“严重安全问题”中,有70%是因为内存安全问题造成。许多备受关注的漏洞以及关键软件的漏洞利用也是因为缺乏内存安全,这些漏洞包括心脏出血漏洞[7],以及为时已久,在Sudo中权限提升的错误[8]。内存安全问题带来的漏洞及漏洞利用如此普遍,而且相当严重,许多软件安全研究者认为在程序中发现内存安全问题,是像“瓮中捉鳖”一样简单的事[9]。
作法
大部分高级编程语言本来就有内存安全的特性,不过只检查本身的代码,不会检查与其交互的系统,因此不是完整的内存安全。在预防内存安全问题的对策中,最常见的是使用垃圾回收功能的自动存储器管理功能,此作法可以避免执行时配置资料出现use-after-free的问题[10]。若再结合数组存取的边界检查,以及不支持原生的指针运算,垃圾回收可以有效确保内存安全性(不过若配合像是像是外部函数调用等,已视为不安全的低端运算,其内存安全性会比较弱)。其缺点是会影响性能,因此有些要求性能的任务关键应用程式,就无法使用此功能[1]。
使用手动存储器管理的编程语言,一般无法在执行时确保内存安全性。需要透过静态程序分析、自动化定理证明,或是程序开发者在程序执行时的小心管理,才能确保内存安全性[10]。像Rust编程语言就用借用检查器(borrow checker)来确保内存安全性[11],而C语言或C++语言则不保证内存安全性。用C语言和C++语言撰写的软件很多,因此也就有许多外部存储器分析工具:像Coverity有C语言的静态存储器分析[12]。
DieHard[13]及Allinea Distributed Debugging Tool有特别的heap分配器,将物件分配在随机的虚拟内存页中,不合法的存储器读写就会停止程序。,此保护是用硬件存储器保护为基础,额外的运算量不大,不过若大量的使用heap分配,运算量会显著增加[14]。用随机化来避免存储器错误,仍然有几率会出错,不过此作法很容易在已有的软件实现(例如使用relinking二进制码)。
Valgrind的memcheck工具使用指令集模拟器,在有存储器检查的虚拟机中执行编译的程序,可以检测一部分的执行时存储器问题,不过会让程序执行速度只有原来的1/40[15],而且若有自定的存储器配置器,需明确告知[16][17]。
若在分析时可以存取原始码,有函数库可以搜集并追踪指针的合法值(metadata),并且将指针存取的位置和metadata比对,看位置是否有效,像贝姆垃圾回收器(Boehm garbage collector)就是一个例子[18]。一般而言,内存安全可以用跟踪垃圾回收(tracing garbage collection)以及在每一次存取时插入执行时的检查来确保:此作法会有额外的运算量,但是比Valgrind要好。所有有垃圾自动的语言都使用此一作法[1]。若是C语言或是C++语言,有工具可以在编辑时进行转换,以在执行时进行内存安全检查,像是CheckPointer[19]及Code sanitizer,程序执行速度约是原来的一半。 Address Sanitizer(ASAN),可以在程序运行时,对堆积、堆栈、全局变量、动态存储器分配等错误进行实时检测。[20],这类工具在模糊测试用于检测程序是否进入异常状态。
ARM架构指令集实现了指针验证(Pointer Authentication, PAC)指令,可以对指针进行签名,在使用指针前验证指针是否受到窜改,因此攻击者需要计算出正确的 PAC 才能进行利用。[21][22]
存储器错误的种类
- 存取错误:存储器的读取或写入无效
- 缓冲区溢出:写入的资料量超过缓冲区范围,会修改到邻近的物件或是内部资料(像是堆栈中的控制资料或是调用堆栈中的返回位置。)
- 缓冲区过读:读取的资料量超过缓冲区范围,可能会读到敏感资料,也可能有助于骇客避开地址空间配置随机加载。
- 竞争危害:二个或多个程序同时读取及写入共享存储器。
- 无效页缺失:存取到虚拟地址空间中,没有加载物理内存中的一个标签页。大部分的环境,存取空指针指向的内容常常会造成例外或是程序中止执行,不过若在没有存储器保护的内核或操作系统,或是该空指针的使用有很多负面的影响,那么无效页缺失会破坏系统。
- 使用已释放存储器(UseAfter Free):在物件已删除后,依其物件的位置存取资料(指向这种位置的指针称为迷途指针)。
- 未初始化变量:使用了未初始化的变量,其中可能有不想要的资料,有些语言中则会是受损的资料。
- 内存泄漏:存储器管理不当,无法追踪到特定存储器,或是追踪到的内容不正确。
- 堆栈溢出:程序将调用堆栈用完,原因多半是因为递归控制不当,或是调用堆栈配置不足。一般来说堆栈保护页(guard page)会中止程序执行,避免存储器破坏,不过若函数的stack frame很大,可能会跳过该页面。
- 存储器用尽:程序想要配置存储器的大小超过系统可提供的量。在有些编程语言里,需要在每次动态配置存储器后另外进行检查。
- 双重释放(Double free):对已经释放的存储器再度释放,若该位置已配置了其他的物件,会错误的释放其他的物件。若该位置尚未被使用,可能会出现其他的问题,特别是使用自由表的分配器(allocators)。
- 无效释放(Invalid free):试图释放无效的位置,可能会破坏Heap存储器。
- 不匹配的释放(Mismatched free):使用多个分配器,试图用其他分配器的解分配函数(deallocation function)来释放存储器[25]。
- 非预期的别名:同一个存储器同时被二个不同用途的函数配置及修改,
参考资料
- ^ 1.0 1.1 1.2 1.3 Dhurjati, Dinakar; Kowshik, Sumant; Adve, Vikram; Lattner, Chris. Memory Safety Without Runtime Checks or Garbage Collection (PDF). Proceedings of the 2003 ACM SIGPLAN Conference on Language, Compiler, and Tool for Embedded Systems (ACM). 1 January 2003: 69–80 [13 March 2017]. ISBN 1581136471. S2CID 1459540. doi:10.1145/780732.780743. (原始内容存档 (PDF)于2023-04-09) (英语).
- ^ Akritidis, Periklis. Practical memory safety for C (PDF). Technical Report - University of Cambridge. Computer Laboratory (University of Cambridge, Computer Laboratory). June 2011 [13 March 2017]. ISSN 1476-2986. UCAM-CL-TR-798. (原始内容存档 (PDF)于2023-05-26).
- ^ Anderson, James P. Computer Security Planning Study (PDF) 2. Electronic Systems Center. [2023-04-09]. ESD-TR-73-51. (原始内容存档 (PDF)于2023-08-22).
- ^ 4.0 4.1 van der Veen, Victor; dutt-Sharma, Nitish; Cavallaro, Lorenzo; Bos, Herbert. Memory Errors: The Past, the Present, and the Future (PDF). Lecture Notes in Computer Science. 2012, 7462 (RAID 2012): 86–106 [13 March 2017]. ISBN 978-3-642-33337-8. doi:10.1007/978-3-642-33338-5_5. (原始内容存档 (PDF)于2016-06-26).
- ^ Wojtczuk, Rafal. Defeating Solar Designer's Non-executable Stack Patch. insecure.org. [13 March 2017]. (原始内容存档于2023-02-16).
- ^ Microsoft: 70 percent of all security bugs are memory safety issues. ZDNET. [21 September 2022]. (原始内容存档于2023-09-29) (英语).
- ^ CVE-2014-0160. Common Vulnerabilities and Exposures. Mitre. [8 February 2018]. (原始内容存档于24 January 2018) (英语).
- ^ Goodin, Dan. Serious flaw that lurked in sudo for 9 years hands over root privileges. Ars Technica. 4 February 2020 [2023-04-12]. (原始内容存档于2022-05-07) (美国英语).
- ^ Fish in a Barrel. fishinabarrel.github.io. [21 September 2022]. (原始内容存档于2023-05-23).
- ^ 10.0 10.1 Crichton, Will. CS 242: Memory safety. stanford-cs242.github.io. [2022-09-22]. (原始内容存档于2022-09-22).
- ^ References. The Rustonomicon. Rust.org. [2017-03-13]. (原始内容存档于2023-05-12) (英语).
- ^ Bessey, Al; Engler, Dawson; Block, Ken; Chelf, Ben; Chou, Andy; Fulton, Bryan; Hallem, Seth; Henri-Gros, Charles; Kamsky, Asya; McPeak, Scott. A few billion lines of code later. Communications of the ACM. 2010-02-01, 53 (2): 66–75 [2017-03-14]. doi:10.1145/1646353.1646374 . (原始内容存档于2017-06-07).
- ^ Berger, Emery D.; Zorn, Benjamin G. DieHard: Probabilistic Memory Safety for Unsafe Languages (PDF). Proceedings of the 27th ACM SIGPLAN Conference on Programming Language Design and Implementation (ACM). 2006-01-01: 158–168 [2017-03-14]. S2CID 8984358. doi:10.1145/1133981.1134000. (原始内容存档 (PDF)于2012-02-05) (英语).
- ^ Memory Debugging in Allinea DDT. (原始内容存档于2015-02-03).
- ^ Gyllenhaal, John. Using Valgrind's Memcheck Tool to Find Memory Errors and Leaks. computing.llnl.gov. [2017-03-13]. (原始内容存档于2018-11-07).
- ^ Memcheck: a memory error detector. Valgrind User Manual. valgrind.org. [2017-03-13]. (原始内容存档于2023-09-28) (英语).
- ^ Kreinin, Yossi. Why custom allocators/pools are hard. Proper Fixation. [2017-03-13]. (原始内容存档于2023-05-12).
- ^ Using the Garbage Collector as Leak Detector. www.hboehm.info. [2017-03-14]. (原始内容存档于2022-10-13) (美国英语).
- ^ Semantic Designs: CheckPointer compared to other safety checking tools. www.semanticdesigns.com. Semantic Designs, Inc. [2023-06-18]. (原始内容存档于2023-06-05).
- ^ Konstantin Serebryany; Derek Bruening and Alexander Potapenko and Dmitriy Vyukov. AddressSanitizer: A Fast Address Sanity Checker. USENIX ATC '12. 2012.
- ^ ARM pointer authentication. lwn.net. Jonathan Corbet. [2024-07-17].
- ^ qualcomm. Pointer Authentication on ARMv8.3 Design and Analysis of the New Software Security Instructions (PDF). qualcomm.com.
- ^ Gv, Naveen. How to Avoid, Find (and Fix) Memory Errors in your C/C++ Code. Cprogramming.com. [13 March 2017]. (原始内容存档于2023-04-18).
- ^ CWE-633: Weaknesses that Affect Memory. Community Weakness Enumeration. MITRE. [13 March 2017]. (原始内容存档于2023-04-15) (英语).
- ^ CWE-762: Mismatched Memory Management Routines. Community Weakness Enumeration. MITRE. [13 March 2017]. (原始内容存档于2023-05-30) (英语).