1.. SPDX-License-Identifier: GPL-2.0 2 3:Author: Deepak Gupta <debug@rivosinc.com> 4:Date: 12 January 2024 5 6==================================================== 7Tracking indirect control transfers on RISC-V Linux 8==================================================== 9 10This document briefly describes the interface provided to userspace by Linux 11to enable indirect branch tracking for user mode applications on RISC-V. 12 131. Feature Overview 14-------------------- 15 16Memory corruption issues usually result in crashes. However, in the 17hands of a creative adversary, these can result in a variety of 18security issues. 19 20Some of those security issues can be code re-use attacks, where an 21adversary can use corrupt function pointers, chaining them together to 22perform jump oriented programming (JOP) or call oriented programming 23(COP) and thus compromise control flow integrity (CFI) of the program. 24 25Function pointers live in read-write memory and thus are susceptible 26to corruption. This can allow an adversary to control the program 27counter (PC) value. On RISC-V, the zicfilp extension enforces a 28restriction on such indirect control transfers: 29 30- Indirect control transfers must land on a landing pad instruction ``lpad``. 31 There are two exceptions to this rule: 32 33 - rs1 = x1 or rs1 = x5, i.e. a return from a function and returns are 34 protected using shadow stack (see zicfiss.rst) 35 36 - rs1 = x7. On RISC-V, the compiler usually does the following to reach a 37 function which is beyond the offset of possible J-type instruction:: 38 39 auipc x7, <imm> 40 jalr (x7) 41 42 This form of indirect control transfer is immutable and doesn't 43 rely on memory. Thus rs1=x7 is exempted from tracking and 44 these are considered software guarded jumps. 45 46The ``lpad`` instruction is a pseudo-op of ``auipc rd, <imm_20bit>`` 47with ``rd=x0``. This is a HINT op. The ``lpad`` instruction must be 48aligned on a 4 byte boundary. It compares the 20 bit immediate with 49x7. If ``imm_20bit`` == 0, the CPU doesn't perform any comparison with 50``x7``. If ``imm_20bit`` != 0, then ``imm_20bit`` must match ``x7`` 51else CPU will raise ``software check exception`` (``cause=18``) with 52``*tval = 2``. 53 54The compiler can generate a hash over function signatures and set them 55up (truncated to 20 bits) in x7 at callsites. Function prologues can 56have ``lpad`` instructions encoded with the same function hash. This 57further reduces the number of valid program counter addresses a call 58site can reach. 59 602. ELF and psABI 61----------------- 62 63The toolchain sets up :c:macro:`GNU_PROPERTY_RISCV_FEATURE_1_FCFI` for 64property :c:macro:`GNU_PROPERTY_RISCV_FEATURE_1_AND` in the notes 65section of the object file. 66 673. Linux enabling 68------------------ 69 70User space programs can have multiple shared objects loaded in their 71address spaces. It's a difficult task to make sure all the 72dependencies have been compiled with indirect branch support. Thus 73it's left to the dynamic loader to enable indirect branch tracking for 74the program. 75 764. prctl() enabling 77-------------------- 78 79:c:macro:`PR_SET_INDIR_BR_LP_STATUS` / :c:macro:`PR_GET_INDIR_BR_LP_STATUS` / 80:c:macro:`PR_LOCK_INDIR_BR_LP_STATUS` are three prctls added to manage indirect 81branch tracking. These prctls are architecture-agnostic and return -EINVAL if 82the underlying functionality is not supported. 83 84* prctl(PR_SET_INDIR_BR_LP_STATUS, unsigned long arg) 85 86If arg1 is :c:macro:`PR_INDIR_BR_LP_ENABLE` and if CPU supports 87``zicfilp`` then the kernel will enable indirect branch tracking for the 88task. The dynamic loader can issue this :c:macro:`prctl` once it has 89determined that all the objects loaded in the address space support 90indirect branch tracking. Additionally, if there is a `dlopen` to an 91object which wasn't compiled with ``zicfilp``, the dynamic loader can 92issue this prctl with arg1 set to 0 (i.e. :c:macro:`PR_INDIR_BR_LP_ENABLE` 93cleared). 94 95* prctl(PR_GET_INDIR_BR_LP_STATUS, unsigned long * arg) 96 97Returns the current status of indirect branch tracking. If enabled 98it'll return :c:macro:`PR_INDIR_BR_LP_ENABLE` 99 100* prctl(PR_LOCK_INDIR_BR_LP_STATUS, unsigned long arg) 101 102Locks the current status of indirect branch tracking on the task. User 103space may want to run with a strict security posture and wouldn't want 104loading of objects without ``zicfilp`` support in them, to disallow 105disabling of indirect branch tracking. In this case, user space can 106use this prctl to lock the current settings. 107 1085. violations related to indirect branch tracking 109-------------------------------------------------- 110 111Pertaining to indirect branch tracking, the CPU raises a software 112check exception in the following conditions: 113 114- missing ``lpad`` after indirect call / jmp 115- ``lpad`` not on 4 byte boundary 116- ``imm_20bit`` embedded in ``lpad`` instruction doesn't match with ``x7`` 117 118In all 3 cases, ``*tval = 2`` is captured and software check exception is 119raised (``cause=18``). 120 121The kernel will treat this as :c:macro:`SIGSEGV` with code = 122:c:macro:`SEGV_CPERR` and follow the normal course of signal delivery. 123