漏洞缓解

本章记录了 Rust 编译器支持的漏洞缓解措施,绝不是对 Rust 编程语言安全特性的全面调查。

本章面向使用 Rust 编程语言的软件工程师,并假设读者已具备 Rust 编程语言及其工具链的先验知识。

简介

Rust 编程语言通过其所有权[3]、引用和借用[4]以及切片类型[5]功能提供内存[1]和线程[2]安全保证。但是,不安全 Rust[6]引入了不安全块、不安全函数和方法、不安全特征以及不受借用规则约束的新类型。

Rust 标准库的部分内容是作为对不安全代码的安全抽象实现的(历史上曾容易受到内存损坏[7]的影响)。此外,Rust 代码和文档鼓励在不安全代码之上创建安全抽象。如果不对不安全代码进行适当的审查和测试,这可能会造成一种虚假的安全感。

不安全 Rust 引入了不提供相同内存和线程安全保证的功能。这会导致程序或库容易受到内存损坏(CWE-119)[8]和并发问题(CWE-557)[9]的影响。现代 C 和 C++ 编译器提供漏洞缓解措施,以增加利用这些问题导致的漏洞的难度。因此,Rust 编译器也必须支持这些漏洞缓解措施,以减轻使用不安全 Rust 导致的漏洞。本章记录了这些漏洞缓解措施及其在 Rust 中的应用方式。

本章不讨论这些漏洞缓解措施的有效性,因为它们在很大程度上取决于除设计和实现之外的几个因素,而是描述它们的作用,以便在给定上下文中理解它们的有效性。

漏洞缓解措施

本节记录了在 AMD64 架构和等效架构上为 Linux 操作系统构建程序时适用于 Rust 编译器的漏洞缓解措施。1 本节中的所有示例均使用 Debian 测试版上的 Rust 编译器 nightly 版本构建。

Rust 编程语言目前没有规范。Rust 编译器(即 rustc)是语言的参考实现。本章中所有关于“Rust 编译器”的引用都指语言的参考实现。

表 I

在 AMD64 架构和等效架构上为 Linux 操作系统构建程序时,Rust 编译器支持的漏洞缓解措施摘要。

漏洞缓解措施默认支持并启用
位置无关可执行文件0.12.0 (2014-10-09)
整数溢出检查是(在启用调试断言时启用,在禁用调试断言时禁用)1.1.0 (2015-06-25)
不可执行内存区域1.8.0 (2016-04-14)
堆栈冲突保护1.20.0 (2017-08-31)
只读重定位和立即绑定1.21.0 (2017-10-12)
堆损坏保护1.32.0(2019-01-17)(通过操作系统默认值或指定分配器)
堆栈粉碎保护Nightly
前向边控制流保护Nightly
后向边控制流保护(例如,影子堆栈和安全堆栈)Nightly

1. 有关目标及其默认选项的列表,请参见 https://github.com/rust-lang/rust/tree/master/compiler/rustc_target/src/spec

位置无关可执行文件

位置无关可执行文件通过为可执行文件生成位置无关代码,并指示动态链接器以类似于共享对象的方式在随机加载地址加载它,从而增加了使用代码重用利用技术(例如,面向返回编程 (ROP) 及其变体)的难度,从而也受益于地址空间布局随机化 (ASLR)。这也称为“完全 ASLR”。

Rust 编译器支持位置无关可执行文件,并从 0.12.0 版本(2014-10-09)[10]–[13]开始默认启用它。

$ readelf -h target/release/hello-rust | grep Type:
  Type:                              DYN (Shared object file)

图 1. 检查可执行文件是否为位置无关可执行文件。

对象类型为 ET_DYN(即共享对象)而不是 ET_EXEC(即可执行文件)的可执行文件是位置无关可执行文件(参见图 1)。

整数溢出检查

整数溢出检查通过检查有符号和无符号整数计算的结果是否无法在其类型中表示(导致溢出或环绕),从而保护程序免受未定义和意外行为(这可能会导致漏洞)的影响。

Rust 编译器支持整数溢出检查,并从 1.0.0 版本(2015-05-15)[14]–[17]开始在启用调试断言时启用它,但直到 1.1.0 版本(2015-06-25)[16]才完成对它的支持。后来在 1.17.0 版本(2017-04-27)[18]–[20]中稳定了一个控制整数溢出检查的选项。

