Skip to content

Commit 0901936

Browse files
gaulclaude
andcommitted
Classify jump tables to de-noise the raw-BR PAC audit
The raw-BR/BLR audit flagged every plain indirect branch uniformly, and on arm64e the overwhelming majority are compiler switch dispatch, not JOP hazards. Recognize the clang jump-table idiom and dismiss it: adrp xB,#pg ; add xB,xB,#off ; ldrsw xE,[xB,xI,lsl #2] ; adr xA,#. ; add xT,xA,xE ; br xT The target is a PC-relative base plus a signed offset read from a table at a statically materialized (read-only) address -- not a value loaded from a corruptible pointer -- so a BR to exactly that xT is benign. jt_advance walks the five producers under strict adjacency (a fresh ADRP restarts, anything else resets), and check_pac_raw_- indirect consults it before the BR advances the machine past stage 5. This inverts armlint's usual bias. Everywhere else a false negative (missed fold) is the safe error; here the audit's dangerous error is hiding a real hazard, so the classifier is deliberately narrow. Only this exact five-producer shape suppresses, and only a BR: a BLR has no jump-table form and is never dismissed, nor is a BR whose register the idiom did not just compute. Linker veneers (adr+br, no table load), the compact ldrb-scaled table variant, and any unclassified branch stay on the worklist. The surviving-BR detail now reads "verify: not a recognized jump table" rather than the old blanket "jump tables are benign". Recon: the idiom is 100% uniform across the arm64e system binaries. Every raw BR in ls (1), ssh (3), sshd (3), zsh (22), and bash (18) is this exact shape -- all 47 byte-verified against the disassembly by an independent decoder, zero mismatches -- so the classifier empties their raw-BR worklists completely while leaving every LR-spill and BLR finding untouched. (This corrects the prior calibration note, which mislabeled ssh's three as veneers.) On plain-arm64 libcapstone run with -a pac, nothing is suppressed: its 151 tail-call BRs and 198 BLRs all survive, since none match the ldrsw idiom -- the conservative result. Forward-declared decode_add_imm_x/decode_add_x_shifted_lsl for the recognizer, which sits far above their definitions. Unit test test_pac_jump_table: the canonical idiom and its commuted final add suppress; a bare BR, a BLR after the full idiom, a BR to the wrong register, a one-instruction gap, a missing ADRP, and a mismatched ADD base all stay flagged. Fixture pac_jump_table (plain arm64 + -a pac sidecar, so it runs on Linux CI too) holds the dismissed table beside a surviving BR and BLR. Two existing PAC snapshots absorb the detail wording change, findings unchanged. Capstone 5.0.9. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent a439d52 commit 0901936

11 files changed

