[PATCH v1 RFC Zisslpcfi 00/20] riscv control-flow integrity for U mode

Deepak Gupta
2023-02-13T04:54:17Z

Hello All,

I've been working on linux support for shadow stack and landing pad
instruction on riscv for a while.

These are still RFC quality. But atleast they're in a shape which can
start a discussion and I can get some feedback. So I decided to sending
out patches.

This patch series implements `zisslpcfi` extension which helps software
to enforce control-flow integrity (cfi) on riscv CPUs. Currently spec is
called zisslpcfi [1] spec. This literally means "unprivileged integer
shadow stack & landing pad based control-flow integrity".

Three major architectures [1, 2, 3] either already support or have
indicated support for control flow integrity extensions based on shadow
stack (for backward edge) and landing pads for indirect call/jmp (for
forward edge). Since these mechanisms are solving one common security
issue (control flow hijacking attacks) plaguing software ecosystem,
there're bound to be common similarities and thus need of abstracting
or having common code. These commonalities are:
- Concept of shadow stack for program
- Protection of shadow stack from regular stores but still allowing
return address to be store via special mechanism(s)
- Concept of indirect branch tracking and thus landing pad instruction
as target for indirect branches.

Due to commonality in three arches for shadow stack and landing pad instr
support, patch series defines following arch-agnostic configs:
- CONFIG_USER_SHADOW_STACK: Selecting this config means kernel will
have support for programs compiled with optin of shadow stacks.
- CONFIG_USER_INDIRECT_BR_LP: Selecting this config means kernel will
have support for program compiled with optin of landing pad
instructions on indirect branches.

There're stubs for abstraction on non-riscv architectures and specific
implementation for riscv architecture. Architecture owners can implement
those stubs abstractions on respective architectures to implement shadow
stack and branch tracking mechanism.

Additional highlights of this patch series specifically targeted towards
implementation of `zisslpcfi`

Shadow stack and landing pad state
----------------------------------
On riscv I choose to insert shadow stack and landing pad state in
thread_info. pt_regs is one choice but I didn't see a compelling reason
to put these in pt_regs. If you do have a compelling reason to put this
in pt_regs, we can make that change.

Shadow stack
------------
Shadow stack is already present on x86 [2] and is going to be part of
riscv and aarch64 [3] architecture. Since shadow stack are writeable
memory but at the same time are not allowed to be writeable by regular
stores. They have special meaning and this patch series proposes a new
`mmap` protection flag `PROT_SHADOWSTACK`. Repsective architecture can
choose to implement this memory protection. With respect to vma flags,
riscv implementation chooses to have only `VM_WRITE` as shadow stack
meaning and is analogous to riscv architecture's PTE encodings
(X=0,R=0,W=1) as shadow stack. On riscv, all stores to shadow stack
memory raise access store faults. Similarly all shadow stack load and
stores on non-shadow stack memory (but valid) will raise access
load/store faults. This patch series creates a synthetic exception code
(=14) which is reserved in privileged spec and feeds that into common
page fault handling routine.

ELF parsing
------------
There're two ways to enable forward cfi and backward cfi
- prctls: `ld` can issue prctls to enable it on the runtime.
- elf marker: Some sort of marker in elf header which kernel can recognize
and setup forward and backward cfi state.

This patch series uses .note.gnu.property section and assumes two flags
exist there for forward cfi and backward cfi, if present kernel setups
the respective cfi state.

Please note that this part will change because risc-v is moving towards
using `.riscv.attributes` section to host such flags. So I'll change the
implementation in future revisions.

Signal & ucontext
------------------
This patch series steals 4(32bit)/8(64bit) bytes from a padding structure
in ucontext. This padding exist for future use case which we don't know yet.
But stealing few bytes from this padding allows us to keep the structure
size same and save some compatiblity issues. Signal patches still need
some work (have to work out a shadow stack token save on signal delivery
and restore mechanism on sigreturn. This will prevent any abuse of
sigreturns to hijack control flow)

Audit mode
-----------
Since this is a RFC, current set of patches suppresses cfi violations in
program and let the program make forward progress. However this shouldn't
be allowed by default and can be built into a policy.

More on `zisslpcfi` riscv extension
------------------------------------

zisslpcfi (CFI) extends ISA in following manner:

Forward cfi (indirect call/jmp)
- Landing pad instruction requirement for indirect call/jmp
All indirect call and jump must land on landing pad instruction `lpcll`
else CPU will raise illegal instruction exception. `lpcll` stands for
land pad check lower label.

- Static label (25bit label) checking instructions for indirect call/jmp
Extension provides mechanism using which a compiler generated label
value can be set in a designated CSR at call site and it can be checked
at the call target. If mismatch happens, CPU will raise illegal
instruction exception. Compiler can generate hash based on function
signature type. Extension provide mechanisms using which label value
is part of the instruction itself as immediate and thus has static
immutable property.

Backward cfi (returns)
- Shadow stack (SS) for function returns
Extension provides sspush x1/x5, sspop x1/x5 and sschkra instructions.
sspush will push on shadow stack while sspop will pop from shadow stack
sschkra will succeed only when x1 == x5 is true else it will raise
illegal instruction exception. Shadow stacks introduces new virtual
memory type and thus new PTE encodings. Existing reserved encoding of
R=0,W=1,X=0 is now shadow stack PTE encoding (only if backward cfi is
enabled for current mode). New virtual memory type allows CPU to
distinguish so that stores coming from sspush or ssamoswap can succeed
while regular stores raise access violations.

