Skip to content

Commit

Permalink
Merge pull request #18 from knurling-rs/readme-problem-solution
Browse files Browse the repository at this point in the history
README: explain stack overflow problem & solution
  • Loading branch information
japaric authored Dec 10, 2020
2 parents 62ac1cd + bc204c0 commit 535efdd
Show file tree
Hide file tree
Showing 3 changed files with 736 additions and 0 deletions.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,62 @@

> adds zero-cost stack overflow protection to your embedded programs
## The problem

Bare metal Rust programs may *not* be memory safe in presence of stack overflows.
For example, this is the case for Rust programs based on v0.6.x of the `cortex-m-rt` crate.

The following program, which contains no `unsafe` code block, can run into *undefined behavior* if it reaches a stack overflow condition.

``` rust
// static variables placed in the .bss / .data sections
static FLAG1: AtomicBool = AtomicU32::new(false); // .bss
static FLAG2: AtomicBool = AtomicU32::new(true); // .data

fn main() {
let _x = fib(100);
}

#[inline(never)]
fn fib(n: u32) -> u32 {
// allocate and initialize one kilobyte of stack memory
let _use_stack = [0xAA; 1024];

if n < 2 {
1
} else {
fib(n - 1) + fib(n - 2) // recursion
}
}

#[interrupt]
fn interrupt_handler() {
// does some operation with `FLAG1` and `FLAG2`
}
```

The default memory layout of ARM Cortex-M programs in RAM is shown below.

<p align="center">
<img src="assets/overflow.svg" alt="left: default memory layout of ARM Cortex-M programs; right: stack overflow condition">
</p>

The function call stack, also known as the "stack", grows downwards on function calls and when local variables (e.g. `let x`) are created (these variables are also placed on the stack).

If the stack grows too large it collides with the `.bss + .data` region, which contains all the program's static variables. The collision results in the static variables being overwritten with unrelated data. This can result in the program observing the static variables in an invalid state: for example an `AtomicBool` may hold the value `3` -- this is undefined behavior because the Rust ABI expects this single-byte variable to be either `0` or `1`.

## The solution

One potential solution is to change the memory layout of the program and place the stack *below* the `.bss+.data` region.

With this flipped memory layout (pictured below) the stack cannot collide with the static variables. Instead it will collide with the boundary of the physical RAM memory region. In the ARM Cortex-M architecture, trying to read or write pass the boundaries of the RAM region produces a "hardware exception". The `cortex-m-rt` crate provides an API to handle this condition: a `HardFault` exception handler can be defined; this "handler" (function) will be executed when the invalid memory operation is attempted.

<p align="center">
<img src="assets/flipped.svg" alt="left: flipped memory layout; right: stack overflow condition">
</p>

`flip-link` implements this stack overflow solution. Linking your program with `flip-link` produces the flipped memory layout, which is memory safe in presence of stack overflows.

## Architecture support

`flip-link` is known to work with ARM Cortex-M programs that link to version `0.6.x` of the [`cortex-m-rt`] crate and are linked using the linker shipped with the Rust toolchain (LLD).
Expand Down
331 changes: 331 additions & 0 deletions assets/flipped.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 535efdd

Please sign in to comment.