fn main() {
    let u: u8 = 255;
    println!("u: {}", u + 1);
}

图 2. hello-rust-integer 程序。

$ cargo run
   Compiling hello-rust-integer v0.1.0 (/home/rcvalle/hello-rust-integer)
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s
     Running `target/debug/hello-rust-integer`
thread 'main' panicked at 'attempt to add with overflow', src/main.rs:3:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

图 3. 使用启用调试断言的 hello-rust-integer 进行构建和执行。

$ cargo run --release
   Compiling hello-rust-integer v0.1.0 (/home/rcvalle/hello-rust-integer)
    Finished release [optimized] target(s) in 0.23s
     Running `target/release/hello-rust-integer`
u: 0

图 4. 使用禁用调试断言的 hello-rust-integer 进行构建和执行。

在启用调试断言时启用整数溢出检查(参见图 3),在禁用调试断言时禁用整数溢出检查(参见图 4)。要独立启用整数溢出检查,请使用控制整数溢出检查的选项、作用域属性或显式检查方法(例如 checked_add2)。

建议在需要环绕语义时使用显式环绕方法(例如 wrapping_add),并在使用不安全 Rust 时始终使用显式检查和环绕方法。

2. 有关 checked、overflowing、saturating 和 wrapping 方法的更多信息(以 u32 为例),请参见 u32 文档

不可执行内存区域

不可执行内存区域通过限制可用于执行任意代码的内存区域,增加了利用的难度。大多数现代处理器都提供对操作系统支持,以将内存区域标记为不可执行,但以前它是由软件模拟的,例如在 grsecurity/PaX 的 PAGEEXECSEGMEXEC 中,在不支持它的处理器上。这也称为“不可执行 (NX) 位”、“禁用执行 (XD) 位”、“从不执行 (XN) 位”等。

Rust 编译器支持不可执行内存区域,并从其初始版本 0.1(2012-01-20)[21]、[22]开始默认启用它,但此后已出现回归[23]–[25],并从 1.8.0 版本(2016-04-14)[25]开始默认强制执行。

$ readelf -l target/release/hello-rust | grep -A 1 GNU_STACK
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10

图 5. 检查给定二进制文件是否启用了不可执行内存区域。

程序头表中存在类型为 PT_GNU_STACK 的元素,且 PF_X(即可执行)标志未设置,表示给定二进制文件启用了不可执行内存区域3。相反,程序头表中存在类型为 PT_GNU_STACK 的元素,且 PF_X 标志已设置,或者程序头表中不存在类型为 PT_GNU_STACK 的元素,表示给定二进制文件未启用不可执行内存区域。

3. 有关它如何影响除堆栈之外的其他内存区域的更多信息,请参见附录部分。

堆栈冲突保护

堆栈冲突保护通过在堆栈增长时读取堆栈页来保护堆栈与另一个内存区域重叠,从而在尝试读取保护页/区域时导致页错误,从而防止堆栈与另一个内存区域重叠——允许彼此覆盖两个区域中的任意数据。这也称为“堆栈探测”或“堆栈探测”。

Rust 编译器通过堆栈探测支持堆栈冲突保护,并从 1.20.0 版(2017-08-31)[26]–[29] 开始默认启用它。

fn main() {
    let v: [u8; 16384] = [1; 16384];
    let first = &v[0];
    println!("The first element is: {first}");
}

图 6. hello-rust-stack-probe-1 程序。

IDA Pro 屏幕截图,列出了修改后的 hello-rust 中的“展开循环”堆栈探测变体。 图 7. 修改后的 hello-rust 中的“展开循环”堆栈探测变体。

fn main() {
    let v: [u8; 65536] = [1; 65536];
    let first = &v[0];
    println!("The first element is: {first}");
}

图 8. hello-rust-stack-probe-2 程序。

IDA Pro 屏幕截图,列出了修改后的 hello-rust 中的“标准循环”堆栈探测变体。 图 9. 修改后的 hello-rust 中的“标准循环”堆栈探测变体。

要检查是否为给定二进制文件启用了堆栈冲突保护,请在堆栈大小大于页面大小的函数的前导部分查找两种堆栈探测变体中的任何一个(参见图 6-9)。

只读重定位和立即绑定

只读重定位通过将这些段标记为只读来保护包含重定位和重定位信息的段(即 .init_array.fini_array.dynamic.got)不被覆盖。这也称为“部分 RELRO”。

