Format exploit mitigations documentation
Formats lines in the exploit mitigations documentation to be at maximum 80 characters long.
This commit is contained in:
parent
3572d7451d
commit
40a83be6eb
1 changed files with 160 additions and 176 deletions
|
|
@ -1,12 +1,12 @@
|
|||
# Exploit Mitigations
|
||||
|
||||
This chapter documents the exploit mitigations supported by the Rust
|
||||
compiler, and is by no means an extensive survey of the Rust programming
|
||||
language’s security features.
|
||||
This chapter documents the exploit mitigations supported by the Rust compiler,
|
||||
and is by no means an extensive survey of the Rust programming language’s
|
||||
security features.
|
||||
|
||||
This chapter is for software engineers working with the Rust programming
|
||||
language, and assumes prior knowledge of the Rust programming language and
|
||||
its toolchain.
|
||||
language, and assumes prior knowledge of the Rust programming language and its
|
||||
toolchain.
|
||||
|
||||
|
||||
## Introduction
|
||||
|
|
@ -14,8 +14,8 @@ its toolchain.
|
|||
The Rust programming language provides memory[1] and thread[2] safety
|
||||
guarantees via its ownership[3], references and borrowing[4], and slice
|
||||
types[5] features. However, Unsafe Rust[6] introduces unsafe blocks, unsafe
|
||||
functions and methods, unsafe traits, and new types that are not subject to
|
||||
the borrowing rules.
|
||||
functions and methods, unsafe traits, and new types that are not subject to the
|
||||
borrowing rules.
|
||||
|
||||
Parts of the Rust standard library are implemented as safe abstractions over
|
||||
unsafe code (and historically have been vulnerable to memory corruption[7]).
|
||||
|
|
@ -23,33 +23,31 @@ Furthermore, the Rust code and documentation encourage creating safe
|
|||
abstractions over unsafe code. This can cause a false sense of security if
|
||||
unsafe code is not properly reviewed and tested.
|
||||
|
||||
Unsafe Rust introduces features that do not provide the same memory and
|
||||
thread safety guarantees. This causes programs or libraries to be
|
||||
susceptible to memory corruption (CWE-119)[8] and concurrency issues
|
||||
(CWE-557)[9]. Modern C and C++ compilers provide exploit mitigations to
|
||||
increase the difficulty to exploit vulnerabilities resulting from these
|
||||
issues. Therefore, the Rust compiler must also support these exploit
|
||||
mitigations in order to mitigate vulnerabilities resulting from the use of
|
||||
Unsafe Rust. This chapter documents these exploit mitigations and how they
|
||||
apply to Rust.
|
||||
Unsafe Rust introduces features that do not provide the same memory and thread
|
||||
safety guarantees. This causes programs or libraries to be susceptible to
|
||||
memory corruption (CWE-119)[8] and concurrency issues (CWE-557)[9]. Modern C
|
||||
and C++ compilers provide exploit mitigations to increase the difficulty to
|
||||
exploit vulnerabilities resulting from these issues. Therefore, the Rust
|
||||
compiler must also support these exploit mitigations in order to mitigate
|
||||
vulnerabilities resulting from the use of Unsafe Rust. This chapter documents
|
||||
these exploit mitigations and how they apply to Rust.
|
||||
|
||||
This chapter does not discuss the effectiveness of these exploit mitigations
|
||||
as they vary greatly depending on several factors besides their design and
|
||||
implementation, but rather describe what they do, so their effectiveness can
|
||||
be understood within a given context.
|
||||
This chapter does not discuss the effectiveness of these exploit mitigations as
|
||||
they vary greatly depending on several factors besides their design and
|
||||
implementation, but rather describe what they do, so their effectiveness can be
|
||||
understood within a given context.
|
||||
|
||||
|
||||
## Exploit mitigations
|
||||
|
||||
This section documents the exploit mitigations applicable to the Rust
|
||||
compiler when building programs for the Linux operating system on the AMD64
|
||||
architecture and equivalent.<sup id="fnref:1" role="doc-noteref"><a
|
||||
href="#fn:1" class="footnote">1</a></sup>
|
||||
This section documents the exploit mitigations applicable to the Rust compiler
|
||||
when building programs for the Linux operating system on the AMD64 architecture
|
||||
and equivalent.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1"
|
||||
class="footnote">1</a></sup>
|
||||
|
||||
The Rust Programming Language currently has no specification. The Rust
|
||||
compiler (i.e., rustc) is the language reference implementation. All
|
||||
references to “the Rust compiler” in this chapter refer to the language
|
||||
reference implementation.
|
||||
The Rust Programming Language currently has no specification. The Rust compiler
|
||||
(i.e., rustc) is the language reference implementation. All references to “the
|
||||
Rust compiler” in this chapter refer to the language reference implementation.
|
||||
|
||||
Table I \
|
||||
Summary of exploit mitigations supported by the Rust compiler when building
|
||||
|
|
@ -83,8 +81,8 @@ instructing the dynamic linker to load it similarly to a shared object at a
|
|||
random load address, thus also benefiting from address-space layout
|
||||
randomization (ASLR). This is also referred to as “full ASLR”.
|
||||
|
||||
The Rust compiler supports position-independent executable, and enables it
|
||||
by default since version 0.12.0 (2014-10-09)[10]–[13].
|
||||
The Rust compiler supports position-independent executable, and enables it by
|
||||
default since version 0.12.0 (2014-10-09)[10]–[13].
|
||||
|
||||
```text
|
||||
$ readelf -h target/release/hello-rust | grep Type:
|
||||
|
|
@ -93,8 +91,7 @@ $ readelf -h target/release/hello-rust | grep Type:
|
|||
Fig. 1. Checking if an executable is a position-independent executable.
|
||||
|
||||
An executable with an object type of `ET_DYN` (i.e., shared object) and not
|
||||
`ET_EXEC` (i.e., executable) is a position-independent executable (see Fig.
|
||||
1).
|
||||
`ET_EXEC` (i.e., executable) is a position-independent executable (see Fig. 1).
|
||||
|
||||
|
||||
### Integer overflow checks
|
||||
|
|
@ -104,8 +101,8 @@ behavior (which may cause vulnerabilities) by checking for results of signed
|
|||
and unsigned integer computations that cannot be represented in their type,
|
||||
resulting in an overflow or wraparound.
|
||||
|
||||
The Rust compiler supports integer overflow checks, and enables it when
|
||||
debug assertions are enabled since version 1.1.0 (2015-06-25)[14]–[20].
|
||||
The Rust compiler supports integer overflow checks, and enables it when debug
|
||||
assertions are enabled since version 1.1.0 (2015-06-25)[14]–[20].
|
||||
|
||||
```compile_fail
|
||||
fn main() {
|
||||
|
|
@ -123,7 +120,7 @@ $ cargo run
|
|||
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.
|
||||
```
|
||||
Fig. 3. Build and execution of hello-rust-integer with debug assertions
|
||||
Fig. 3. Build and execution of hello-rust-integer with debug assertions
|
||||
enabled.
|
||||
|
||||
```text
|
||||
|
|
@ -133,24 +130,24 @@ $ cargo run --release
|
|||
Running `target/release/hello-rust-integer`
|
||||
u: 0
|
||||
```
|
||||
Fig. 4. Build and execution of hello-rust-integer with debug assertions
|
||||
Fig. 4. Build and execution of hello-rust-integer with debug assertions
|
||||
disabled.
|
||||
|
||||
Integer overflow checks are enabled when debug assertions are enabled (see
|
||||
Fig. 3), and disabled when debug assertions are disabled (see Fig. 4). To
|
||||
enable integer overflow checks independently, use the option to control
|
||||
integer overflow checks, scoped attributes, or explicit checking methods
|
||||
such as `checked_add`<sup id="fnref:2" role="doc-noteref"><a href="#fn:2"
|
||||
Integer overflow checks are enabled when debug assertions are enabled (see Fig.
|
||||
3), and disabled when debug assertions are disabled (see Fig. 4). To enable
|
||||
integer overflow checks independently, use the option to control integer
|
||||
overflow checks, scoped attributes, or explicit checking methods such as
|
||||
`checked_add`<sup id="fnref:2" role="doc-noteref"><a href="#fn:2"
|
||||
class="footnote">2</a></sup>.
|
||||
|
||||
It is recommended that explicit wrapping methods such as `wrapping_add` be
|
||||
used when wrapping semantics are intended, and that explicit checking and
|
||||
wrapping methods always be used when using Unsafe Rust.
|
||||
It is recommended that explicit wrapping methods such as `wrapping_add` be used
|
||||
when wrapping semantics are intended, and that explicit checking and wrapping
|
||||
methods always be used when using Unsafe Rust.
|
||||
|
||||
<small id="fn:2">2\. See [the `u32` docs](../std/primitive.u32.html)
|
||||
for more information on the checked, overflowing, saturating, and wrapping
|
||||
methods (using u32 as an example). <a href="#fnref:2"
|
||||
class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
<small id="fn:2">2\. See [the `u32` docs](../std/primitive.u32.html) for more
|
||||
information on the checked, overflowing, saturating, and wrapping methods
|
||||
(using u32 as an example). <a href="#fnref:2" class="reversefootnote"
|
||||
role="doc-backlink">↩</a></small>
|
||||
|
||||
|
||||
### Non-executable memory regions
|
||||
|
|
@ -158,29 +155,28 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
|
|||
Non-executable memory regions increase the difficulty of exploitation by
|
||||
limiting the memory regions that can be used to execute arbitrary code. Most
|
||||
modern processors provide support for the operating system to mark memory
|
||||
regions as non executable, but it was previously emulated by software, such
|
||||
as in grsecurity/PaX's
|
||||
[PAGEEXEC](https://pax.grsecurity.net/docs/pageexec.txt) and
|
||||
[SEGMEXEC](https://pax.grsecurity.net/docs/segmexec.txt), on processors that
|
||||
did not provide support for it. This is also known as “No Execute (NX) Bit”,
|
||||
“Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others.
|
||||
regions as non executable, but it was previously emulated by software, such as
|
||||
in grsecurity/PaX's [PAGEEXEC](https://pax.grsecurity.net/docs/pageexec.txt)
|
||||
and [SEGMEXEC](https://pax.grsecurity.net/docs/segmexec.txt), on processors
|
||||
that did not provide support for it. This is also known as “No Execute (NX)
|
||||
Bit”, “Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others.
|
||||
|
||||
The Rust compiler supports non-executable memory regions, and enables it by
|
||||
default since its initial release, version 0.1 (2012-01-20)[21], [22], but
|
||||
has regressed since then[23]–[25], and enforced by default since version
|
||||
1.8.0 (2016-04-14)[25].
|
||||
default since its initial release, version 0.1 (2012-01-20)[21], [22], but has
|
||||
regressed since then[23]–[25], and enforced by default since version 1.8.0
|
||||
(2016-04-14)[25].
|
||||
|
||||
```text
|
||||
$ readelf -l target/release/hello-rust | grep -A 1 GNU_STACK
|
||||
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
|
||||
0x0000000000000000 0x0000000000000000 RW 0x10
|
||||
```
|
||||
Fig. 5. Checking if non-executable memory regions are enabled for a given
|
||||
Fig. 5. Checking if non-executable memory regions are enabled for a given
|
||||
binary.
|
||||
|
||||
The presence of an element of type `PT_GNU_STACK` in the program header
|
||||
table with the `PF_X` (i.e., executable) flag unset indicates non-executable
|
||||
memory regions<sup id="fnref:3" role="doc-noteref"><a href="#fn:3"
|
||||
The presence of an element of type `PT_GNU_STACK` in the program header table
|
||||
with the `PF_X` (i.e., executable) flag unset indicates non-executable memory
|
||||
regions<sup id="fnref:3" role="doc-noteref"><a href="#fn:3"
|
||||
class="footnote">3</a></sup> are enabled for a given binary (see Fig. 5).
|
||||
Conversely, the presence of an element of type `PT_GNU_STACK` in the program
|
||||
header table with the `PF_X` flag set or the absence of an element of type
|
||||
|
|
@ -196,16 +192,15 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
|
|||
|
||||
Stack clashing protection protects the stack from overlapping with another
|
||||
memory region—allowing arbitrary data in both to be overwritten using each
|
||||
other—by reading from the stack pages as the stack grows to cause a page
|
||||
fault when attempting to read from the guard page/region. This is also
|
||||
referred to as “stack probes” or “stack probing”.
|
||||
other—by reading from the stack pages as the stack grows to cause a page fault
|
||||
when attempting to read from the guard page/region. This is also referred to as
|
||||
“stack probes” or “stack probing”.
|
||||
|
||||
The Rust compiler supports stack clashing protection via stack probing, and
|
||||
enables it by default since version 1.20.0 (2017-08-31)[26]–[29].
|
||||
|
||||

|
||||
Fig. 6. IDA Pro listing cross references to `__rust_probestack` in
|
||||
hello-rust.
|
||||
Fig. 6. IDA Pro listing cross references to `__rust_probestack` in hello-rust.
|
||||
|
||||
```rust
|
||||
fn hello() {
|
||||
|
|
@ -223,11 +218,11 @@ Fig 7. Modified hello-rust.
|
|||
Fig. 8. IDA Pro listing cross references to `__rust_probestack` in modified
|
||||
hello-rust.
|
||||
|
||||
To check if stack clashing protection is enabled for a given binary, search
|
||||
for cross references to `__rust_probestack`. The `__rust_probestack` is
|
||||
called in the prologue of functions whose stack size is larger than a page
|
||||
size (see Fig. 6), and can be forced for illustration purposes by modifying
|
||||
the hello-rust example as seen in Fig. 7 and Fig. 8.
|
||||
To check if stack clashing protection is enabled for a given binary, search for
|
||||
cross references to `__rust_probestack`. The `__rust_probestack` is called in
|
||||
the prologue of functions whose stack size is larger than a page size (see Fig.
|
||||
6), and can be forced for illustration purposes by modifying the hello-rust
|
||||
example as seen in Fig. 7 and Fig. 8.
|
||||
|
||||
|
||||
### Read-only relocations and immediate binding
|
||||
|
|
@ -246,21 +241,20 @@ $ readelf -l target/release/hello-rust | grep GNU_RELRO
|
|||
```
|
||||
Fig. 9. Checking if read-only relocations is enabled for a given binary.
|
||||
|
||||
The presence of an element of type `PT_GNU_RELRO` in the program header
|
||||
table indicates read-only relocations are enabled for a given binary (see
|
||||
Fig. 9). Conversely, the absence of an element of type `PT_GNU_RELRO` in the
|
||||
program header table indicates read-only relocations are not enabled for a
|
||||
given binary.
|
||||
The presence of an element of type `PT_GNU_RELRO` in the program header table
|
||||
indicates read-only relocations are enabled for a given binary (see Fig. 9).
|
||||
Conversely, the absence of an element of type `PT_GNU_RELRO` in the program
|
||||
header table indicates read-only relocations are not enabled for a given
|
||||
binary.
|
||||
|
||||
**Immediate binding** protects additional segments containing relocations
|
||||
(i.e., `.got.plt`) from being overwritten by instructing the dynamic linker
|
||||
to perform all relocations before transferring control to the program during
|
||||
startup, so all segments containing relocations can be marked read only
|
||||
(when combined with read-only relocations). This is also referred to as
|
||||
“full RELRO”.
|
||||
(i.e., `.got.plt`) from being overwritten by instructing the dynamic linker to
|
||||
perform all relocations before transferring control to the program during
|
||||
startup, so all segments containing relocations can be marked read only (when
|
||||
combined with read-only relocations). This is also referred to as “full RELRO”.
|
||||
|
||||
The Rust compiler supports immediate binding, and enables it by default
|
||||
since version 1.21.0 (2017-10-12)[30], [31].
|
||||
The Rust compiler supports immediate binding, and enables it by default since
|
||||
version 1.21.0 (2017-10-12)[30], [31].
|
||||
|
||||
```text
|
||||
$ readelf -d target/release/hello-rust | grep BIND_NOW
|
||||
|
|
@ -270,16 +264,15 @@ Fig. 10. Checking if immediate binding is enabled for a given binary.
|
|||
|
||||
The presence of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW`
|
||||
flag<sup id="fnref:4" role="doc-noteref"><a href="#fn:4"
|
||||
class="footnote">4</a></sup> in the dynamic section indicates immediate
|
||||
binding is enabled for a given binary (see Fig. 10). Conversely, the absence
|
||||
of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag in the
|
||||
dynamic section indicates immediate binding is not enabled for a given
|
||||
binary.
|
||||
class="footnote">4</a></sup> in the dynamic section indicates immediate binding
|
||||
is enabled for a given binary (see Fig. 10). Conversely, the absence of an
|
||||
element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag in the dynamic
|
||||
section indicates immediate binding is not enabled for a given binary.
|
||||
|
||||
The presence of both an element of type `PT_GNU_RELRO` in the program header
|
||||
table and of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW`
|
||||
flag in the dynamic section indicates full RELRO is enabled for a given
|
||||
binary (see Fig. 9 and Fig. 10).
|
||||
table and of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag
|
||||
in the dynamic section indicates full RELRO is enabled for a given binary (see
|
||||
Fig. 9 and Fig. 10).
|
||||
|
||||
<small id="fn:4">4\. And the `DF_1_NOW` flag for some link editors. <a
|
||||
href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
|
|
@ -287,26 +280,24 @@ href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></small>
|
|||
|
||||
### Heap corruption protection
|
||||
|
||||
Heap corruption protection protects memory allocated dynamically by
|
||||
performing several checks, such as checks for corrupted links between list
|
||||
elements, invalid pointers, invalid sizes, double/multiple “frees” of the
|
||||
same memory allocated, and many corner cases of these. These checks are
|
||||
implementation specific, and vary per allocator.
|
||||
Heap corruption protection protects memory allocated dynamically by performing
|
||||
several checks, such as checks for corrupted links between list elements,
|
||||
invalid pointers, invalid sizes, double/multiple “frees” of the same memory
|
||||
allocated, and many corner cases of these. These checks are implementation
|
||||
specific, and vary per allocator.
|
||||
|
||||
[ARM Memory Tagging Extension
|
||||
(MTE)](https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/enhancing-memory-safety),
|
||||
when available, will provide hardware assistance for a probabilistic
|
||||
mitigation to detect memory safety violations by tagging memory allocations,
|
||||
and automatically checking that the correct tag is used on every memory
|
||||
access.
|
||||
when available, will provide hardware assistance for a probabilistic mitigation
|
||||
to detect memory safety violations by tagging memory allocations, and
|
||||
automatically checking that the correct tag is used on every memory access.
|
||||
|
||||
Rust’s default allocator has historically been
|
||||
[jemalloc](http://jemalloc.net/), and it has long been the cause of issues
|
||||
and the subject of much discussion[32]–[38]. Consequently, it has been
|
||||
removed as the default allocator in favor of the operating system’s standard
|
||||
C library default allocator<sup id="fnref:5" role="doc-noteref"><a
|
||||
href="#fn:5" class="footnote">5</a></sup> since version 1.32.0
|
||||
(2019-01-17)[39].
|
||||
[jemalloc](http://jemalloc.net/), and it has long been the cause of issues and
|
||||
the subject of much discussion[32]–[38]. Consequently, it has been removed as
|
||||
the default allocator in favor of the operating system’s standard C library
|
||||
default allocator<sup id="fnref:5" role="doc-noteref"><a href="#fn:5"
|
||||
class="footnote">5</a></sup> since version 1.32.0 (2019-01-17)[39].
|
||||
|
||||
```rust,no_run
|
||||
fn main() {
|
||||
|
|
@ -330,8 +321,7 @@ $ cargo run
|
|||
free(): invalid next size (normal)
|
||||
Aborted
|
||||
```
|
||||
Fig. 12. Build and execution of hello-rust-heap with debug assertions
|
||||
enabled.
|
||||
Fig. 12. Build and execution of hello-rust-heap with debug assertions enabled.
|
||||
|
||||
```text
|
||||
$ cargo run --release
|
||||
|
|
@ -341,47 +331,43 @@ $ cargo run --release
|
|||
free(): invalid next size (normal)
|
||||
Aborted
|
||||
```
|
||||
Fig. 13. Build and execution of hello-rust-heap with debug assertions
|
||||
disabled.
|
||||
Fig. 13. Build and execution of hello-rust-heap with debug assertions disabled.
|
||||
|
||||
Heap corruption checks are being performed when using the default allocator
|
||||
(i.e., the GNU Allocator) as seen in Fig. 12 and Fig. 13.
|
||||
|
||||
<small id="fn:5">5\. Linux's standard C library default allocator is the GNU
|
||||
Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram
|
||||
Gloger, which in turn is derived from dlmalloc (Doug Lea malloc) by Doug
|
||||
Lea. <a href="#fnref:5" class="reversefootnote"
|
||||
role="doc-backlink">↩</a></small>
|
||||
Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram Gloger,
|
||||
which in turn is derived from dlmalloc (Doug Lea malloc) by Doug Lea. <a
|
||||
href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
|
||||
|
||||
### Stack smashing protection
|
||||
|
||||
Stack smashing protection protects programs from stack-based buffer
|
||||
overflows by inserting a random guard value between local variables and the
|
||||
saved return instruction pointer, and checking if this value has changed
|
||||
when returning from a function. This is also known as “Stack Protector” or
|
||||
“Stack Smashing Protector (SSP)”.
|
||||
Stack smashing protection protects programs from stack-based buffer overflows
|
||||
by inserting a random guard value between local variables and the saved return
|
||||
instruction pointer, and checking if this value has changed when returning from
|
||||
a function. This is also known as “Stack Protector” or “Stack Smashing
|
||||
Protector (SSP)”.
|
||||
|
||||
The Rust compiler supports stack smashing protection on nightly builds[42].
|
||||
|
||||

|
||||
Fig. 14. IDA Pro listing cross references to `__stack_chk_fail` in
|
||||
hello-rust.
|
||||
Fig. 14. IDA Pro listing cross references to `__stack_chk_fail` in hello-rust.
|
||||
|
||||
To check if stack smashing protection is enabled for a given binary, search
|
||||
for cross references to `__stack_chk_fail`. The presence of these
|
||||
cross-references in Rust-compiled code (e.g., `hello_rust::main`) indicates
|
||||
that the stack smashing protection is enabled (see Fig. 14).
|
||||
To check if stack smashing protection is enabled for a given binary, search for
|
||||
cross references to `__stack_chk_fail`. The presence of these cross-references
|
||||
in Rust-compiled code (e.g., `hello_rust::main`) indicates that the stack
|
||||
smashing protection is enabled (see Fig. 14).
|
||||
|
||||
|
||||
### Forward-edge control flow protection
|
||||
|
||||
Forward-edge control flow protection protects programs from having its
|
||||
control flow changed/hijacked by performing checks to ensure that
|
||||
destinations of indirect branches are one of their valid destinations in the
|
||||
control flow graph. The comprehensiveness of these checks vary per
|
||||
implementation. This is also known as “forward-edge control flow integrity
|
||||
(CFI)”.
|
||||
Forward-edge control flow protection protects programs from having its control
|
||||
flow changed/hijacked by performing checks to ensure that destinations of
|
||||
indirect branches are one of their valid destinations in the control flow
|
||||
graph. The comprehensiveness of these checks vary per implementation. This is
|
||||
also known as “forward-edge control flow integrity (CFI)”.
|
||||
|
||||
Newer processors provide hardware assistance for forward-edge control flow
|
||||
protection, such as ARM Branch Target Identification (BTI), ARM Pointer
|
||||
|
|
@ -407,9 +393,9 @@ $ readelf -s -W target/debug/rust-cfi | grep "\.cfi"
|
|||
Fig. 15. Checking if LLVM CFI is enabled for a given binary[41].
|
||||
|
||||
The presence of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and
|
||||
references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge control
|
||||
flow protection) is enabled for a given binary. Conversely, the absence of
|
||||
symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to
|
||||
references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge
|
||||
control flow protection) is enabled for a given binary. Conversely, the absence
|
||||
of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to
|
||||
`__cfi_check`) indicates that LLVM CFI is not enabled for a given binary (see
|
||||
Fig. 15).
|
||||
|
||||
|
|
@ -421,32 +407,32 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
|
|||
### Backward-edge control flow protection
|
||||
|
||||
**Shadow stack** protects saved return instruction pointers from being
|
||||
overwritten by storing a copy of them on a separate (shadow) stack, and
|
||||
using these copies as authoritative values when returning from functions.
|
||||
This is also known as “ShadowCallStack” and “Return Flow Guard”, and is
|
||||
considered an implementation of backward-edge control flow protection (or
|
||||
“backward-edge CFI”).
|
||||
overwritten by storing a copy of them on a separate (shadow) stack, and using
|
||||
these copies as authoritative values when returning from functions. This is
|
||||
also known as “ShadowCallStack” and “Return Flow Guard”, and is considered an
|
||||
implementation of backward-edge control flow protection (or “backward-edge
|
||||
CFI”).
|
||||
|
||||
**Safe stack** protects not only the saved return instruction pointers, but
|
||||
also register spills and some local variables from being overwritten by
|
||||
storing unsafe variables, such as large arrays, on a separate (unsafe)
|
||||
stack, and using these unsafe variables on the separate stack instead. This
|
||||
is also known as “SafeStack”, and is also considered an implementation of
|
||||
backward-edge control flow protection.
|
||||
also register spills and some local variables from being overwritten by storing
|
||||
unsafe variables, such as large arrays, on a separate (unsafe) stack, and using
|
||||
these unsafe variables on the separate stack instead. This is also known as
|
||||
“SafeStack”, and is also considered an implementation of backward-edge control
|
||||
flow protection.
|
||||
|
||||
Both shadow and safe stack are intended to be a more comprehensive
|
||||
alternatives to stack smashing protection as they protect the saved return
|
||||
instruction pointers (and other data in the case of safe stack) from
|
||||
arbitrary writes and non-linear out-of-bounds writes.
|
||||
Both shadow and safe stack are intended to be a more comprehensive alternatives
|
||||
to stack smashing protection as they protect the saved return instruction
|
||||
pointers (and other data in the case of safe stack) from arbitrary writes and
|
||||
non-linear out-of-bounds writes.
|
||||
|
||||
Newer processors provide hardware assistance for backward-edge control flow
|
||||
protection, such as ARM Pointer Authentication, and Intel Shadow Stack as
|
||||
part of Intel CET.
|
||||
protection, such as ARM Pointer Authentication, and Intel Shadow Stack as part
|
||||
of Intel CET.
|
||||
|
||||
The Rust compiler supports shadow stack for aarch64 only
|
||||
<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote">7</a></sup>
|
||||
on nightly Rust compilers [43]-[44]. Safe stack is available on nightly
|
||||
Rust compilers [45]-[46].
|
||||
The Rust compiler supports shadow stack for aarch64 only <sup id="fnref:7"
|
||||
role="doc-noteref"><a href="#fn:7" class="footnote">7</a></sup> on nightly Rust
|
||||
compilers [43]-[44]. Safe stack is available on nightly Rust compilers
|
||||
[45]-[46].
|
||||
|
||||
```text
|
||||
$ readelf -s target/release/hello-rust | grep __safestack_init
|
||||
|
|
@ -454,15 +440,14 @@ $ readelf -s target/release/hello-rust | grep __safestack_init
|
|||
```
|
||||
Fig. 16. Checking if LLVM SafeStack is enabled for a given binary.
|
||||
|
||||
The presence of the `__safestack_init` symbol indicates that LLVM SafeStack
|
||||
is enabled for a given binary (see Fig. 16). Conversely, the absence of the
|
||||
The presence of the `__safestack_init` symbol indicates that LLVM SafeStack is
|
||||
enabled for a given binary (see Fig. 16). Conversely, the absence of the
|
||||
`__safestack_init` symbol indicates that LLVM SafeStack is not enabled for a
|
||||
given binary.
|
||||
|
||||
<small id="fn:7">7\. The shadow stack implementation for the AMD64
|
||||
architecture and equivalent in LLVM was removed due to performance and
|
||||
security issues. <a href="#fnref:7" class="reversefootnote"
|
||||
role="doc-backlink">↩</a></small>
|
||||
<small id="fn:7">7\. The shadow stack implementation for the AMD64 architecture
|
||||
and equivalent in LLVM was removed due to performance and security issues. <a
|
||||
href="#fnref:7" class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
|
||||
|
||||
## Appendix
|
||||
|
|
@ -470,29 +455,28 @@ role="doc-backlink">↩</a></small>
|
|||
As of the latest version of the [Linux Standard Base (LSB) Core
|
||||
Specification](https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/progheader.html),
|
||||
the `PT_GNU_STACK` program header indicates whether the stack should be
|
||||
executable, and the absence of this header indicates that the stack should
|
||||
be executable. However, the Linux kernel currently sets the
|
||||
`READ_IMPLIES_EXEC` personality upon loading any executable with the
|
||||
`PT_GNU_STACK` program header and the `PF_X `flag set or with the absence of
|
||||
this header, resulting in not only the stack, but also all readable virtual
|
||||
memory mappings being executable.
|
||||
executable, and the absence of this header indicates that the stack should be
|
||||
executable. However, the Linux kernel currently sets the `READ_IMPLIES_EXEC`
|
||||
personality upon loading any executable with the `PT_GNU_STACK` program header
|
||||
and the `PF_X `flag set or with the absence of this header, resulting in not
|
||||
only the stack, but also all readable virtual memory mappings being executable.
|
||||
|
||||
An attempt to fix this [was made in
|
||||
2012](https://lore.kernel.org/lkml/f298f914-2239-44e4-8aa1-a51282e7fac0@zmail15.collab.prod.int.phx2.redhat.com/),
|
||||
and another [was made in
|
||||
2020](https://lore.kernel.org/kernel-hardening/20200327064820.12602-1-keescook@chromium.org/).
|
||||
The former never landed, and the latter partially fixed it, but introduced
|
||||
other issues—the absence of the `PT_GNU_STACK` program header still causes
|
||||
not only the stack, but also all readable virtual memory mappings to be
|
||||
executable in some architectures, such as IA-32 and equivalent (or causes
|
||||
the stack to be non-executable in some architectures, such as AMD64 and
|
||||
equivalent, contradicting the LSB).
|
||||
other issues—the absence of the `PT_GNU_STACK` program header still causes not
|
||||
only the stack, but also all readable virtual memory mappings to be executable
|
||||
in some architectures, such as IA-32 and equivalent (or causes the stack to be
|
||||
non-executable in some architectures, such as AMD64 and equivalent,
|
||||
contradicting the LSB).
|
||||
|
||||
The `READ_IMPLIES_EXEC` personality needs to be completely separated from
|
||||
the `PT_GNU_STACK` program header by having a separate option for it (or
|
||||
setarch -X could just be used whenever `READ_IMPLIES_EXEC` is needed), and
|
||||
the absence of the `PT_GNU_STACK` program header needs to have more secure
|
||||
defaults (unrelated to `READ_IMPLIES_EXEC`).
|
||||
The `READ_IMPLIES_EXEC` personality needs to be completely separated from the
|
||||
`PT_GNU_STACK` program header by having a separate option for it (or setarch -X
|
||||
could just be used whenever `READ_IMPLIES_EXEC` is needed), and the absence of
|
||||
the `PT_GNU_STACK` program header needs to have more secure defaults (unrelated
|
||||
to `READ_IMPLIES_EXEC`).
|
||||
|
||||
|
||||
## References
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue