Tracking indirect control transfers on RISC-V Linux¶
This document briefly describes the interface provided to userspace by Linux to enable indirect branch tracking for user mode applications on RISC-V
1. Feature Overview¶
Memory corruption issues usually result into crashes, however when in hands of an adversary and if used creatively can result into a variety security issues.
One of those security issues can be code re-use attacks on program where adversary can use corrupt function pointers and chain them together to perform jump oriented programming (JOP) or call oriented programming (COP) and thus compromising control flow integrity (CFI) of the program.
Function pointers live in read-write memory and thus are susceptible to corruption and allows an adversary to reach any program counter (PC) in address space. On RISC-V zicfilp extension enforces a restriction on such indirect control transfers:
indirect control transfers must land on a landing pad instruction
lpad
. There are two exception to this rule:rs1 = x1 or rs1 = x5, i.e. a return from a function and returns are protected using shadow stack (see Shadow stack to protect function returns on RISC-V Linux)
rs1 = x7. On RISC-V compiler usually does below to reach function which is beyond the offset possible J-type instruction:
auipc x7, <imm> jalr (x7) Such form of indirect control transfer are still immutable and don't rely
on memory and thus rs1=x7 is exempted from tracking and considered software guarded jumps.
lpad
instruction is pseudo of auipc rd, <imm_20bit>
with rd=x0
and
is a HINT nop. lpad
instruction must be aligned on 4 byte boundary and
compares 20 bit immediate with x7. If imm_20bit
== 0, CPU doesn’t perform
any comparision with x7
. If imm_20bit
!= 0, then imm_20bit
must
match x7
else CPU will raise software check exception
(cause=18
)
with *tval = 2
.
Compiler can generate a hash over function signatures and setup them (truncated
to 20bit) in x7 at callsites and function prologues can have lpad
with same
function hash. This further reduces number of program counters a call site can
reach.
2. ELF and psABI¶
Toolchain sets up GNU_PROPERTY_RISCV_FEATURE_1_FCFI
for property
GNU_PROPERTY_RISCV_FEATURE_1_AND
in notes section of the object file.
3. Linux enabling¶
User space programs can have multiple shared objects loaded in its address space and it’s a difficult task to make sure all the dependencies have been compiled with support of indirect branch. Thus it’s left to dynamic loader to enable indirect branch tracking for the program.
4. prctl() enabling¶
PR_SET_INDIR_BR_LP_STATUS
/ PR_GET_INDIR_BR_LP_STATUS
/
PR_LOCK_INDIR_BR_LP_STATUS
are three prctls added to manage indirect
branch tracking. prctls are arch agnostic and returns -EINVAL on other arches.
prctl(PR_SET_INDIR_BR_LP_STATUS, unsigned long arg)
If arg1 is PR_INDIR_BR_LP_ENABLE
and if CPU supports zicfilp
then kernel will enable indirect branch tracking for the task. Dynamic loader
can issue this prctl
once it has determined that all the objects
loaded in address space support indirect branch tracking. Additionally if there
is a dlopen to an object which wasn’t compiled with zicfilp
, dynamic
loader can issue this prctl with arg1 set to 0 (i.e.
PR_INDIR_BR_LP_ENABLE
being clear)
prctl(PR_GET_INDIR_BR_LP_STATUS, unsigned long arg)
Returns current status of indirect branch tracking. If enabled it’ll return
PR_INDIR_BR_LP_ENABLE
prctl(PR_LOCK_INDIR_BR_LP_STATUS, unsigned long arg)
Locks current status of indirect branch tracking on the task. User space may
want to run with strict security posture and wouldn’t want loading of objects
without zicfilp
support in it and thus would want to disallow disabling of
indirect branch tracking. In that case user space can use this prctl to lock
current settings.