1
1
# Exploit Mitigations
2
2
3
- This chapter documents the exploit mitigations supported by the Rust
4
- compiler, and is by no means an extensive survey of the Rust programming
5
- language’s security features.
3
+ This chapter documents the exploit mitigations supported by the Rust compiler,
4
+ and is by no means an extensive survey of the Rust programming language’s
5
+ security features.
6
6
7
7
This chapter is for software engineers working with the Rust programming
8
- language, and assumes prior knowledge of the Rust programming language and
9
- its toolchain.
8
+ language, and assumes prior knowledge of the Rust programming language and its
9
+ toolchain.
10
10
11
11
12
12
## Introduction
13
13
14
14
The Rust programming language provides memory[ 1] and thread[ 2] safety
15
15
guarantees via its ownership[ 3] , references and borrowing[ 4] , and slice
16
16
types[ 5] features. However, Unsafe Rust[ 6] introduces unsafe blocks, unsafe
17
- functions and methods, unsafe traits, and new types that are not subject to
18
- the borrowing rules.
17
+ functions and methods, unsafe traits, and new types that are not subject to the
18
+ borrowing rules.
19
19
20
20
Parts of the Rust standard library are implemented as safe abstractions over
21
21
unsafe code (and historically have been vulnerable to memory corruption[ 7] ).
22
22
Furthermore, the Rust code and documentation encourage creating safe
23
23
abstractions over unsafe code. This can cause a false sense of security if
24
24
unsafe code is not properly reviewed and tested.
25
25
26
- Unsafe Rust introduces features that do not provide the same memory and
27
- thread safety guarantees. This causes programs or libraries to be
28
- susceptible to memory corruption (CWE-119)[ 8] and concurrency issues
29
- (CWE-557)[ 9] . Modern C and C++ compilers provide exploit mitigations to
30
- increase the difficulty to exploit vulnerabilities resulting from these
31
- issues. Therefore, the Rust compiler must also support these exploit
32
- mitigations in order to mitigate vulnerabilities resulting from the use of
33
- Unsafe Rust. This chapter documents these exploit mitigations and how they
34
- apply to Rust.
26
+ Unsafe Rust introduces features that do not provide the same memory and thread
27
+ safety guarantees. This causes programs or libraries to be susceptible to
28
+ memory corruption (CWE-119)[ 8] and concurrency issues (CWE-557)[ 9] . Modern C
29
+ and C++ compilers provide exploit mitigations to increase the difficulty to
30
+ exploit vulnerabilities resulting from these issues. Therefore, the Rust
31
+ compiler must also support these exploit mitigations in order to mitigate
32
+ vulnerabilities resulting from the use of Unsafe Rust. This chapter documents
33
+ these exploit mitigations and how they apply to Rust.
35
34
36
- This chapter does not discuss the effectiveness of these exploit mitigations
37
- as they vary greatly depending on several factors besides their design and
38
- implementation, but rather describe what they do, so their effectiveness can
39
- be understood within a given context.
35
+ This chapter does not discuss the effectiveness of these exploit mitigations as
36
+ they vary greatly depending on several factors besides their design and
37
+ implementation, but rather describe what they do, so their effectiveness can be
38
+ understood within a given context.
40
39
41
40
42
41
## Exploit mitigations
43
42
44
- This section documents the exploit mitigations applicable to the Rust
45
- compiler when building programs for the Linux operating system on the AMD64
46
- architecture and equivalent.<sup id =" fnref:1 " role =" doc-noteref " ><a
47
- href="#fn:1" class="footnote">1</a ></sup >
43
+ This section documents the exploit mitigations applicable to the Rust compiler
44
+ when building programs for the Linux operating system on the AMD64 architecture
45
+ and equivalent.<sup id =" fnref:1 " role =" doc-noteref " ><a href="#fn:1"
46
+ class="footnote">1</a ></sup >
48
47
49
- The Rust Programming Language currently has no specification. The Rust
50
- compiler (i.e., rustc) is the language reference implementation. All
51
- references to “the Rust compiler” in this chapter refer to the language
52
- reference implementation.
48
+ The Rust Programming Language currently has no specification. The Rust compiler
49
+ (i.e., rustc) is the language reference implementation. All references to “the
50
+ Rust compiler” in this chapter refer to the language reference implementation.
53
51
54
52
Table I \
55
53
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
83
81
random load address, thus also benefiting from address-space layout
84
82
randomization (ASLR). This is also referred to as “full ASLR”.
85
83
86
- The Rust compiler supports position-independent executable, and enables it
87
- by default since version 0.12.0 (2014-10-09)[ 10] –[ 13] .
84
+ The Rust compiler supports position-independent executable, and enables it by
85
+ default since version 0.12.0 (2014-10-09)[ 10] –[ 13] .
88
86
89
87
``` text
90
88
$ readelf -h target/release/hello-rust | grep Type:
@@ -93,8 +91,7 @@ $ readelf -h target/release/hello-rust | grep Type:
93
91
Fig. 1. Checking if an executable is a position-independent executable.
94
92
95
93
An executable with an object type of ` ET_DYN ` (i.e., shared object) and not
96
- ` ET_EXEC ` (i.e., executable) is a position-independent executable (see Fig.
97
- 1).
94
+ ` ET_EXEC ` (i.e., executable) is a position-independent executable (see Fig. 1).
98
95
99
96
100
97
### Integer overflow checks
@@ -104,8 +101,8 @@ behavior (which may cause vulnerabilities) by checking for results of signed
104
101
and unsigned integer computations that cannot be represented in their type,
105
102
resulting in an overflow or wraparound.
106
103
107
- The Rust compiler supports integer overflow checks, and enables it when
108
- debug assertions are enabled since version 1.1.0 (2015-06-25)[ 14] –[ 20] .
104
+ The Rust compiler supports integer overflow checks, and enables it when debug
105
+ assertions are enabled since version 1.1.0 (2015-06-25)[ 14] –[ 20] .
109
106
110
107
``` compile_fail
111
108
fn main() {
@@ -123,7 +120,7 @@ $ cargo run
123
120
thread 'main' panicked at 'attempt to add with overflow', src/main.rs:3:23
124
121
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
125
122
```
126
- Fig. 3. Build and execution of hello-rust-integer with debug assertions
123
+ Fig. 3. Build and execution of hello-rust-integer with debug assertions
127
124
enabled.
128
125
129
126
``` text
@@ -133,54 +130,53 @@ $ cargo run --release
133
130
Running `target/release/hello-rust-integer`
134
131
u: 0
135
132
```
136
- Fig. 4. Build and execution of hello-rust-integer with debug assertions
133
+ Fig. 4. Build and execution of hello-rust-integer with debug assertions
137
134
disabled.
138
135
139
- Integer overflow checks are enabled when debug assertions are enabled (see
140
- Fig. 3), and disabled when debug assertions are disabled (see Fig. 4). To
141
- enable integer overflow checks independently, use the option to control
142
- integer overflow checks, scoped attributes, or explicit checking methods
143
- such as ` checked_add ` <sup id =" fnref:2 " role =" doc-noteref " ><a href="#fn:2"
136
+ Integer overflow checks are enabled when debug assertions are enabled (see Fig.
137
+ 3), and disabled when debug assertions are disabled (see Fig. 4). To enable
138
+ integer overflow checks independently, use the option to control integer
139
+ overflow checks, scoped attributes, or explicit checking methods such as
140
+ ` checked_add ` <sup id =" fnref:2 " role =" doc-noteref " ><a href="#fn:2"
144
141
class="footnote">2</a ></sup >.
145
142
146
- It is recommended that explicit wrapping methods such as ` wrapping_add ` be
147
- used when wrapping semantics are intended, and that explicit checking and
148
- wrapping methods always be used when using Unsafe Rust.
143
+ It is recommended that explicit wrapping methods such as ` wrapping_add ` be used
144
+ when wrapping semantics are intended, and that explicit checking and wrapping
145
+ methods always be used when using Unsafe Rust.
149
146
150
- <small id =" fn:2 " >2\. See [ the ` u32 ` docs] ( ../std/primitive.u32.html )
151
- for more information on the checked, overflowing, saturating, and wrapping
152
- methods (using u32 as an example). <a href="#fnref:2"
153
- class="reversefootnote" role="doc-backlink">↩</a ></small >
147
+ <small id =" fn:2 " >2\. See [ the ` u32 ` docs] ( ../std/primitive.u32.html ) for more
148
+ information on the checked, overflowing, saturating, and wrapping methods
149
+ (using u32 as an example). <a href="#fnref:2" class="reversefootnote "
150
+ role="doc-backlink">↩</a ></small >
154
151
155
152
156
153
### Non-executable memory regions
157
154
158
155
Non-executable memory regions increase the difficulty of exploitation by
159
156
limiting the memory regions that can be used to execute arbitrary code. Most
160
157
modern processors provide support for the operating system to mark memory
161
- regions as non executable, but it was previously emulated by software, such
162
- as in grsecurity/PaX's
163
- [ PAGEEXEC] ( https://pax.grsecurity.net/docs/pageexec.txt ) and
164
- [ SEGMEXEC] ( https://pax.grsecurity.net/docs/segmexec.txt ) , on processors that
165
- did not provide support for it. This is also known as “No Execute (NX) Bit”,
166
- “Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others.
158
+ regions as non executable, but it was previously emulated by software, such as
159
+ in grsecurity/PaX's [ PAGEEXEC] ( https://pax.grsecurity.net/docs/pageexec.txt )
160
+ and [ SEGMEXEC] ( https://pax.grsecurity.net/docs/segmexec.txt ) , on processors
161
+ that did not provide support for it. This is also known as “No Execute (NX)
162
+ Bit”, “Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others.
167
163
168
164
The Rust compiler supports non-executable memory regions, and enables it by
169
- default since its initial release, version 0.1 (2012-01-20)[ 21] , [ 22] , but
170
- has regressed since then[ 23] –[ 25] , and enforced by default since version
171
- 1.8.0 (2016-04-14)[ 25] .
165
+ default since its initial release, version 0.1 (2012-01-20)[ 21] , [ 22] , but has
166
+ regressed since then[ 23] –[ 25] , and enforced by default since version 1.8.0
167
+ (2016-04-14)[ 25] .
172
168
173
169
``` text
174
170
$ readelf -l target/release/hello-rust | grep -A 1 GNU_STACK
175
171
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
176
172
0x0000000000000000 0x0000000000000000 RW 0x10
177
173
```
178
- Fig. 5. Checking if non-executable memory regions are enabled for a given
174
+ Fig. 5. Checking if non-executable memory regions are enabled for a given
179
175
binary.
180
176
181
- The presence of an element of type ` PT_GNU_STACK ` in the program header
182
- table with the ` PF_X ` (i.e., executable) flag unset indicates non-executable
183
- memory regions<sup id =" fnref:3 " role =" doc-noteref " ><a href="#fn:3"
177
+ The presence of an element of type ` PT_GNU_STACK ` in the program header table
178
+ with the ` PF_X ` (i.e., executable) flag unset indicates non-executable memory
179
+ regions<sup id =" fnref:3 " role =" doc-noteref " ><a href="#fn:3"
184
180
class="footnote">3</a ></sup > are enabled for a given binary (see Fig. 5).
185
181
Conversely, the presence of an element of type ` PT_GNU_STACK ` in the program
186
182
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>
196
192
197
193
Stack clashing protection protects the stack from overlapping with another
198
194
memory region—allowing arbitrary data in both to be overwritten using each
199
- other—by reading from the stack pages as the stack grows to cause a page
200
- fault when attempting to read from the guard page/region. This is also
201
- referred to as “stack probes” or “stack probing”.
195
+ other—by reading from the stack pages as the stack grows to cause a page fault
196
+ when attempting to read from the guard page/region. This is also referred to as
197
+ “stack probes” or “stack probing”.
202
198
203
199
The Rust compiler supports stack clashing protection via stack probing, and
204
200
enables it by default since version 1.20.0 (2017-08-31)[ 26] –[ 29] .
205
201
206
202
![ Screenshot of IDA Pro listing cross references to __ rust_probestack in hello-rust.] ( images/image1.png " Cross references to __rust_probestack in hello-rust. ")
207
- Fig. 6. IDA Pro listing cross references to ` __rust_probestack ` in
208
- hello-rust.
203
+ Fig. 6. IDA Pro listing cross references to ` __rust_probestack ` in hello-rust.
209
204
210
205
``` rust
211
206
fn hello () {
@@ -223,11 +218,11 @@ Fig 7. Modified hello-rust.
223
218
Fig. 8. IDA Pro listing cross references to ` __rust_probestack ` in modified
224
219
hello-rust.
225
220
226
- To check if stack clashing protection is enabled for a given binary, search
227
- for cross references to ` __rust_probestack ` . The ` __rust_probestack ` is
228
- called in the prologue of functions whose stack size is larger than a page
229
- size (see Fig. 6), and can be forced for illustration purposes by modifying
230
- the hello-rust example as seen in Fig. 7 and Fig. 8.
221
+ To check if stack clashing protection is enabled for a given binary, search for
222
+ cross references to ` __rust_probestack ` . The ` __rust_probestack ` is called in
223
+ the prologue of functions whose stack size is larger than a page size (see Fig.
224
+ 6), and can be forced for illustration purposes by modifying the hello-rust
225
+ example as seen in Fig. 7 and Fig. 8.
231
226
232
227
233
228
### Read-only relocations and immediate binding
@@ -246,21 +241,20 @@ $ readelf -l target/release/hello-rust | grep GNU_RELRO
246
241
```
247
242
Fig. 9. Checking if read-only relocations is enabled for a given binary.
248
243
249
- The presence of an element of type ` PT_GNU_RELRO ` in the program header
250
- table indicates read-only relocations are enabled for a given binary (see
251
- Fig. 9). Conversely, the absence of an element of type ` PT_GNU_RELRO ` in the
252
- program header table indicates read-only relocations are not enabled for a
253
- given binary.
244
+ The presence of an element of type ` PT_GNU_RELRO ` in the program header table
245
+ indicates read-only relocations are enabled for a given binary (see Fig. 9).
246
+ Conversely, the absence of an element of type ` PT_GNU_RELRO ` in the program
247
+ header table indicates read-only relocations are not enabled for a given
248
+ binary.
254
249
255
250
** Immediate binding** protects additional segments containing relocations
256
- (i.e., ` .got.plt ` ) from being overwritten by instructing the dynamic linker
257
- to perform all relocations before transferring control to the program during
258
- startup, so all segments containing relocations can be marked read only
259
- (when combined with read-only relocations). This is also referred to as
260
- “full RELRO”.
251
+ (i.e., ` .got.plt ` ) from being overwritten by instructing the dynamic linker to
252
+ perform all relocations before transferring control to the program during
253
+ startup, so all segments containing relocations can be marked read only (when
254
+ combined with read-only relocations). This is also referred to as “full RELRO”.
261
255
262
- The Rust compiler supports immediate binding, and enables it by default
263
- since version 1.21.0 (2017-10-12)[ 30] , [ 31] .
256
+ The Rust compiler supports immediate binding, and enables it by default since
257
+ version 1.21.0 (2017-10-12)[ 30] , [ 31] .
264
258
265
259
``` text
266
260
$ readelf -d target/release/hello-rust | grep BIND_NOW
@@ -270,43 +264,40 @@ Fig. 10. Checking if immediate binding is enabled for a given binary.
270
264
271
265
The presence of an element with the ` DT_BIND_NOW ` tag and the ` DF_BIND_NOW `
272
266
flag<sup id =" fnref:4 " role =" doc-noteref " ><a href="#fn:4"
273
- class="footnote">4</a ></sup > in the dynamic section indicates immediate
274
- binding is enabled for a given binary (see Fig. 10). Conversely, the absence
275
- of an element with the ` DT_BIND_NOW ` tag and the ` DF_BIND_NOW ` flag in the
276
- dynamic section indicates immediate binding is not enabled for a given
277
- binary.
267
+ class="footnote">4</a ></sup > in the dynamic section indicates immediate binding
268
+ is enabled for a given binary (see Fig. 10). Conversely, the absence of an
269
+ element with the ` DT_BIND_NOW ` tag and the ` DF_BIND_NOW ` flag in the dynamic
270
+ section indicates immediate binding is not enabled for a given binary.
278
271
279
272
The presence of both an element of type ` PT_GNU_RELRO ` in the program header
280
- table and of an element with the ` DT_BIND_NOW ` tag and the ` DF_BIND_NOW `
281
- flag in the dynamic section indicates full RELRO is enabled for a given
282
- binary (see Fig. 9 and Fig. 10).
273
+ table and of an element with the ` DT_BIND_NOW ` tag and the ` DF_BIND_NOW ` flag
274
+ in the dynamic section indicates full RELRO is enabled for a given binary (see
275
+ Fig. 9 and Fig. 10).
283
276
284
277
<small id =" fn:4 " >4\. And the ` DF_1_NOW ` flag for some link editors. <a
285
278
href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a ></small >
286
279
287
280
288
281
### Heap corruption protection
289
282
290
- Heap corruption protection protects memory allocated dynamically by
291
- performing several checks, such as checks for corrupted links between list
292
- elements, invalid pointers, invalid sizes, double/multiple “frees” of the
293
- same memory allocated, and many corner cases of these. These checks are
294
- implementation specific, and vary per allocator.
283
+ Heap corruption protection protects memory allocated dynamically by performing
284
+ several checks, such as checks for corrupted links between list elements,
285
+ invalid pointers, invalid sizes, double/multiple “frees” of the same memory
286
+ allocated, and many corner cases of these. These checks are implementation
287
+ specific, and vary per allocator.
295
288
296
289
[ ARM Memory Tagging Extension
297
290
(MTE)] ( https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/enhancing-memory-safety ) ,
298
- when available, will provide hardware assistance for a probabilistic
299
- mitigation to detect memory safety violations by tagging memory allocations,
300
- and automatically checking that the correct tag is used on every memory
301
- access.
291
+ when available, will provide hardware assistance for a probabilistic mitigation
292
+ to detect memory safety violations by tagging memory allocations, and
293
+ automatically checking that the correct tag is used on every memory access.
302
294
303
295
Rust’s default allocator has historically been
304
- [ jemalloc] ( http://jemalloc.net/ ) , and it has long been the cause of issues
305
- and the subject of much discussion[ 32] –[ 38] . Consequently, it has been
306
- removed as the default allocator in favor of the operating system’s standard
307
- C library default allocator<sup id =" fnref:5 " role =" doc-noteref " ><a
308
- href="#fn:5" class="footnote">5</a ></sup > since version 1.32.0
309
- (2019-01-17)[ 39] .
296
+ [ jemalloc] ( http://jemalloc.net/ ) , and it has long been the cause of issues and
297
+ the subject of much discussion[ 32] –[ 38] . Consequently, it has been removed as
298
+ the default allocator in favor of the operating system’s standard C library
299
+ default allocator<sup id =" fnref:5 " role =" doc-noteref " ><a href="#fn:5"
300
+ class="footnote">5</a ></sup > since version 1.32.0 (2019-01-17)[ 39] .
310
301
311
302
``` rust,no_run
312
303
fn main() {
@@ -330,8 +321,7 @@ $ cargo run
330
321
free(): invalid next size (normal)
331
322
Aborted
332
323
```
333
- Fig. 12. Build and execution of hello-rust-heap with debug assertions
334
- enabled.
324
+ Fig. 12. Build and execution of hello-rust-heap with debug assertions enabled.
335
325
336
326
``` text
337
327
$ cargo run --release
@@ -341,47 +331,43 @@ $ cargo run --release
341
331
free(): invalid next size (normal)
342
332
Aborted
343
333
```
344
- Fig. 13. Build and execution of hello-rust-heap with debug assertions
345
- disabled.
334
+ Fig. 13. Build and execution of hello-rust-heap with debug assertions disabled.
346
335
347
336
Heap corruption checks are being performed when using the default allocator
348
337
(i.e., the GNU Allocator) as seen in Fig. 12 and Fig. 13.
349
338
350
339
<small id =" fn:5 " >5\. Linux's standard C library default allocator is the GNU
351
- Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram
352
- Gloger, which in turn is derived from dlmalloc (Doug Lea malloc) by Doug
353
- Lea. <a href="#fnref:5" class="reversefootnote"
354
- role="doc-backlink">↩</a ></small >
340
+ Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram Gloger,
341
+ which in turn is derived from dlmalloc (Doug Lea malloc) by Doug Lea. <a
342
+ href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a ></small >
355
343
356
344
357
345
### Stack smashing protection
358
346
359
- Stack smashing protection protects programs from stack-based buffer
360
- overflows by inserting a random guard value between local variables and the
361
- saved return instruction pointer, and checking if this value has changed
362
- when returning from a function. This is also known as “Stack Protector” or
363
- “Stack Smashing Protector (SSP)”.
347
+ Stack smashing protection protects programs from stack-based buffer overflows
348
+ by inserting a random guard value between local variables and the saved return
349
+ instruction pointer, and checking if this value has changed when returning from
350
+ a function. This is also known as “Stack Protector” or “Stack Smashing
351
+ Protector (SSP)”.
364
352
365
353
The Rust compiler supports stack smashing protection on nightly builds[ 42] .
366
354
367
355
![ 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. ")
368
- Fig. 14. IDA Pro listing cross references to ` __stack_chk_fail ` in
369
- hello-rust.
356
+ Fig. 14. IDA Pro listing cross references to ` __stack_chk_fail ` in hello-rust.
370
357
371
- To check if stack smashing protection is enabled for a given binary, search
372
- for cross references to ` __stack_chk_fail ` . The presence of these
373
- cross-references in Rust-compiled code (e.g., ` hello_rust::main ` ) indicates
374
- that the stack smashing protection is enabled (see Fig. 14).
358
+ To check if stack smashing protection is enabled for a given binary, search for
359
+ cross references to ` __stack_chk_fail ` . The presence of these cross-references
360
+ in Rust-compiled code (e.g., ` hello_rust::main ` ) indicates that the stack
361
+ smashing protection is enabled (see Fig. 14).
375
362
376
363
377
364
### Forward-edge control flow protection
378
365
379
- Forward-edge control flow protection protects programs from having its
380
- control flow changed/hijacked by performing checks to ensure that
381
- destinations of indirect branches are one of their valid destinations in the
382
- control flow graph. The comprehensiveness of these checks vary per
383
- implementation. This is also known as “forward-edge control flow integrity
384
- (CFI)”.
366
+ Forward-edge control flow protection protects programs from having its control
367
+ flow changed/hijacked by performing checks to ensure that destinations of
368
+ indirect branches are one of their valid destinations in the control flow
369
+ graph. The comprehensiveness of these checks vary per implementation. This is
370
+ also known as “forward-edge control flow integrity (CFI)”.
385
371
386
372
Newer processors provide hardware assistance for forward-edge control flow
387
373
protection, such as ARM Branch Target Identification (BTI), ARM Pointer
@@ -407,9 +393,9 @@ $ readelf -s -W target/debug/rust-cfi | grep "\.cfi"
407
393
Fig. 15. Checking if LLVM CFI is enabled for a given binary[ 41] .
408
394
409
395
The presence of symbols suffixed with ".cfi" or the ` __cfi_init ` symbol (and
410
- references to ` __cfi_check ` ) indicates that LLVM CFI (i.e., forward-edge control
411
- flow protection) is enabled for a given binary. Conversely, the absence of
412
- symbols suffixed with ".cfi" or the ` __cfi_init ` symbol (and references to
396
+ references to ` __cfi_check ` ) indicates that LLVM CFI (i.e., forward-edge
397
+ control flow protection) is enabled for a given binary. Conversely, the absence
398
+ of symbols suffixed with ".cfi" or the ` __cfi_init ` symbol (and references to
413
399
` __cfi_check ` ) indicates that LLVM CFI is not enabled for a given binary (see
414
400
Fig. 15).
415
401
@@ -421,78 +407,76 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
421
407
### Backward-edge control flow protection
422
408
423
409
** Shadow stack** protects saved return instruction pointers from being
424
- overwritten by storing a copy of them on a separate (shadow) stack, and
425
- using these copies as authoritative values when returning from functions.
426
- This is also known as “ShadowCallStack” and “Return Flow Guard”, and is
427
- considered an implementation of backward-edge control flow protection (or
428
- “backward-edge CFI”).
410
+ overwritten by storing a copy of them on a separate (shadow) stack, and using
411
+ these copies as authoritative values when returning from functions. This is
412
+ also known as “ShadowCallStack” and “Return Flow Guard”, and is considered an
413
+ implementation of backward-edge control flow protection (or “backward-edge
414
+ CFI”).
429
415
430
416
** Safe stack** protects not only the saved return instruction pointers, but
431
- also register spills and some local variables from being overwritten by
432
- storing unsafe variables, such as large arrays, on a separate (unsafe)
433
- stack, and using these unsafe variables on the separate stack instead. This
434
- is also known as “SafeStack”, and is also considered an implementation of
435
- backward-edge control flow protection.
417
+ also register spills and some local variables from being overwritten by storing
418
+ unsafe variables, such as large arrays, on a separate (unsafe) stack, and using
419
+ these unsafe variables on the separate stack instead. This is also known as
420
+ “SafeStack”, and is also considered an implementation of backward-edge control
421
+ flow protection.
436
422
437
- Both shadow and safe stack are intended to be a more comprehensive
438
- alternatives to stack smashing protection as they protect the saved return
439
- instruction pointers (and other data in the case of safe stack) from
440
- arbitrary writes and non-linear out-of-bounds writes.
423
+ Both shadow and safe stack are intended to be a more comprehensive alternatives
424
+ to stack smashing protection as they protect the saved return instruction
425
+ pointers (and other data in the case of safe stack) from arbitrary writes and
426
+ non-linear out-of-bounds writes.
441
427
442
428
Newer processors provide hardware assistance for backward-edge control flow
443
- protection, such as ARM Pointer Authentication, and Intel Shadow Stack as
444
- part of Intel CET.
429
+ protection, such as ARM Pointer Authentication, and Intel Shadow Stack as part
430
+ of Intel CET.
445
431
446
- The Rust compiler supports shadow stack for aarch64 only
447
- < sup id = " fnref:7 " role =" doc-noteref " ><a href =" #fn:7 " class =" footnote " >7</a ></sup >
448
- on nightly Rust compilers [ 43] -[ 44] . Safe stack is available on nightly
449
- Rust compilers [ 45] -[ 46] .
432
+ The Rust compiler supports shadow stack for aarch64 only <sup id="fnref:7"
433
+ role="doc-noteref"><a href =" #fn:7 " class =" footnote " >7</a ></sup > on nightly Rust
434
+ compilers [ 43] -[ 44] . Safe stack is available on nightly Rust compilers
435
+ [ 45] -[ 46] .
450
436
451
437
``` text
452
438
$ readelf -s target/release/hello-rust | grep __safestack_init
453
439
1177: 00000000000057b0 444 FUNC GLOBAL DEFAULT 9 __safestack_init
454
440
```
455
441
Fig. 16. Checking if LLVM SafeStack is enabled for a given binary.
456
442
457
- The presence of the ` __safestack_init ` symbol indicates that LLVM SafeStack
458
- is enabled for a given binary (see Fig. 16). Conversely, the absence of the
443
+ The presence of the ` __safestack_init ` symbol indicates that LLVM SafeStack is
444
+ enabled for a given binary (see Fig. 16). Conversely, the absence of the
459
445
` __safestack_init ` symbol indicates that LLVM SafeStack is not enabled for a
460
446
given binary.
461
447
462
- <small id =" fn:7 " >7\. The shadow stack implementation for the AMD64
463
- architecture and equivalent in LLVM was removed due to performance and
464
- security issues. <a href="#fnref:7" class="reversefootnote"
465
- role="doc-backlink">↩</a ></small >
448
+ <small id =" fn:7 " >7\. The shadow stack implementation for the AMD64 architecture
449
+ and equivalent in LLVM was removed due to performance and security issues. <a
450
+ href="#fnref:7" class="reversefootnote" role="doc-backlink">↩</a ></small >
466
451
467
452
468
453
## Appendix
469
454
470
455
As of the latest version of the [ Linux Standard Base (LSB) Core
471
456
Specification] ( https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/progheader.html ) ,
472
457
the ` PT_GNU_STACK ` program header indicates whether the stack should be
473
- executable, and the absence of this header indicates that the stack should
474
- be executable. However, the Linux kernel currently sets the
475
- ` READ_IMPLIES_EXEC ` personality upon loading any executable with the
476
- ` PT_GNU_STACK ` program header and the ` PF_X ` flag set or with the absence of
477
- this header, resulting in not only the stack, but also all readable virtual
478
- memory mappings being executable.
458
+ executable, and the absence of this header indicates that the stack should be
459
+ executable. However, the Linux kernel currently sets the ` READ_IMPLIES_EXEC `
460
+ personality upon loading any executable with the ` PT_GNU_STACK ` program header
461
+ and the ` PF_X ` flag set or with the absence of this header, resulting in not
462
+ only the stack, but also all readable virtual memory mappings being executable.
479
463
480
464
An attempt to fix this [ was made in
481
465
2012] ( https://lore.kernel.org/lkml/f298f914-2239-44e4-8aa1-a51282e7fac0@zmail15.collab.prod.int.phx2.redhat.com/ ) ,
482
466
and another [ was made in
483
467
2020] ( https://lore.kernel.org/kernel-hardening/20200327064820.12602-1-keescook@chromium.org/ ) .
484
468
The former never landed, and the latter partially fixed it, but introduced
485
- other issues—the absence of the ` PT_GNU_STACK ` program header still causes
486
- not only the stack, but also all readable virtual memory mappings to be
487
- executable in some architectures, such as IA-32 and equivalent (or causes
488
- the stack to be non-executable in some architectures, such as AMD64 and
489
- equivalent, contradicting the LSB).
490
-
491
- The ` READ_IMPLIES_EXEC ` personality needs to be completely separated from
492
- the ` PT_GNU_STACK ` program header by having a separate option for it (or
493
- setarch -X could just be used whenever ` READ_IMPLIES_EXEC ` is needed), and
494
- the absence of the ` PT_GNU_STACK ` program header needs to have more secure
495
- defaults (unrelated to ` READ_IMPLIES_EXEC ` ).
469
+ other issues—the absence of the ` PT_GNU_STACK ` program header still causes not
470
+ only the stack, but also all readable virtual memory mappings to be executable
471
+ in some architectures, such as IA-32 and equivalent (or causes the stack to be
472
+ non-executable in some architectures, such as AMD64 and equivalent,
473
+ contradicting the LSB).
474
+
475
+ The ` READ_IMPLIES_EXEC ` personality needs to be completely separated from the
476
+ ` PT_GNU_STACK ` program header by having a separate option for it (or setarch -X
477
+ could just be used whenever ` READ_IMPLIES_EXEC ` is needed), and the absence of
478
+ the ` PT_GNU_STACK ` program header needs to have more secure defaults (unrelated
479
+ to ` READ_IMPLIES_EXEC ` ).
496
480
497
481
498
482
## References
0 commit comments