Title: Paper Notes: Twenty years later: Evaluating the Adoption of Control Flow Integrity
Date: 2025-01-16 18:00

- PDF: [Twenty years later: Evaluating the Adoption of Control Flow Integrity](https://dl.acm.org/doi/pdf/10.1145/3702982))

The paper starts by calling Aleph One. 1996. Smashing the stack for fun and
profit "One of the first memory corruption attacks described in the
literature", ROP a "new attack technique", goes on saying that "In practice,
backward-edge CFI is named ShadowCallStack", and "Stack-based buffer overflow
vulnerabilities are prominent targets for attacks." So we're off to a great
start.

The paper is about SeeCFI, a [python
tool](https://github.com/software-engineering-and-security/SeeCFI/) based on
[angr](https://angr.io/) to statically detect the presence/absence of CFI in
given binaries via a "generic" approach. The approach isn't generic at all, as
it only considers clang CFI on aarch64:

- for backward-edge CFI it checks if `str x18, <ret_addr>` is present in the prologue of
  functions, and if `ldr <ret_addr>, x18>` in the epilogue.
- for forward-edge:
    - for cross-dso, it checks the presence of the `__cfi_check` and
      `__cfi_slowpath` symbols [by using
      `grep`](https://github.com/software-engineering-and-security/SeeCFI/blob/main/checker/MultiModuleCFIChecker.py) (!!)
    - for non-cross-dso, it builds the complete CFG of the binary, to then checks if all branching nodes with 2 possible
      targets land in blocks with either an `ud` or an `call` instruction, if
      the register used in the branch is used in the `call`
      instruction. If it happens at least once single, then it's a win.

The paper doesn't consider non "memory-unsafe" binaries, which is odd, but it
seems that the authors aren't aware that those libraries/binaries also contains
valid gadgets:

> Android developers started moving from writing low-level code in
memory-unsafe languages such as C and C++ to the memory-safe language Rust,
eliminating the need for CFI completely

Horrifyingly, "On average SeeCFI needed 1.5 minutes to analyze each binary,
resulting in an overall runtime of, for instance, 45.8 hours in the case of
Android 13 (Pixel 7 Pro).", which is likely due to the fact that they're
lifting the whole CFG of every single binary for no reason, instead of doing it
lazily block by block.

Also, some binaries didn't load, and the diagnostic was "Most of the errors
occur due to an angr issue", without explaining what the issue is/was, nor if
they sent some patches to get it fixed.

The paper considering only clang type-based CFI is absurdly bad, as the ecosystem is
moving to ARMv9
[BTI/PAC-RET](https://developer.android.com/ndk/guides/abis#armv9_enabling_pac_and_bti_for_cc)
as an additional layer.
For example, BTI/PAC is enabled for all userland and kernel-land on GrapheneOS,
which isn't the case on stock Android. This should have been obvious when they
wrote "GrapheneOS adds ∼16.7% to the Pixel 3 series, ∼14.2% of binaries to the
Pixel 4 and 5 series, and <1% to the Pixel 6 and 7 series. Out of the
additional binaries ∼14.5% (Pixel 3 series), ∼11.5% (Pixel 4 and 5 series), and
0% (Pixel 6 and 7 series) are compiled using forward-edge CFI."

This shouldn't have been a paper, let alone published in the "ACM Transactions
on Software Engineering and Methodology", it's at best two weeks of work by
an intern. Heck, the GrapheneOS project [published a detailed analysis on the topic](https://discuss.grapheneos.org/d/19060-information-on-cfi-in-grapheneos-and-response-to-a-paper)
as a rebuttal in a couple of hours, and it's way better than the paper itself.
