Skip to content

Commit c5708ca

Browse files
committed
Add documentation for LLVM CFI support
This commit adds initial documentation for LLVM Control Flow Integrity (CFI) support to the Rust compiler (see #89652 and #89653).
1 parent 5d30e93 commit c5708ca

File tree

2 files changed

+202
-19
lines changed

2 files changed

+202
-19
lines changed

src/doc/rustc/src/exploit-mitigations.md

+23-17
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ equivalent.
123123
<tr>
124124
<td>Forward-edge control flow protection
125125
</td>
126-
<td>No
126+
<td>Yes
127127
</td>
128-
<td>
128+
<td>Nightly
129129
</td>
130130
</tr>
131131
<tr>
@@ -465,24 +465,27 @@ implementations such as [LLVM ControlFlowIntegrity
465465
commercially available [grsecurity/PaX Reuse Attack Protector
466466
(RAP)](https://grsecurity.net/rap_faq).
467467

468-
The Rust compiler does not support forward-edge control flow protection on
469-
Linux<sup id="fnref:6" role="doc-noteref"><a href="#fn:6"
470-
class="footnote">6</a></sup>. There is work currently ongoing to add support
471-
for the [sanitizers](https://github.com/google/sanitizers)[40], which may or
472-
may not include support for LLVM CFI.
468+
The Rust compiler supports forward-edge control flow protection on nightly
469+
builds[40]-[41] <sup id="fnref:6" role="doc-noteref"><a href="#fn:6"
470+
class="footnote">6</a></sup>.
473471

474472
```text
475-
$ readelf -s target/release/hello-rust | grep __cfi_init
473+
$ readelf -s -W target/debug/rust-cfi | grep "\.cfi"
474+
12: 0000000000005170 46 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi7add_one.cfi
475+
15: 00000000000051a0 16 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi7add_two.cfi
476+
17: 0000000000005270 396 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi4main.cfi
477+
...
476478
```
477-
Fig. 15. Checking if LLVM CFI is enabled for a given binary.
479+
Fig. 15. Checking if LLVM CFI is enabled for a given binary[41].
478480

479-
The presence of the `__cfi_init` symbol (and references to `__cfi_check`)
480-
indicates that LLVM CFI (i.e., forward-edge control flow protection) is
481-
enabled for a given binary. Conversely, the absence of the `__cfi_init`
482-
symbol (and references to `__cfi_check`) indicates that LLVM CFI is not
483-
enabled for a given binary (see Fig. 15).
481+
The presence of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and
482+
references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge control
483+
flow protection) is enabled for a given binary. Conversely, the absence of
484+
symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to
485+
`__cfi_check`) indicates that LLVM CFI is not enabled for a given binary (see
486+
Fig. 15).
484487

485-
<small id="fn:6">6\. It supports Control Flow Guard (CFG) on Windows (see
488+
<small id="fn:6">6\. It also supports Control Flow Guard (CFG) on Windows (see
486489
<https://github.com/rust-lang/rust/issues/68793>). <a href="#fnref:6"
487490
class="reversefootnote" role="doc-backlink">↩</a></small>
488491

@@ -689,5 +692,8 @@ defaults (unrelated to `READ_IMPLIES_EXEC`).
689692
39. A. Crichton. “Remove the alloc\_jemalloc crate #55238.” GitHub.
690693
<https://github.com/rust-lang/rust/pull/55238>.
691694

692-
40. J. Aparicio. 2017. “Tracking issue for sanitizer support #39699.”
693-
<https://github.com/rust-lang/rust/issues/39699>.
695+
40. R. de C Valle. “Tracking Issue for LLVM Control Flow Integrity (CFI) Support
696+
for Rust #89653.” GitHub. <https://github.com/rust-lang/rust/issues/89653>.
697+
698+
41. “ControlFlowIntegrity.” The Rust Unstable Book.
699+
<https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html#controlflowintegrity>.

src/doc/unstable-book/src/compiler-flags/sanitizer.md

+179-2
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
# `sanitizer`
22

3-
The tracking issue for this feature is: [#39699](https://github.com/rust-lang/rust/issues/39699).
3+
The tracking issues for this feature are:
4+
5+
* [#39699](https://github.com/rust-lang/rust/issues/39699).
6+
* [#89653](https://github.com/rust-lang/rust/issues/89653).
47

58
------------------------
69

710
This feature allows for use of one of following sanitizers:
811

912
* [AddressSanitizer][clang-asan] a fast memory error detector.
13+
* [ControlFlowIntegrity][clang-cfi] LLVM Control Flow Integrity (CFI) provides
14+
forward-edge control flow protection.
1015
* [HWAddressSanitizer][clang-hwasan] a memory error detector similar to
1116
AddressSanitizer, but based on partial hardware assistance.
1217
* [LeakSanitizer][clang-lsan] a run-time memory leak detector.
1318
* [MemorySanitizer][clang-msan] a detector of uninitialized reads.
1419
* [ThreadSanitizer][clang-tsan] a fast data race detector.
1520

16-
To enable a sanitizer compile with `-Zsanitizer=address`,
21+
To enable a sanitizer compile with `-Zsanitizer=address`,`-Zsanitizer=cfi`,
1722
`-Zsanitizer=hwaddress`, `-Zsanitizer=leak`, `-Zsanitizer=memory` or
1823
`-Zsanitizer=thread`.
1924

@@ -177,6 +182,176 @@ Shadow byte legend (one shadow byte represents 8 application bytes):
177182
==39249==ABORTING
178183
```
179184
185+
# ControlFlowIntegrity
186+
187+
The LLVM Control Flow Integrity (CFI) support in the Rust compiler initially
188+
provides forward-edge control flow protection for Rust-compiled code only by
189+
aggregating function pointers in groups identified by their number of arguments.
190+
191+
Forward-edge control flow protection for C or C++ and Rust -compiled code "mixed
192+
binaries" (i.e., for when C or C++ and Rust -compiled code share the same
193+
virtual address space) will be provided in later work by defining and using
194+
compatible type identifiers (see Type metadata in the design document in the
195+
tracking issue [#89653](https://github.com/rust-lang/rust/issues/89653)).
196+
197+
LLVM CFI can be enabled with -Zsanitizer=cfi and requires LTO (i.e., -Clto).
198+
199+
## Example
200+
201+
```text
202+
#![feature(asm, naked_functions)]
203+
204+
use std::mem;
205+
206+
fn add_one(x: i32) -> i32 {
207+
x + 1
208+
}
209+
210+
#[naked]
211+
pub extern "C" fn add_two(x: i32) {
212+
// x + 2 preceeded by a landing pad/nop block
213+
unsafe {
214+
asm!(
215+
"
216+
nop
217+
nop
218+
nop
219+
nop
220+
nop
221+
nop
222+
nop
223+
nop
224+
nop
225+
lea rax, [rdi+2]
226+
ret
227+
",
228+
options(noreturn)
229+
);
230+
}
231+
}
232+
233+
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
234+
f(arg) + f(arg)
235+
}
236+
237+
fn main() {
238+
let answer = do_twice(add_one, 5);
239+
240+
println!("The answer is: {}", answer);
241+
242+
println!("With CFI enabled, you should not see the next answer");
243+
let f: fn(i32) -> i32 = unsafe {
244+
// Offsets 0-8 make it land in the landing pad/nop block, and offsets 1-8 are
245+
// invalid branch/call destinations (i.e., within the body of the function).
246+
mem::transmute::<*const u8, fn(i32) -> i32>((add_two as *const u8).offset(5))
247+
};
248+
let next_answer = do_twice(f, 5);
249+
250+
println!("The next answer is: {}", next_answer);
251+
}
252+
```
253+
Fig. 1. Modified example from the [Advanced Functions and
254+
Closures][rust-book-ch19-05] chapter of the [The Rust Programming
255+
Language][rust-book] book.
256+
257+
[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged)
258+
259+
```shell
260+
$ rustc rust_cfi.rs -o rust_cfi
261+
$ ./rust_cfi
262+
The answer is: 12
263+
With CFI enabled, you should not see the next answer
264+
The next answer is: 14
265+
$
266+
```
267+
Fig. 2. Build and execution of the modified example with LLVM CFI disabled.
268+
269+
[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged)
270+
271+
```shell
272+
$ rustc -Clto -Zsanitizer=cfi rust_cfi.rs -o rust_cfi
273+
$ ./rust_cfi
274+
The answer is: 12
275+
With CFI enabled, you should not see the next answer
276+
Illegal instruction
277+
$
278+
```
279+
Fig. 3. Build and execution of the modified example with LLVM CFI enabled.
280+
281+
When LLVM CFI is enabled, if there are any attempts to change/hijack control
282+
flow using an indirect branch/call to an invalid destination, the execution is
283+
terminated (see Fig. 3).
284+
285+
```rust
286+
use std::mem;
287+
288+
fn add_one(x: i32) -> i32 {
289+
x + 1
290+
}
291+
292+
fn add_two(x: i32, _y: i32) -> i32 {
293+
x + 2
294+
}
295+
296+
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
297+
f(arg) + f(arg)
298+
}
299+
300+
fn main() {
301+
let answer = do_twice(add_one, 5);
302+
303+
println!("The answer is: {}", answer);
304+
305+
println!("With CFI enabled, you should not see the next answer");
306+
let f: fn(i32) -> i32 =
307+
unsafe { mem::transmute::<*const u8, fn(i32) -> i32>(add_two as *const u8) };
308+
let next_answer = do_twice(f, 5);
309+
310+
println!("The next answer is: {}", next_answer);
311+
}
312+
```
313+
Fig. 4. Another modified example from the [Advanced Functions and
314+
Closures][rust-book-ch19-05] chapter of the [The Rust Programming
315+
Language][rust-book] book.
316+
317+
[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged)
318+
319+
```shell
320+
$ rustc rust_cfi.rs -o rust_cfi
321+
$ ./rust_cfi
322+
The answer is: 12
323+
With CFI enabled, you should not see the next answer
324+
The next answer is: 14
325+
$
326+
```
327+
Fig. 5. Build and execution of the modified example with LLVM CFI disabled.
328+
329+
[//]: # (FIXME: Replace with output from cargo using nightly when #89652 is merged)
330+
331+
```shell
332+
$ rustc -Clto -Zsanitizer=cfi rust_cfi.rs -o rust_cfi
333+
$ ./rust_cfi
334+
The answer is: 12
335+
With CFI enabled, you should not see the next answer
336+
Illegal instruction
337+
$
338+
```
339+
Fig. 6. Build and execution of the modified example with LLVM CFI enabled.
340+
341+
When LLVM CFI is enabled, if there are any attempts to change/hijack control
342+
flow using an indirect branch/call to a function with different number of
343+
arguments than intended/passed in the call/branch site, the execution is also
344+
terminated (see Fig. 6).
345+
346+
Forward-edge control flow protection not only by aggregating function pointers
347+
in groups identified by their number of arguments, but also their argument
348+
types, will also be provided in later work by defining and using compatible type
349+
identifiers (see Type metadata in the design document in the tracking
350+
issue [#89653](https://github.com/rust-lang/rust/issues/89653)).
351+
352+
[rust-book-ch19-05]: https://doc.rust-lang.org/book/ch19-05-advanced-functions-and-closures.html
353+
[rust-book]: https://doc.rust-lang.org/book/title-page.html
354+
180355
# HWAddressSanitizer
181356
182357
HWAddressSanitizer is a newer variant of AddressSanitizer that consumes much
@@ -404,12 +579,14 @@ Sanitizers produce symbolized stacktraces when llvm-symbolizer binary is in `PAT
404579
405580
* [Sanitizers project page](https://github.com/google/sanitizers/wiki/)
406581
* [AddressSanitizer in Clang][clang-asan]
582+
* [ControlFlowIntegrity in Clang][clang-cfi]
407583
* [HWAddressSanitizer in Clang][clang-hwasan]
408584
* [LeakSanitizer in Clang][clang-lsan]
409585
* [MemorySanitizer in Clang][clang-msan]
410586
* [ThreadSanitizer in Clang][clang-tsan]
411587
412588
[clang-asan]: https://clang.llvm.org/docs/AddressSanitizer.html
589+
[clang-cfi]: https://clang.llvm.org/docs/ControlFlowIntegrity.html
413590
[clang-hwasan]: https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html
414591
[clang-lsan]: https://clang.llvm.org/docs/LeakSanitizer.html
415592
[clang-msan]: https://clang.llvm.org/docs/MemorySanitizer.html

0 commit comments

Comments
 (0)