opcodes:
zisslpcfi opcodes are carved out of new opcode encodings. These opcodes
encodings were reserved until now. A new extension called zimops make
these opcodes into "may be operations". zimops stands for unprivileged
may be operations (mops) and if implemented default behavior is to mov 0
to rd. zisslpcfi extension changes executable in a way where it should be
able to run on riscv cpu which implements cfi extension as well as riscv
cpu which doesn't implement cfi extension. As long as zimops is
implemented, all such instructions will not fault and simply move 0 to rd.
A hart implementing cfi must implement zimops. Any future extension can
re-purpose zimops to change behavior and claim them while also not
breaking binary/executable compatiblity . zisslpcfi is first such
extension to modify zimops behavior.

Instructions:
zisslpcfi defines following instructions.

Backward control flow:

sspush x1/x5:
Decrement shadow stack pointer and pushes x1 or x5 on shadow stack.

sspop x1/x5:
Pops from shadow stack into x1 or x5. Increments shadow stack pointer.

ssprr:
Reads current shadow stack pointer into a destination register.

sschckra:
Compares x1 with x5. Raises illegal instr exception if x1 != x5.

ssamoswap:
Atomically swaps value on top of shadow stack.

Forward control flow:

Forward control flow extends architecture to allow software to set labels
(25bits of label) at call/jmp site and check labels at target. Extension
gives instructions to set label as part of immediate in instruction itself
. Since immediate is limited in terms of bit length, labels are set and
checked in ladder fashion of 9, 8 and 8 bits.

lpsll, lpsml, lpsul:
sets lower (9bit), mid (8bit) and upper (8bit) label values in CSR_LPLR
respectively.

lpcll, lpcml, lpcul:
checks lower (9bit), mid (8bit) and upper (8bit) label values with
CSR_LPLR respectively. Check label instructions raise illegal instruction
fault when labels mismatch. `lpcll` has dual purpose; it acts as landing
pad instruction as well label checking for lower 9 bits.

Tests and other bits
********************
For convenience this patch has been tested with followng qemu impl.
https://github.com/deepak0414/qemu/tree/scfi_menvcfg_gh_Zisslpcfi-0.1

I've been able to boot linux kernel using this implementation and run
very basic simple tests apps. For convenience here is the branch which
has implementation.
https://github.com/deepak0414/linux-riscv-cfi/tree/Zisslpcfi-0.4_v6.1-rc2

In order to perform unit-tests on qemu-impl, I've been using riscv-test
and created unit tests to test implementation. riscv-tests branch URL is
below
https://github.com/deepak0414/riscv-tests/tree/cfi_tests

[1] - https://github.com/riscv/riscv-cfi
[2] - https://www.intel.com/content/dam/develop/external/us/en/documents/catc17-introduction-intel-cet-844137.pdf
[3] - https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/arm-a-profile-architecture-2022

Deepak Gupta (20):
sslp stubs: shadow stack and landing pad stubs
riscv: zisslpcfi enumeration
riscv: zisslpcfi extension csr and bit definitions
riscv: kernel enabling user code for shadow stack and landing pad
mmap : Introducing new protection "PROT_SHADOWSTACK" for mmap
riscv: Implementing "PROT_SHADOWSTACK" on riscv
elf: ELF header parsing in GNU property for cfi state
riscv: ELF header parsing in GNU property for riscv zisslpcfi
riscv mmu: riscv shadow stack page fault handling
riscv mmu: write protect and shadow stack
mmu: maybe_mkwrite updated to manufacture shadow stack PTEs
riscv mm: manufacture shadow stack pte and is vma shadowstack
riscv: illegal instruction handler for cfi violations
riscv: audit mode for cfi violations
sslp prctl: arch-agnostic prctl for shadow stack and landing pad instr
riscv: Implements sslp prctls
riscv ucontext: adding shadow stack pointer field in ucontext
riscv signal: Save and restore of shadow stack for signal
config: adding two new config for control flow integrity
riscv: select config for shadow stack and landing pad instr support

arch/riscv/Kconfig | 4 +
arch/riscv/include/asm/csr.h | 28 ++++
arch/riscv/include/asm/elf.h | 54 ++++++++
arch/riscv/include/asm/hwcap.h | 6 +-
arch/riscv/include/asm/mman.h | 19 +++
arch/riscv/include/asm/pgtable.h | 21 ++-
arch/riscv/include/asm/processor.h | 26 ++++
arch/riscv/include/asm/thread_info.h | 5 +
arch/riscv/include/uapi/asm/ucontext.h | 32 ++++-
arch/riscv/kernel/asm-offsets.c | 5 +
arch/riscv/kernel/cpu.c | 1 +
arch/riscv/kernel/cpufeature.c | 1 +
arch/riscv/kernel/entry.S | 40 ++++++
arch/riscv/kernel/process.c | 155 +++++++++++++++++++++
arch/riscv/kernel/signal.c | 45 ++++++
arch/riscv/kernel/sys_riscv.c | 22 +++
arch/riscv/kernel/traps.c | 183 ++++++++++++++++++++++++-
arch/riscv/mm/fault.c | 23 +++-
arch/riscv/mm/init.c | 2 +-
arch/riscv/mm/pageattr.c | 7 +
fs/binfmt_elf.c | 5 +
include/linux/elf.h | 8 ++
include/linux/mm.h | 23 +++-
include/linux/pgtable.h | 4 +
include/linux/processor.h | 17 +++
include/uapi/asm-generic/mman-common.h | 6 +
include/uapi/linux/elf.h | 6 +
include/uapi/linux/prctl.h | 26 ++++
init/Kconfig | 19 +++
kernel/sys.c | 40 ++++++
mm/mmap.c | 4 +
31 files changed, 825 insertions(+), 12 deletions(-)
create mode 100644 arch/riscv/include/asm/mman.h