Jump-oriented programming
Jump-Oriented Programming (JOP), is similar to Return-Oriented Programming (ROP). In an ROP attack, the software stack is scanned for gadgets that can be strung together to form a new program. ROP attacks look for sequences that end in a function return (RET
). In contrast, JOP attacks target sequences that end in other forms of indirect (absolute) branches, like function pointers or case statements. You can see an example here:
The attacker exploits the fact that BLR or BR
instructions can target any executable address, and not just the addresses that are entry points defined by the compiler or developer. This means that the instructions can be hijacked to string gadgets together.
Branch target instructions
To help protect against JOP attacks, Armv8.5-A introduced Branch Target Instructions (BTIs). BTIs are also called landing pads. The processor can be configured so that indirect branches (BR
and BLR
) can only allow target landing pad instructions. If the target of an indirect branch is not a landing pad, a Branch Target Exception is generated as you can see here:
The use of landing pads significantly reduces the number of possible targets for an indirect branch and makes it harder to string chains of gadgets together to form a new program.
Enabling branch target checking
Support for landing pads is enabled for each page, using a new bit (GP
bit) in the translation tables. Per-page controls allows a filesystem to contain a mixture of landing pad-protected code and legacy code, which is illustrated here:
The encoding for BTI instructions, like the pointer-authentication instructions, is allocated within the NOP space. BTI-protected code can still function when run on older processors that do not support BTI, or when GP=0
, although without the additional protection.
How BTI is implemented
PSTATE
includes a field, BTYPE
, that records the branch type. On executing an indirect branch, the type of indirect branch is recorded in PSTATE.BTYPE
. The following list shows the value BTYPE
takes for different branch instructions:
- BTYPE=11:
BR, BRAA, BRAB, BRAAZ, BRABZ
with any register other thanX16
orX17
- BTYPE=10:
BLR, BLRAA, BLRAB, BLRAAZ, BLRABZ
- BTYPE=01:
BR, BRAA, BRAB, BRAAZ, BRABZ
withX16
orX17
Executing any other type of instruction, including direct branches, causes BTYPE
to be set to b00
.
Why store two bits? A simple implementation could record whether an indirect branch was in process or not. However, recording the type of indirect branches further limits the possibilities of finding gadgets. The syntax of the BTI instruction includes an argument, specifying which types of indirect branch it can be targeted by:
Argument | Accepted PSTATE.BTYPE |
Use case |
---|---|---|
BTI c |
0b10 and 0b01 |
Function calls |
BTI j |
0b11 and 0b01 |
Non-function call branches, like case-statements |
BTI jc |
All | All |
When BTYPE!=00
, the processor checks whether the instruction being targeted is a landing. If it is not a landing, or if it is the wrong type of indirect branch, an exception is generated.
X16 and X17
Why does the architecture distinguish between indirect branches that use X16
or X17
and those that do not?
X16
and X17
have special significance in the Procedure Call Standard used by Arm. They are referred to as the intra-procedure call corruptible registers, or IP0 or IP1. They can be used by static linkers for inserting branch-range extending veneers, or by dynamic linkers for handling jump tables.
This is relevant to us because it means that a function might be entered directly from the caller using BL
or BLR
or indirectly via linker generated code using X16
or X17
. Therefore, the landing pad for a function entry needs to be able to accept both.
Function entry and return
The function return instructions, RET, RETAA
and RETAB
, are also a form of indirect branch. If these instructions were required to target a BTI
, every function call would need to be followed by a BTI
. This would cause undesirable code bloat. Also, the pointer authentication feature already provides a way to protect function returns.
For function entry, the pointer signing instructions PACIxSP
and PACIxZ
act like landing pads. These instructions are like BTI
instructions. This means that when the landing pad feature is used pointer authentication, there is no need to start every function with a BTI
. This also avoids code bloat.