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 (
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:
BR, BRAA, BRAB, BRAAZ, BRABZwith any register other than
BLR, BLRAA, BLRAB, BLRAAZ, BLRABZ
BR, BRAA, BRAB, BRAAZ, BRABZwith
Executing any other type of instruction, including direct branches, causes
BTYPE to be set to
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:
||Non-function call branches, like case-statements|
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
X17 and those that do not?
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
BLR or indirectly via linker generated code using
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
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.