Lines changed: 290 additions & 32 deletions

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,9 @@ arms automatically on arm64e slices, whose ABI mandates FEAT_PAuth
307307
hardening rather than missed folds; `pac` audits the binary against
308308
the arm64e-style full pointer-authentication contract (return
309309
addresses spilled unsigned, unauthenticated `br`/`blr`). Audit
310-
findings are review items: jump tables and linker veneers
311-
legitimately appear as raw `br`. The PAC audit arms automatically on
310+
findings are review items; the raw-`br` check recognizes and
311+
auto-dismisses the clang jump-table idiom, so what remains is BLRs,
312+
linker veneers, and genuinely unclassified branches. The PAC audit arms automatically on
312313
arm64e slices (whose ABI already assumes full signing), so macOS
313314
system binaries surface their worklist with no flag; a plain arm64
314315
slice never opted in, so it stays silent unless you pass `-a pac`

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ The largest untouched family; none of these need liveness machinery.
7575
| Split fusion pairs (cmp+b.cond, aese+aesmc same-dest, adrp+add) | Informational: "these should be adjacent"; per-core tables from the SOGs |
7676
| Render `mov xd, #0` (not `mov xd, xzr`) and `movi v0.2d, #0` (not `movi d0, #0`) | Apple eliminates only those spellings at rename; rendering tweaks to existing checks |
7777
| Loaded value as base not offset (`[x9, x8]``[x8, x9]` when x8 was just loaded) | Apple guide §4.6.7: 1 cycle of address-generation latency |
78-
| PAC audit v2: jump-table classification to auto-dismiss benign raw `br` (the clang `adrp`/`add __const` + `ldrsw` + `add` + `br` idiom), non-SP LR stores (jmp_buf/context saves; rare -- 0 in bash/dyld, lives in libsystem_c), zero-discriminator forward-edge worklist (`braaz`/`blraaz`; dyld 169, bash 85, ssh 109) | v1 (`-a pac`) covers SP-based spills and flags every raw BR/BLR uniformly. Auto-arm on arm64e slices: **done** for both `-a pac` and `-m pauth` (one cpusubtype gate in scan_macho) |
78+
| PAC audit v2: non-SP LR stores (jmp_buf/context saves; rare -- 0 in bash/dyld, lives in libsystem_c), zero-discriminator forward-edge worklist (`braaz`/`blraaz`; dyld 169, bash 85, ssh 109), the compact `ldrb`-scaled jump-table variant (`adr` + `ldrb` + `add …, lsl #2` + `br`; seen in Homebrew arm64 libcapstone, unmatched by the ldrsw classifier) | Auto-arm on arm64e slices: **done** for both `-a pac` and `-m pauth`. Jump-table classification for the dominant `ldrsw` idiom: **done** (jt_advance in check_pac_raw_indirect empties the arm64e raw-BR worklist) |
7979
| LDP/STP synthesized through a scratch ADD (`add x27, xN, #big ; ldp x3, x4, [x27]`) → two plain `ldr`/`str` with the offset folded in | Size-neutral 2-for-2 that drops the ADD from the address dependency chain and frees the scratch; gc emits it whenever a pair offset exceeds ±504 or is 8-misaligned (~13k in go), LLVM for big Q-register spill offsets (~9k in librustc_driver); requires the split offsets to encode (scaled imm12, or LDUR/STUR range) |
8080

8181
## Window candidates (2026-07 corpus sweep)

