Format exploit mitigations documentation

Formats lines in the exploit mitigations documentation to be at maximum
80 characters long.
This commit is contained in:
Ramon de C Valle 2023-07-27 13:14:43 -07:00
parent 3572d7451d
commit 40a83be6eb

View file

@ -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
languages 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 languages
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].
![Screenshot of IDA Pro listing cross references to __rust_probestack in hello-rust.](images/image1.png "Cross references to __rust_probestack in hello-rust.")
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.
Rusts 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 systems 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 systems 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].
![Screenshot of IDA Pro listing cross references to __stack_chk_fail in hello-rust.](images/image3.png "Cross references to __stack_chk_fail in hello-rust.")
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