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 than X16 or X17
  • BTYPE=10: BLR, BLRAA, BLRAB, BLRAAZ, BLRABZ
  • BTYPE=01: BR, BRAA, BRAB, BRAAZ, BRABZ with X16 or X17

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.

Previous Next