analyses.md

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,27 +1665,37 @@ Throughout, `datasize` is the operand width in bits: 32 for the W-form,
16651665
* "unauthenticated BR/BLR (PAC audit)": in fully signed code,
16661666
function-pointer transfers go through BRAA(Z)/BLRAA(Z), which
16671667
authenticate the target register before branching; each raw BR/BLR
1668-
is a JOP hazard. Two benign shapes survive in honest arm64e
1669-
binaries and a peephole cannot tell them apart, so they appear in
1670-
the output for human review: jump tables (the target is computed
1671-
from a bounded index into read-only offsets, not a corruptible
1672-
pointer -- clang leaves these raw even on arm64e) and linker
1673-
long-branch veneers (static target, `br x16`). A raw BLR has no
1674-
benign class and deserves the closest look. The authenticated
1668+
is a JOP hazard. The dominant benign shape is compiler switch
1669+
dispatch, and a jump-table classifier keeps it off the worklist:
1670+
the clang idiom `adrp xB ; add xB,xB,#off ; ldrsw xE,[xB,xI,lsl #2]
1671+
; adr xA,#. ; add xT,xA,xE ; br xT` computes its target as a
1672+
PC-relative base plus a signed offset read from a statically
1673+
addressed (read-only) table -- not a corruptible pointer -- so a BR
1674+
to exactly that `xT` is dismissed. The match is strict-adjacency and
1675+
deliberately narrow: for an audit the dangerous error is hiding a
1676+
real hazard, so only this exact five-producer shape is recognized.
1677+
What still surfaces: every BLR (a call has no jump-table form),
1678+
linker long-branch veneers (`adr`+`br`, no table load), the compact
1679+
`ldrb`-scaled table variant (a different idiom, left for a future
1680+
pass), and any genuinely unclassified branch. The authenticated
16751681
variants and RET differ in encoding and never match.
16761682
* Calibration on macOS 26 (Apple's arm64e system binaries): zero
16771683
unsigned LR spills across ls, zsh, ssh, and sshd -- Apple's signing
16781684
is complete, and the window produces no false positives over
1679-
thousands of signed prologues -- with tiny indirect worklists (ssh:
1680-
exactly its 3 `br x16` veneer-class branches, address-verified
1681-
against the disassembly; zsh: 22, the interpreter's computed
1682-
jumps). Over a binary that never opted into pac-ret the flag
1683-
reports every function by design -- the assertion is simply false
1684-
there (Homebrew's plain-arm64 gh: 31109 spills and 20129 raw
1685-
BLRs, Go emitting neither signing nor authenticated calls). That
1686-
gap is exactly why the auto-arm gates on cpusubtype rather than
1687-
firing everywhere: the four system binaries above are arm64e and
1688-
now surface their worklists with no flag, while gh and the other
1685+
thousands of signed prologues. Every raw BR in these four is the
1686+
clang jump-table idiom (ls 1, ssh 3, sshd 3, zsh 22, bash 18 --
1687+
each of the 47 byte-verified against the disassembly, zero
1688+
mismatches), so the classifier empties the raw-BR worklist
1689+
entirely; what would remain on other binaries is veneers, the
1690+
`ldrb` variant, or real hazards. Over a binary that never opted
1691+
into pac-ret the LR-spill flag reports every function by design --
1692+
the assertion is simply false there (Homebrew's plain-arm64 gh:
1693+
31109 spills and 20129 raw BLRs, Go emitting neither signing nor
1694+
authenticated calls; its jump-table BRs use a different idiom and
1695+
stay flagged too). That gap is exactly why the auto-arm gates on
1696+
cpusubtype rather than firing everywhere: the four system binaries
1697+
above are arm64e and now surface their worklists with no flag,
1698+
while gh and the other
16891699
Homebrew arm64 binaries stay silent unless `-a pac` is asked for
16901700
explicitly.
16911701

armlint.c

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,22 @@ struct armlint_state {
468468
// x30 (check_pac_lr_spill; reset by any control transfer).
469469
unsigned pac_sign_recent;
470470

471+
// Jump-table recognizer for the PAC raw-BR audit
472+
// (check_pac_raw_indirect). The clang switch idiom
473+
// adrp xB,#pg ; add xB,xB,#off ; ldrsw xE,[xB,xI,lsl #2] ;
474+
// adr xA,#. ; add xT,xA,xE ; br xT
475+
// computes the branch target from a PC-relative base plus a
476+
// signed offset read from a statically-addressed table, so it is
477+
// not a corruptible-pointer transfer and is auto-dismissed.
478+
// jt_stage counts matched producers (1..5) under strict
479+
// adjacency; the *_reg fields carry the live registers between
480+
// stages. jt_page_reg serves the adrp/add/ldrsw base.
481+
unsigned jt_stage;
482+
unsigned jt_page_reg;
483+
unsigned jt_offset_reg;
484+
unsigned jt_adr_reg;
485+
unsigned jt_target_reg;
486+
471487
// LDXR/STXR retry-loop progress for the LSE fold (-m lse):
472488
// stage 1 after the exclusive load, stage 2 after the middle ALU
473489
// op, stage 3 after the store-exclusive (lse_is_swp marks the
@@ -1353,6 +1369,7 @@ bool armlint_flush(armlint_state *state, armlint_finding *out)
13531369
state->pending_sgn_active = false;
13541370
state->aut_active = false;
13551371
state->pac_sign_recent = 0;
1372+
state->jt_stage = 0;
13561373
state->lse_stage = 0;
13571374
state->pending_lse_active = false;
13581375
state->pending_fp_active = false;
@@ -5788,21 +5805,107 @@ bool check_pac_lr_spill(armlint_state *state, const cs_insn *insn,
57885805
return found;
57895806
}
57905807

5808+
// Defined below, near the addressing-mode folds; forward-declared for
5809+
// the jump-table recognizer.
5810+
static bool decode_add_imm_x(uint32_t op, unsigned *out_rd,
5811+
unsigned *out_rn, uint32_t *out_imm);
5812+
static bool decode_add_x_shifted_lsl(uint32_t op, unsigned *out_rd,
5813+
unsigned *out_rn, unsigned *out_rm,
5814+
unsigned *out_shift);
5815+
5816+
// Advance the jump-table recognizer by one instruction. Each producer
5817+
// of the clang switch idiom moves jt_stage forward under strict
5818+
// adjacency; a fresh ADRP always restarts the match, and anything else
5819+
// resets it. After the fifth producer (stage 5) jt_target_reg names
5820+
// the register the BR will read. The BR is consulted before this runs,
5821+
// so reaching stage 5 here and then seeing the branch is what closes
5822+
// the match.
5823+
static void jt_advance(armlint_state *state, uint32_t op)
5824+
{
5825+
unsigned rd, rn, rm, shift;
5826+
uint32_t imm;
5827+
switch (state->jt_stage) {
5828+
case 1:
5829+
// add xB, xB, #off -- the table base, same register as the ADRP.
5830+
if (decode_add_imm_x(op, &rd, &rn, &imm)
5831+
&& rd == state->jt_page_reg && rn == state->jt_page_reg) {
5832+
state->jt_stage = 2;
5833+
return;
5834+
}
5835+
break;
5836+
case 2:
5837+
// ldrsw xE, [xB, xI, lsl #2] off the table base: register
5838+
// offset (bit 21), option:S == 0b0111 (LSL #2, sign-extended
5839+
// word), base == jt_page_reg.
5840+
if ((op & 0xFFE00C00u) == 0xB8A00800u
5841+
&& ((op >> 12) & 0xFu) == 0x7u
5842+
&& ((op >> 5) & 0x1Fu) == state->jt_page_reg) {
5843+
state->jt_offset_reg = op & 0x1Fu;
5844+
state->jt_stage = 3;
5845+
return;
5846+
}
5847+
break;
5848+
case 3:
5849+
// adr xA, #. -- the PC-relative base the offsets are added to.
5850+
if ((op & 0x9F000000u) == 0x10000000u) {
5851+
state->jt_adr_reg = op & 0x1Fu;
5852+
state->jt_stage = 4;
5853+
return;
5854+
}
5855+
break;
5856+
case 4:
5857+
// add xT, xA, xE (either operand order): target = base + offset.
5858+
if (decode_add_x_shifted_lsl(op, &rd, &rn, &rm, &shift)
5859+
&& shift == 0
5860+
&& ((rn == state->jt_adr_reg && rm == state->jt_offset_reg)
5861+
|| (rn == state->jt_offset_reg && rm == state->jt_adr_reg))) {
5862+
state->jt_target_reg = rd;
5863+
state->jt_stage = 5;
5864+
return;
5865+
}
5866+
break;
5867+
default:
5868+
break;
5869+
}
5870+
// No advance from the current stage: a fresh ADRP starts a new
5871+
// candidate table, anything else (including the closing BR) resets.
5872+
if ((op & 0x9F000000u) == 0x90000000u) {
5873+
state->jt_page_reg = op & 0x1Fu;
5874+
state->jt_stage = 1;
5875+
} else {
5876+
state->jt_stage = 0;
5877+
}
5878+
}
5879+
57915880
bool check_pac_raw_indirect(armlint_state *state, const cs_insn *insn,
57925881
size_t offset, armlint_finding *out)
57935882
{
57945883
if (insn->size != 4 || !(state->features & ARMLINT_AUDIT_PAC)) {
5884+
state->jt_stage = 0;
57955885
return false;
57965886
}
57975887

57985888
uint32_t op = insn_word(insn);
57995889
bool is_br = (op & 0xFFFFFC1Fu) == 0xD61F0000u;
58005890
bool is_blr = (op & 0xFFFFFC1Fu) == 0xD63F0000u;
5891+
unsigned rn = (op >> 5) & 0x1Fu;
5892+
5893+
// A BR to exactly the register the jump-table idiom just computed
5894+
// is a read-only-table dispatch, not a corruptible-pointer
5895+
// transfer -- dismiss it. Consult the recognizer before advancing
5896+
// it past this instruction. Only BR is ever a jump table: a BLR is
5897+
// a call and has no benign class, so it is never suppressed.
5898+
bool benign_jt = is_br && state->jt_stage == 5u
5899+
&& rn == state->jt_target_reg;
5900+
jt_advance(state, op);
5901+
58015902
if (!is_br && !is_blr) {
58025903
return false;
58035904
}
5905+
if (benign_jt) {
5906+
return false;
5907+
}
58045908

5805-
unsigned rn = (op >> 5) & 0x1Fu;
58065909
out->name = "unauthenticated BR/BLR (PAC audit)";
58075910
out->start_offset = offset;
58085911
out->insn_count = 1;
@@ -5812,7 +5915,7 @@ bool check_pac_raw_indirect(armlint_state *state, const cs_insn *insn,
58125915
"-> blraaz x%u (verify the discriminator)", rn);
58135916
} else {
58145917
snprintf(out->detail, sizeof(out->detail),
5815-
"-> braaz x%u (jump tables are benign)", rn);
5918+
"-> braaz x%u (verify: not a recognized jump table)", rn);
58165919
}
58175920
snprintf(out->lines[0], sizeof(out->lines[0]),
58185921
"%s %s", insn->mnemonic, insn->op_str);

armlint.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -539,14 +539,22 @@ bool check_pac_lr_spill(armlint_state *state, const cs_insn *insn,
539539
// BLR. In fully signed code, function-pointer transfers go through
540540
// BRAA(Z)/BLRAA(Z), which authenticate the target register before
541541
// branching; each raw BR/BLR is a JOP hazard. The output is an
542-
// auditor's worklist, not an error list: two benign shapes survive
543-
// in honest arm64e binaries and a peephole cannot tell them apart --
544-
// jump tables (the target is computed from a bounded index into
545-
// read-only offsets, not a corruptible pointer; clang leaves these
546-
// raw even on arm64e) and linker long-branch veneers. A raw BLR has
547-
// no benign class and deserves the closest look. The authenticated
548-
// variants and RET differ in encoding and never match. Reported as
549-
// "unauthenticated BR/BLR (PAC audit)".
542+
// auditor's worklist, not an error list, and a jump-table classifier
543+
// keeps compiler switch dispatch off it: the clang idiom
544+
// adrp xB,#pg ; add xB,xB,#off ; ldrsw xE,[xB,xI,lsl #2] ;
545+
// adr xA,#. ; add xT,xA,xE ; br xT
546+
// computes its target as a PC-relative base plus a signed offset read
547+
// from a table at a statically materialized (read-only) address --
548+
// not a corruptible pointer -- so a BR to exactly that xT is
549+
// dismissed. The match is strict-adjacency and conservative: the
550+
// dangerous direction for an audit is hiding a real hazard, so only
551+
// this exact five-producer shape is recognized. A BLR is never
552+
// dismissed (a call has no jump-table form), nor is any BR whose
553+
// register was not just computed by the idiom -- so linker veneers
554+
// (adr+br, no table load) and the compact ldrb-scaled table variant
555+
// stay flagged, as does any genuinely unclassified branch. The
556+
// authenticated variants and RET differ in encoding and never match.
557+
// Reported as "unauthenticated BR/BLR (PAC audit)".
550558
bool check_pac_raw_indirect(armlint_state *state, const cs_insn *insn,
551559
size_t offset, armlint_finding *out);
552560

armlint_test.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10377,6 +10377,19 @@ static void blraaz_(uint8_t out[4], unsigned rn)
1037710377
write_le32(out, 0xD63F081Fu | ((rn & 0x1Fu) << 5));
1037810378
}
1037910379

10380+
// LDRSW Xt, [Xn, Xm, lsl #2] -- register offset, sign-extended word.
10381+
// Base 0xB8A00800 with option=011 (UXTX/LSL), S=1: bits 15:12 = 0b0111.
10382+
static void ldrsw_reg_lsl2(uint8_t out[4], unsigned rt, unsigned rn,
10383+
unsigned rm)
10384+
{
10385+
uint32_t op = 0xB8A00800u
10386+
| ((rm & 0x1Fu) << 16)
10387+
| (0x7u << 12)
10388+
| ((rn & 0x1Fu) << 5)
10389+
| (rt & 0x1Fu);
10390+
write_le32(out, op);
10391+
}
10392+
1038010393
static void test_cssc_minmax(void)
1038110394
{
1038210395
uint8_t code[12];
@@ -11526,6 +11539,88 @@ static void test_pac_audit(void)
1152611539
assert(run_pac_audit_check(code, 12) == 2);
1152711540
}
1152811541

11542+
// The raw-BR audit dismisses the clang jump-table idiom: adrp/add of a
11543+
// table base, an ldrsw of a signed offset indexed off it, an adr for
11544+
// the PC-relative base, an add combining them, then br. The target is
11545+
// a read-only-table dispatch, not a corruptible pointer.
11546+
static void test_pac_jump_table(void)
11547+
{
11548+
uint8_t code[28];
11549+
11550+
// The canonical idiom -- suppressed, no finding.
11551+
adrp_x(&code[0], 17, 1);
11552+
add_x_imm(&code[4], 17, 17, 0x100);
11553+
ldrsw_reg_lsl2(&code[8], 16, 17, 16); // ldrsw x16, [x17, x16, lsl #2]
11554+
adr_(&code[12], 17, 0);
11555+
add_x(&code[16], 16, 17, 16); // add x16, x17, x16
11556+
br_(&code[20], 16);
11557+
assert(run_pac_audit_check(code, 24) == 0);
11558+
11559+
// The final add's operands commute: add x16, x16, x17 also folds.
11560+
adrp_x(&code[0], 17, 1);
11561+
add_x_imm(&code[4], 17, 17, 0x100);
11562+
ldrsw_reg_lsl2(&code[8], 16, 17, 16);
11563+
adr_(&code[12], 17, 0);
11564+
add_x(&code[16], 16, 16, 17);
11565+
br_(&code[20], 16);
11566+
assert(run_pac_audit_check(code, 24) == 0);
11567+
11568+
// Negative: a bare BR with no preceding idiom stays flagged.
11569+
br_(&code[0], 9);
11570+
assert(run_pac_audit_check(code, 4) == 1);
11571+
11572+
// Negative: a BLR after the full idiom is never a jump table --
11573+
// a call has no benign class, so it is still flagged.
11574+
adrp_x(&code[0], 17, 1);
11575+
add_x_imm(&code[4], 17, 17, 0x100);
11576+
ldrsw_reg_lsl2(&code[8], 16, 17, 16);
11577+
adr_(&code[12], 17, 0);
11578+
add_x(&code[16], 16, 17, 16);
11579+
blr_(&code[20], 16);
11580+
ret_(&code[24]);
11581+
assert(run_pac_audit_check(code, 28) == 1);
11582+
11583+
// Negative: the branch reads a register the idiom did not compute
11584+
// (the target is x16; branching x17 is a different value).
11585+
adrp_x(&code[0], 17, 1);
11586+
add_x_imm(&code[4], 17, 17, 0x100);
11587+
ldrsw_reg_lsl2(&code[8], 16, 17, 16);
11588+
adr_(&code[12], 17, 0);
11589+
add_x(&code[16], 16, 17, 16);
11590+
br_(&code[20], 17);
11591+
assert(run_pac_audit_check(code, 24) == 1);
11592+
11593+
// Negative: a gap breaks the strict adjacency the idiom requires.
11594+
adrp_x(&code[0], 17, 1);
11595+
add_x_imm(&code[4], 17, 17, 0x100);
11596+
ldrsw_reg_lsl2(&code[8], 16, 17, 16);
11597+
adr_(&code[12], 17, 0);
11598+
add_x(&code[16], 16, 17, 16);
11599+
nop_insn(&code[20]);
11600+
br_(&code[24], 16);
11601+
assert(run_pac_audit_check(code, 28) == 1);
11602+
11603+
// Negative: no ADRP anchoring the table base -- the sequence could
11604+
// load its offset off an attacker-controlled register, so it is
11605+
// not recognized and stays flagged.
11606+
add_x_imm(&code[0], 17, 17, 0x100);
11607+
ldrsw_reg_lsl2(&code[4], 16, 17, 16);
11608+
adr_(&code[8], 17, 0);
11609+
add_x(&code[12], 16, 17, 16);
11610+
br_(&code[16], 16);
11611+
assert(run_pac_audit_check(code, 20) == 1);
11612+
11613+
// Negative: the ADD base register is not the ADRP's, so the table
11614+
// base is unproven.
11615+
adrp_x(&code[0], 17, 1);
11616+
add_x_imm(&code[4], 17, 18, 0x100); // add x17, x18, #.. (Rn != adrp)
11617+
ldrsw_reg_lsl2(&code[8], 16, 17, 16);
11618+
adr_(&code[12], 17, 0);
11619+
add_x(&code[16], 16, 17, 16);
11620+
br_(&code[20], 16);
11621+
assert(run_pac_audit_check(code, 24) == 1);
11622+
}
11623+
1152911624
// check_br_x30: an indirect branch through the link register is a
1153011625
// spelled-out RET.
1153111626
static void test_br_x30(void)
@@ -13023,6 +13118,7 @@ int main(void)
1302313118
test_add_stlr_fold();
1302413119
test_aut_ret();
1302513120
test_pac_audit();
13121+
test_pac_jump_table();
1302613122
test_br_x30();
1302713123
test_branch_to_next();
1302813124
test_lse_rmw();

fixtures/pac_audit.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LR spill without PACIASP/PACIBSP (PAC audit) at offset: 0x44: -> pacibsp before
1010
unauthenticated BR/BLR (PAC audit) at offset: 0x58: -> blraaz x8 (verify the discriminator) (1 instructions)
1111
blr x8
1212

13-
unauthenticated BR/BLR (PAC audit) at offset: 0x5c: -> braaz x9 (jump tables are benign) (1 instructions)
13+
unauthenticated BR/BLR (PAC audit) at offset: 0x5c: -> braaz x9 (verify: not a recognized jump table) (1 instructions)
1414
br x9
1515

1616
Optimization opportunities by type:

fixtures/pac_autoarm_arm64e.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ LR spill without PACIASP/PACIBSP (PAC audit) at offset: 0x10: -> pacibsp before
44
unauthenticated BR/BLR (PAC audit) at offset: 0x1c: -> blraaz x9 (verify the discriminator) (1 instructions)
55
blr x9
66

7-
unauthenticated BR/BLR (PAC audit) at offset: 0x20: -> braaz x8 (jump tables are benign) (1 instructions)
7+
unauthenticated BR/BLR (PAC audit) at offset: 0x20: -> braaz x8 (verify: not a recognized jump table) (1 instructions)
88
br x8
99

1010
Optimization opportunities by type:

0 commit comments

Comments
 (0)