Rust 编译器支持只读重定位,并从 1.21.0 版(2017-10-12)[30]、[31] 开始默认启用它。

$ readelf -l target/release/hello-rust | grep GNU_RELRO
  GNU_RELRO      0x000000000002ee00 0x000000000002fe00 0x000000000002fe00

图 9. 检查是否为给定二进制文件启用了只读重定位。

程序头表中存在类型为 PT_GNU_RELRO 的元素表示为给定二进制文件启用了只读重定位(参见图 9)。相反,程序头表中不存在类型为 PT_GNU_RELRO 的元素表示为给定二进制文件未启用只读重定位。

立即绑定通过指示动态链接器在启动期间将控制权转移到程序之前执行所有重定位来保护包含重定位的附加段(即 .got.plt)不被覆盖,因此所有包含重定位的段都可以标记为只读(与只读重定位结合使用)。这也称为“完整 RELRO”。

Rust 编译器支持立即绑定,并从 1.21.0 版(2017-10-12)[30]、[31] 开始默认启用它。

$ readelf -d target/release/hello-rust | grep BIND_NOW
 0x000000000000001e (FLAGS)              BIND_NOW

图 10. 检查是否为给定二进制文件启用了立即绑定。

动态部分中存在带有 DT_BIND_NOW 标签和 DF_BIND_NOW 标志4 的元素表示为给定二进制文件启用了立即绑定(参见图 10)。相反,动态部分中不存在带有 DT_BIND_NOW 标签和 DF_BIND_NOW 标志的元素表示为给定二进制文件未启用立即绑定。

程序头表中存在类型为 PT_GNU_RELRO 的元素,以及动态部分中存在带有 DT_BIND_NOW 标签和 DF_BIND_NOW 标志的元素,表示为给定二进制文件启用了完整 RELRO(参见图 9-10)。

4. 以及某些链接编辑器中的 DF_1_NOW 标志。

堆损坏保护

堆损坏保护通过执行一些检查来保护动态分配的内存,例如检查列表元素之间损坏的链接、无效指针、无效大小、对同一分配内存的双重/多次“释放”以及这些情况的许多极端情况。这些检查是特定于实现的,并且因分配器而异。

ARM 内存标记扩展 (MTE)(如果可用)将通过标记内存分配并自动检查每次内存访问是否使用正确的标记来提供硬件协助,以概率方式缓解内存安全违规。

Rust 的默认分配器历史上一直是 jemalloc,它长期以来一直是问题的根源,也是许多讨论的主题[32]–[38]。因此,它已被从默认分配器中移除,取而代之的是操作系统标准 C 库默认分配器5,从 1.32.0 版(2019-01-17)[39] 开始。

fn main() {
    let mut x = Box::new([0; 1024]);

    for i in 0..1026 {
        unsafe {
            let elem = x.get_unchecked_mut(i);
            *elem = 0x4141414141414141u64;
        }
    }
}

图 11. hello-rust-heap 程序。

