---
# try also 'default' to start simple
theme: default
# random image from a curated Unsplash collection by Anthony
# like them? see https://unsplash.com/collections/94734566/slidev
# background: https://source.unsplash.com/collection/94734566/1920x1080
# apply any windi css classes to the current slide
class: 'text-center'
# https://sli.dev/custom/highlighters.html
highlighter: shiki
# show line numbers in code blocks
lineNumbers: false
# persist drawings in exports and build
drawings:
  persist: false
# use UnoCSS (experimental)
css: unocss

routerMode: 'hash'
---

# Control-flow integrity

<!--
The last comment block of each slide will be treated as slide notes. It will be visible and editable in Presenter Mode along with the slide. [Read more in the docs](https://sli.dev/guide/syntax.html#notes)
-->

<style>
h1 {
  background-color: #2B90B6;
  background-image: linear-gradient(45deg, #4EC5D4 10%, #146b8c 20%);
  background-size: 100%;
  -webkit-background-clip: text;
  -moz-background-clip: text;
  -webkit-text-fill-color: transparent;
  -moz-text-fill-color: transparent;
}
</style>

---
layout: 'intro'
---

<h1 text="!5xl">MaskRay (宋方睿)</h1>

<div class="leading-8 opacity-80">
<a href="https://maskray.me/portfolio/llvm/">LLVM contributor since 2017</a>, ld.lld and Clang Driver code owner, maintainer of a bunch of components<br>
binutils, glibc, GCC<br>
</div>

<div class="my-10 grid grid-cols-[40px_1fr] w-min gap-y-4">
  <ri-github-line class="opacity-50"/>
  <div><a href="https://github.com/MaskRay" target="_blank">MaskRay</a></div>
  <ri-user-3-line class="opacity-50"/>
  <div><a href="https://maskray.me" target="_blank">maskray.me</a></div>
</div>

<!-- <img src="/img/me.jpg" class="rounded-full size-200px object-cover-top abs-tr mt-16 mr-12"/> -->

---

## What is control-flow integrity?

* _Control-flow graph_ (CFG) is a graph representation of all paths that might be traversed through a program during its execution.
  + conservative approximation of the control flow
* _Control-flow integrity_ (CFI) is security policy dictating that program execution must follow a control-flow graph.
* There are many CFI schemes in research papers. This talk focuses on schemes implemented in GCC and Clang.

---

## backward-edge and forward-edge

* backward-edge (simple): function returns
* forward-edge: function calls. We focus on indirect calls (function pointers, virtual functions)
* not categorized: exception handling and symbol interposition

---
layout: 'intro'
---

<h1 text="!5xl">Backward-edge CFI</h1>

* Coarse-grained: we are returning to the caller
* Fine-grained: the function return corresponds to an edge in the CFG (redundant if we enforce forward-edge CFI)

---

## StackGuard/Stack Smashing Protector

* _StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks_ (1998) introduced an approach to detect tampered return addresses on the stack.
* GCC 4.1 [implemented](https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=7d69de618e732d343228a07d797a30e39a6363f4) `-fstack-protector` and `-fstack-protector-all`.

---

```c
void foo(const char *a) {
  char s[10];
  strcpy(s, a);
  puts(s);
}
```
```asm
  movq    %fs:40, %rax     # load the canary from the thread control block
  movq    %rax, 24(%rsp)   # place the value before the return address
  ...
  movq    %fs:40, %rax     # load the canary again
  cmpq    24(%rsp), %rax   # compare it with the entry before the return address
  jne     .LBB0_2          # fail if mismatching
  <epilogue>
.LBB0_2:
  callq   __stack_chk_fail@PLT
```

---

## Retguard

OpenBSD introduced Retguard in 2017. See [RETGUARD and stack canaries](https://isopenbsdsecu.re/mitigations/retguard/).
Retguard is more fine-grained than StackGuard, but it has more expensive function prologues and epilogues.

For each instrumented function, a cookie is allocated from a pool of 4000 entries.
In the prologue, `return_address ^ cookie` is pushed next to the return address (similar to [XOR Random Canary](http://phrack.org/issues/56/5.html)).
The epilogue pops the XOR value and the return address and verifies that they match (`(value ^ cookie) == return_address`).

Not encrypting the return address directly is important to preserve return address prediction for the CPU.
The two `int3` instructions are to disrupt ROP gadgets which may form from `je ...; retq` (`02 cc cc c3` is `addb %ah, %cl; int3; retq`).
([ROP gadgets removal](https://isopenbsdsecu.re/mitigations/rop_removal/) says that the gadget removal may not be useful.)
If a static branch predictor exists (likely not-taken for a forward branch) the initial prediction is likely wrong.

---

```asm
// From https://www.openbsd.org/papers/eurobsdcon2018-rop.pdf
// prologue
ffffffff819ff700: 4c 8b 1d 61 21 24 00 mov 2367841(%rip),%r11 # <__retguard_2759>
ffffffff819ff707: 4c 33 1c 24          xor (%rsp),%r11
ffffffff819ff70b: 55                   push %rbp
ffffffff819ff70c: 48 89 e5             mov %rsp,%rbp
ffffffff819ff70f: 41 53                push %r11

// epilogue
ffffffff8115a457: 41 5b                pop %r11
ffffffff8115a459: 5d                   pop %rbp
ffffffff8115a45a: 4c 33 1c 24          xor (%rsp),%r11
ffffffff8115a45e: 4c 3b 1d 03 74 ae 00 cmp 11432963(%rip),%r11 # <__retguard_2759>
ffffffff8115a465: 74 02                je ffffffff8115a469
ffffffff8115a467: cc                   int3
ffffffff8115a468: cc                   int3
ffffffff8115a469: c3                   retq
```

---

## SafeStack

[Code-Pointer Integrity](https://dslab.epfl.ch/pubs/cpi.pdf) (2014) proposed stack object instrumentation, which was merged into LLVM in 2015.
To use it, you can run `clang -fsanitize=safe-stack`. In a link action, the driver option links in the runtime `compiler-rt/lib/safestack`.

The pass moves some stack objects into a separate stack, which is normally referenced by a thread-local variable `__safestack_unsafe_stack_ptr` or via a function call `__safestack_pointer_address`.
These objects include those that are not guaranteed to be free of stack smashing, mainly via `ScalarEvolution`.

As an example, the local variable `a` below is moved to the unsafe stack as there is a risk that `bar` may have out-of-bounds accesses.

---

```c
void bar(int *);
void foo() {
  int a;
  bar(&a);
}
```

```asm
foo:                                    # @foo
# %bb.0:                                # %entry
        pushq   %r14
        pushq   %rbx
        pushq   %rax
        movq    __safestack_unsafe_stack_ptr@GOTTPOFF(%rip), %rbx
        movq    %fs:(%rbx), %r14
        leaq    -16(%r14), %rax
        movq    %rax, %fs:(%rbx)
        leaq    -4(%r14), %rdi
        callq   bar@PLT
        movq    %r14, %fs:(%rbx)
        addq    $8, %rsp
        popq    %rbx
        popq    %r14
        retq
```

---

## Shadow stack (userspace or hardware-assisted)

During a function call, the return address is stored in a shadow stack. The normal stack may contain a copy, or (as a variant) not at all.
Upon return, an entry is popped from the shadow stack. It is either used as the return address or (as a variant) compared with the normal return address.

Below we describe some userspace and hardware-assisted schemes.

---

## `-fsanitize=shadow-call-stack` (userspace)

See <https://clang.llvm.org/docs/ShadowCallStack.html>.
The instrumentation stores the return address in a shadow stack during a function call.
Upon return, the return address is popped from the shadow stack.
The return address is also stored on the regular stack for return address prediction and compatibility with unwinders, but is otherwise unused.

To use this for AArch64, run `clang --target=aarch64-unknown-linux-gnu -fsanitize=shadow-call-stack -ffixed-x18`.
```asm
str	x30, [x18], #8      // push the return address to the shadow stack
sub	sp, sp, #32
stp	x29, x30, [sp, #16] // the normal stack contains a copy as well
...
ldp	x29, x30, [sp, #16]
add	sp, sp, #32
ldr	x30, [x18, #-8]!    // pop the return address from the shadow stack
ret
```

GCC [ported](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102768) the feature in 2022-02 (milestone: 12.0).

In the Linux kernel, select `CONFIG_SHADOW_CALL_STACK` to use this scheme.

---

RISC-V

When using RVC, the push operation on the shadow stack uses 6 bytes.
In the future, x3 (gp) is another choice and reserving it as a platform register will be helpful.

```asm
// clang --target=riscv64-unknown-linux-gnu -fsanitize=shadow-call-stack -ffixed-x18
sd      ra, 0(s2)       // push the return address to the shadow stack
addi    s2, s2, 8
addi    sp, sp, -32
sd      ra, 24(sp)      // the normal stack contains a copy as well
...
addi    sp, sp, 32
ld      ra, -8(s2)      // pop the return address from the shadow stack
addi    s2, s2, -8
ret
```

---

## Intel Control-flow Enforcement Technology and AMD Shadow Stack

Supported by Intel's 11th Gen and AMD Zen 3.

A RET instruction pops the return address from both the regular stack and the shadow stack, and compares them.
A control protection exception (#CP) is raised in case of a mismatch.

If all relocatable files with `.note.gnu.property` have set the `GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit, or `-z force-ibt` is specified, the output will have the bit.

On Windows the scheme is branded as Hardware-enforced Stack Protection.
In the MSVC linker, `/cetcompat` marks an executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack.

`setjmp/longjmp` need to save and restore the shadow stack pointer.

---

## Armv8.3 Pointer Authentication

Instructions are provided to sign a pointer with a 64-bit user-chosen context value (usually zero, X16, or SP) and a 128-bit secret key.
The computed Pointer Authentication Code is stored in the unused high bits of the pointer.
The instructions are allocated from the HINT space for compatibility with older CPUs.

A major use case is to sign/authenticate the return address.
`paciasp` is inserted at the start of the function prologue to sign the to-be-saved LR (X30). (It serves as an implicit `bti c` as well.)
`autiasp` is inserted at the end of the function prologue to authenticate the loaded LR.

```asm
paciasp                        # instrumented
sub     sp, sp, #0x20
stp     x29, x30, [sp, #0x10]
...
ldp     x29, x30, [sp, #0x10]
add     sp, sp, #0x20
autiasp                        # instrumented
ret
```

ld.lld added support in <https://reviews.llvm.org/D62609> (with substantial changes afterwards).
If `-z pac-plt` is specified, `autia1716` is used for a PLT entry; a relocatable file with `.note.gnu.property` and the `GNU_PROPERTY_AARCH64_FEATURE_1_PAC` bit cleared gets a warning.

---
layout: 'intro'
---

<h1 text="!5xl">Forward-edge CFI</h1>

```cpp
void (*indirect)(void);
indirect(); // is indirect a pointer to a function of type void (void) ?

A *a;
a->f(); // of a derived type of A or of an unrelated type?
```

---

## `pax-future.txt`

<https://pax.grsecurity.net/docs/pax-future.txt> (c.2) describes a scheme where

* a call site provides a hash indicating the intended function prototype
* the callee's epilogue checks whether the hash matches its own prototype

In case of a mismatch, it indicates that the callee does not have the intended prototype.
The program should terminate.

```asm
callee
epilogue:     mov register,[esp]
              cmp [register+1],MAGIC
              jnz .1
              retn
          .1: jmp esp

caller:
              call callee
              test eax,MAGIC
```

The epilogue assumes that a call site hash is present, so an instrumented function cannot be called by a non-instrumented call site.

---

## `-fsanitize=cfi`

See <https://clang.llvm.org/docs/ControlFlowIntegrity.html>.
Clang has implemented the traditional indirect function call check and many C++ specific checks under this umbrella option.
They all rely on [Type Metadata](https://llvm.org/docs/TypeMetadata.html) and link-time optimizations: LTO collects functions with the same signature so that a type check can be performed efficiently.

For virtual calls, related virtual tables are placed together in the object file (this requires LTO).
After loading the dynamic type of an object, `-fsanitize=cfi` can efficiently check whether the type matches an address point of possible virtual tables.

---

## `-fsanitize=kcfi`

Introduced to llvm-project in [2022-11](https://reviews.llvm.org/D119296) (milestone: 16.0.0. Glad as a reviewer).
GCC [feature request](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107048).

The instrumentation does:

* store a hash of the function prototype before the function entry.
* in an indirect call site, load the hash, trap if it does not match the expected prototype.
* record the trap location in a section named `.kcfi_traps`. The Linux kernel uses the section to check whether a trap is caused by KCFI.
* define a weak absolute symbol `__kcfi_typeid_<func>` if the function has a C identifier name and is address-taken.

---

```asm
__cfi_bar:
	.rept 11; nop; .endr
	movl	$27004076, %eax                 # imm = 0x19C0CAC
bar:
	retq

__cfi_foo:
	.rept 11; nop; .endr
	movl	$2992198919, %eax               # imm = 0xB2595507
foo:
	movq	%rdi, %rax
	movl	$42, %edi
	movl	$4267963220, %r10d              # imm = 0xFE63F354
	addl	-4(%rax), %r10d
	je	.Ltmp0
.Ltmp1:
	ud2
	.section	.kcfi_traps,"ao",@progbits,.text
.Ltmp2:
	.long	.Ltmp1-.Ltmp2
	.text
.Ltmp0:
	jmpq	*%rax                           # TAILCALL
```

---

## FineIBT

This is a instrumentation and hardware hybrid scheme. It uses both an indirect branch target indicator (ENDBR) and a prototype hash check.
See <https://dustri.org/b/paper-notes-fineibt.html> for an analysis.

`testb 0x11, fs:0x48` is a compromise that it can be disabled at run-time.
`fs:0x48` will take an unused field `tcbhead_t:unused_vgetcpu_cache` in `sysdeps/x86_64/nptl/tls.h`.
This allows an attacker to disable FineIBT by zeroing this byte.

[x86/ibt: Implement FineIBT](https://git.kernel.org/linus/89245600941e4e0f87d77f60ee269b5e61ef4e49) is the Linux kernel implementation which patches `-fsanitize=kcfi` output into FineIBT.

---

## Intel Indirect Branch Tracking (hardware, coarse-grained)

When enabled, the CPU ensures that every indirect branch lands on a special instruction (ENDBR, `endbr32` for x86-32 and `endbr64` for x86-64), otherwise a control-protection (#CP) exception is raised.
A jump/call instruction with the `notrack` prefix skips the check.

With `-fcf-protection={branch,full}`, the compiler inserts ENDBR at the start of a basic block which may be reached indirectly.
This is conservative:

* every address-taken basic block needs ENDBR
* every non-internal-linkage function needs `endbr` as it may be reached via PLT
* every function compiled for the large code model needs ENDBR as it may be reached via a large code model branch
* landing pads for exception handling need ENDBR

---

## Armv8.5 Branch Target Identification (hardware, coarse-grained)

This is similar to Intel Indirect Branch Tracking.
When enabled, the CPU ensures that every indirect branch lands on a special instruction, otherwise a Branch Target exception is raised.
The most common landing instructions are `bti {c,j,jc}` with different branch instruction compatibility.
`paciasp` and `pacibsp` are implicitly `bti c`.

This feature is more fine-grained: every memory page may set a bit `VM_ARM64_BTI` (set with the mmap flag `PROT_BTI`) to indicate that indirect calls from it do not need protection.
This allows uninstrumented code to mapped simply by skipping `PROT_BTI`.

---

## Compiler warnings

`clang -Wcast-function-type` warns when a function pointer is cast to an incompatible function pointer.
Calling such a cast function pointer likely leads to `-fsanitize=cfi`/`-fsanitize=kcfi` runtime errors.
For practical reasons, GCC made a choice to allow some common violations.

Clang 16.0.0 makes `-Wcast-function-type` [stricter](https://reviews.llvm.org/D134831) and warns more cases.
`-Wno-cast-function-type-strict` restores the previous state that ignores many cases including some ABI equivalent cases.