$ cargo run
   Compiling hello-rust-heap v0.1.0 (/home/rcvalle/hello-rust-heap)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/hello-rust-heap`
free(): invalid next size (normal)
Aborted

图 12. 使用启用的调试断言构建和执行 hello-rust-heap。

$ cargo run --release
   Compiling hello-rust-heap v0.1.0 (/home/rcvalle/hello-rust-heap)
    Finished release [optimized] target(s) in 0.25s
     Running `target/release/hello-rust-heap`
free(): invalid next size (normal)
Aborted

图 13. 使用禁用的调试断言构建和执行 hello-rust-heap。

使用默认分配器(即 GNU 分配器)时会执行堆损坏检查(参见图 12-13)。

5. Linux 的标准 C 库默认分配器是 GNU 分配器,它源自 Wolfram Gloger 的 ptmalloc(pthreads malloc),而 ptmalloc 又源自 Doug Lea 的 dlmalloc(Doug Lea malloc)。

堆栈粉碎保护

堆栈粉碎保护通过在局部变量和保存的返回指令指针之间插入一个随机保护值,并在从函数返回时检查该值是否已更改,来保护程序免受基于堆栈的缓冲区溢出。这也称为“堆栈保护器”或“堆栈粉碎保护器 (SSP)”。

Rust 编译器在 nightly 构建[40] 中支持堆栈粉碎保护。

IDA Pro 屏幕截图,列出了 hello-rust 中对 __stack_chk_fail 的交叉引用。 图 14. IDA Pro 列出了 hello-rust 中对 __stack_chk_fail 的交叉引用。

要检查是否为给定二进制文件启用了堆栈粉碎保护,请搜索对 __stack_chk_fail 的交叉引用(参见图 14)。

前向边控制流保护

前向边控制流保护通过执行检查以确保间接分支的目标是其控制流图中的有效目标之一,来保护程序免受其控制流更改/劫持。这些检查的全面性因实现而异。这也称为“前向边控制流完整性 (CFI)”。

较新的处理器为前向边控制流保护提供了硬件协助,例如 ARM 分支目标识别 (BTI)、ARM 指针身份验证和英特尔间接分支跟踪 (IBT),作为英特尔控制流强制技术 (CET) 的一部分。但是,基于 ARM BTI 和英特尔 IBT 的实现不如基于软件的实现(如 LLVM ControlFlowIntegrity (CFI) 和市售的 grsecurity/PaX 重用攻击保护器 (RAP))全面。

Rust 编译器在 nightly 构建[41]-[42] 6 中支持前向边控制流保护。

$ readelf -s -W target/release/hello-rust | grep "\.cfi"
     5: 0000000000006480   657 FUNC    LOCAL  DEFAULT   15 _ZN10hello_rust4main17h4e359f1dcd627c83E.cfi

图 15. 检查是否为给定二进制文件启用了 LLVM CFI。

存在以“.cfi”为后缀的符号或 __cfi_init 符号(以及对 __cfi_check 的引用)表示为给定二进制文件启用了 LLVM CFI(即前向边控制流保护)。相反,不存在以“.cfi”为后缀的符号或 __cfi_init 符号(以及对 __cfi_check 的引用)表示为给定二进制文件未启用 LLVM CFI(参见图 15)。

6. 它还在 Windows 上支持控制流保护 (CFG)(参见 https://github.com/rust-lang/rust/issues/68793)。

后向边控制流保护

影子堆栈通过在单独的(影子)堆栈上存储它们的副本,并在从函数返回时使用这些副本作为权威值来保护保存的返回指令指针不被覆盖。这也称为“ShadowCallStack”和“返回流保护”,被认为是后向边控制流保护(或“后向边 CFI”)的实现。

安全堆栈不仅保护保存的返回指令指针,还保护寄存器溢出和一些局部变量不被覆盖,方法是将不安全的变量(例如大型数组)存储在单独的(不安全)堆栈上,并在单独的堆栈上使用这些不安全的变量。这也称为“SafeStack”,也被认为是后向边控制流保护的实现。

影子堆栈和安全堆栈都旨在成为堆栈粉碎保护的更全面的替代方案,因为它们可以保护保存的返回指令指针(以及安全堆栈中的其他数据)免受任意写入和非线性越界写入。

较新的处理器为后向边控制流保护提供了硬件协助,例如 ARM 指针身份验证和英特尔影子堆栈,作为英特尔 CET 的一部分。

Rust 编译器在 nightly 构建[43]-[44] 中支持 AArch64 架构7 的影子堆栈,还在 nightly 构建[45]-[46] 中支持安全堆栈。

$ readelf -s target/release/hello-rust | grep __safestack_init
   678: 0000000000008c80   426 FUNC    GLOBAL DEFAULT   15 __safestack_init

图 16. 检查是否为给定二进制文件启用了 LLVM SafeStack。

存在 __safestack_init 符号表示为给定二进制文件启用了 LLVM SafeStack。相反,不存在 __safestack_init 符号表示为给定二进制文件未启用 LLVM SafeStack(参见图 16)。

7. 由于性能和安全问题,LLVM 中针对 AMD64 架构的影子堆栈实现已被移除。

附录

根据最新版本的 Linux 标准基础 (LSB) 核心规范PT_GNU_STACK 程序头指示堆栈是否应该可执行,而该程序头的缺失则表示堆栈应该可执行。但是,Linux 内核目前在加载任何带有 PT_GNU_STACK 程序头和 PF_X 标志设置的或没有该程序头的可执行文件时,都会设置 READ_IMPLIES_EXEC 个性,这会导致不仅堆栈,而且所有可读的虚拟内存映射都可执行。

在 2012 年 曾尝试修复此问题,并在 2020 年 再次尝试修复。前者从未成功,后者部分修复了问题,但引入了其他问题——在某些架构(如 IA-32 及等效架构)中,PT_GNU_STACK 程序头的缺失仍然会导致不仅堆栈,而且所有可读的虚拟内存映射都可执行(或者在某些架构(如 AMD64 及等效架构)中会导致堆栈不可执行,这与 LSB 相矛盾)。

READ_IMPLIES_EXEC 个性需要通过单独的选项与 PT_GNU_STACK 程序头完全分离(或者在需要 READ_IMPLIES_EXEC 时可以使用 setarch -X),并且 PT_GNU_STACK 程序头的缺失需要具有更安全的默认值(与 READ_IMPLIES_EXEC 无关)。

参考资料

  1. D. Hosfelt。 “无所畏惧的安全:内存安全”。Mozilla Hacks。 https://hacks.mozilla.org/2019/01/fearless-security-memory-safety/.

  2. D. Hosfelt。 “无所畏惧的安全:线程安全”。Mozilla Hacks。 https://hacks.mozilla.org/2019/02/fearless-security-thread-safety/.

  3. S. Klabnik 和 C. Nichols。 “什么是所有权?”。Rust 编程语言。 https://doc.rust-lang.net.cn/book/ch04-01-what-is-ownership.html.

  4. S. Klabnik 和 C. Nichols。 “引用和借用”。Rust 编程语言。 https://doc.rust-lang.net.cn/book/ch04-02-references-and-borrowing.html.

  5. S. Klabnik 和 C. Nichols。 “切片类型”。Rust 编程语言。 https://doc.rust-lang.net.cn/book/ch04-03-slices.html.

  6. S. Klabnik 和 C. Nichols。 “不安全 Rust”。Rust 编程语言。 https://doc.rust-lang.net.cn/book/ch19-01-unsafe-rust.html.

  7. S. Davidoff。 “Rust 的标准库是如何在多年内存在漏洞而无人察觉的”。Medium。 https://medium.com/@shnatsel/how-rusts-standard-library-was-vulnerable-for-years-and-nobody-noticed-aebf0503c3d6.

  8. “内存缓冲区内操作限制不当 (CWE-119)”。MITRE CWE 列表。 https://cwe.mitre.org/data/definitions/119.html.

  9. “并发问题 (CWE-557)”。MITRE CWE 列表。 https://cwe.mitre.org/data/definitions/557.html.

  10. K. McAllister。 “内存漏洞缓解 #15179”。GitHub。 https://github.com/rust-lang/rust/issues/15179.

  11. K. McAllister。 “RFC:内存漏洞缓解 #145”。GitHub。 https://github.com/rust-lang/rfcs/pull/145.

  12. K. McAllister。 “RFC:内存漏洞缓解”。GitHub。 https://github.com/kmcallister/rfcs/blob/hardening/active/0000-memory-exploit-mitigation.md.

  13. D. Micay。 “在 Linux 上默认启用 PIE 以实现完整的 ASLR #16340”。GitHub。 https://github.com/rust-lang/rust/pull/16340.

  14. N. Matsakis。 “整数溢出 #560”。GitHub。 https://github.com/rust-lang/rfcs/pull/560.

  15. G. Lehel 和 N. Matsakis。 “整数溢出”。GitHub。 https://rust-lang.github.io/rfcs/0560-integer-overflow.html.

  16. A. Turon。 “整数溢出 (RFC 560) 的跟踪问题 #22020”。GitHub。 https://github.com/rust-lang/rust/issues/22020.

  17. H. Wilson。 “关于 Rust 中整数溢出的神话和传说”。Huon 在互联网上。 http://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/.

  18. B. Anderson。 “稳定 -C 溢出检查 #1535”。GitHub。 https://github.com/rust-lang/rfcs/pull/1535.

  19. B. Anderson。 “稳定的溢出检查”。GitHub。 https://github.com/brson/rfcs/blob/overflow/text/0000-stable-overflow-checks.md.

  20. N. Froyd。 “添加 -C 溢出检查选项 #40037”。GitHub。 https://github.com/rust-lang/rust/pull/40037.

  21. R. Á. de Espíndola。 “rustc 需要可执行堆栈 #798”。GitHub。 https://github.com/rust-lang/rust/issues/798.

  22. A. Seipp。 “确保 librustrt.so 与不可执行堆栈链接。#1066”。GitHub。 https://github.com/rust-lang/rust/pull/1066.

  23. D. Micay。 “Rust 二进制文件不应该具有可执行堆栈 #5643”。GitHub。 https://github.com/rust-lang/rust/issues/5643.

  24. D. Micay。 “将汇编对象堆栈标记为不可执行 #5647”。GitHub。 https://github.com/rust-lang/rust/pull/5647.

  25. A. Clark。 “在 linux 和 bsd 上显式禁用堆栈执行 #30859”。GitHub。 https://github.com/rust-lang/rust/pull/30859.

  26. Zoxc。 “用堆栈探测替换堆栈溢出检查 #16012”。GitHub。 https://github.com/rust-lang/rust/issues/16012.

  27. A. Crichton。 “rustc:为 x86 实现堆栈探测 #42816”。GitHub。 https://github.com/rust-lang/rust/pull/42816.

  28. A. Crichton。 “添加 __rust_probestack 内在函数 #175”。GitHub。 https://github.com/rust-lang/compiler-builtins/pull/175.

  29. S. Guelton、S. Ledru、J. Stone。 “将堆栈冲突保护带到 Clang/X86——开源方式”。LLVM 项目博客。 https://blog.llvm.org/posts/2021-01-05-stack-clash-protection/.

  30. B. Anderson。 “考虑默认应用 -Wl,-z,relro 或 -Wl,-z,relro,-z,now #29877”。GitHub。 https://github.com/rust-lang/rust/issues/29877.

  31. J. Löthberg。 “添加对完整 RELRO 的支持 #43170”。GitHub。 https://github.com/rust-lang/rust/pull/43170.

  32. N. Matsakis。 “Rust 中的分配器”。Baby Steps。 http://smallcultfollowing.com/babysteps/blog/2014/11/14/allocators-in-rust/.

  33. A. Crichton。 “RFC:允许更改默认分配器 #1183”。GitHub。 https://github.com/rust-lang/rfcs/pull/1183.

  34. A. Crichton。 “RFC:替换 jemalloc”。GitHub。 https://rust-lang.github.io/rfcs/1183-swap-out-jemalloc.html.

  35. A. Crichton。 “更改全局默认分配器 (RFC 1974) 的跟踪问题 #27389”。GitHub。 https://github.com/rust-lang/rust/issues/27389.

  36. S. Fackler。 “为稳定准备全局分配器 #1974”。GitHub。 https://github.com/rust-lang/rfcs/pull/1974.

  37. A. Crichton。 “RFC:全局分配器”。GitHub。 https://rust-lang.github.io/rfcs/1974-global-allocators.html.

  38. B. Anderson。 “将默认全局分配器切换到 System,删除 alloc_jemalloc,在 rustc 中使用 jemallocator #36963”。GitHub。 https://github.com/rust-lang/rust/issues/36963.

  39. A. Crichton。 “删除 alloc_jemalloc crate #55238”。GitHub。 https://github.com/rust-lang/rust/pull/55238.

  40. bbjornse。 “添加使用 LLVM 堆栈粉碎保护的代码生成选项 #84197”。GitHub。 https://github.com/rust-lang/rust/pull/84197

  41. R. de C. Valle。 “LLVM 控制流完整性 (CFI) 对 Rust 支持的跟踪问题 #89653”。GitHub。 https://github.com/rust-lang/rust/issues/89653.

  42. “ControlFlowIntegrity”。Rust 不稳定手册。 https://doc.rust-lang.net.cn/unstable-book/compiler-flags/sanitizer.html#controlflowintegrity.

  43. I. Lozano。 “添加对 LLVM ShadowCallStack 的支持 #98208”。GitHub。 https://github.com/rust-lang/rust/pull/98208.

  44. “ShadowCallStack”。Rust 不稳定手册。 https://doc.rust-lang.net.cn/unstable-book/compiler-flags/sanitizer.html#shadowcallstack.

  45. W. Wiser。 “添加对 LLVM SafeStack 的支持 #112000” GitHub。 https://github.com/rust-lang/rust/pull/112000

  46. “SafeStack”。Rust 不稳定手册。 https://doc.rust-lang.net.cn/org/unstable-book/compiler-flags/sanitizer.html#safestack.