Skip to content

Commit 9af7c9c

Browse files
committed
aarch64: Add AArch64 Kernel Control Flow Integrity implementation
Implement AArch64-specific KCFI backend providing runtime validation of indirect function calls with ARM exception handling infrastructure. Core AArch64 KCFI Features: * Function preamble generation using .word directives for type ID storage at -4 byte offset from function entry point (no prefix NOPs needed due to 4-byte instruction alignment) * Enhanced debugging through ESR (Exception Syndrome Register) encoding in BRK instruction immediate values for precise failure analysis * Scratch register allocation using w16/w17 (x16/x17) following AArch64 procedure call standard for intra-procedure-call registers * Support for both regular calls (BLR) and sibling calls (BR) with appropriate register usage and jump instructions Assembly Code Generation: * Atomic bundled KCFI check + call/branch sequences using UNSPECV_KCFI_CHECK to prevent optimizer separation and maintain security properties * Constant loading for type IDs using MOV/MOVK instruction pairs for values requiring 32-bit representation * Direct comparison approach using CMP instruction for type validation without arithmetic operations (contrast with x86_64's additive approach) Assembly Code Pattern for AArch64: ldur w16, [target, #-4] ; Load actual type ID from preamble mov w17, #type_id_low ; Load expected type (lower 16 bits) movk w17, #type_id_high, lsl gcc-mirror#16 ; Load upper 16 bits if needed cmp w16, w17 ; Compare type IDs directly b.eq .Lpass ; Branch if types match .Ltrap: brk #esr_value ; Enhanced trap with register info .Lpass: blr/br target ; Execute validated indirect transfer ESR (Exception Syndrome Register) Integration: * BRK instruction immediate encoding format: 0x8000 | ((TypeIndex & 31) << 5) | (AddrIndex & 31) * TypeIndex indicates which W register contains expected type (W17 = 17) * AddrIndex indicates which X register contains target address (0-30) * Example: brk #33313 (0x8221) = expected type in W17, target address in X1 * Enables kernel exception handlers to precisely identify KCFI violation context * Supports advanced debugging and forensic analysis of control flow attacks AArch64-Specific Optimizations: * No prefix NOP calculation needed due to natural 4-byte instruction alignment * Type ID storage using single .word directive in function preambles * Register allocator integration via explicit w16/w17 clobber annotations * Function label emission coordination through ASM_OUTPUT_FUNCTION_LABEL macro redirection to aarch64_declare_function_name() for preamble integration * Support for large immediate values with MOV/MOVK instruction generation Target Hook Implementation: * aarch64_kcfi_calculate_prefix_nops(): Returns 0 (no alignment needed) * aarch64_kcfi_gen_checked_call(): Bundled check+call RTL generation * aarch64_kcfi_emit_type_id_instruction(): .word directive emission * aarch64_kcfi_add_clobbers(): w16/w17 register constraint management * Integration in aarch64_override_options() for initialization Machine Description Integration: * UNSPECV_KCFI_CHECK unspec for atomic check+call bundling * Support for both regular calls and sibling calls with distinct patterns * Runtime ESR value calculation for accurate register encoding * Clobber specifications for w16 (loaded type) and w17 (expected type) Security Properties: * Direct comparison-based type validation with immediate trap on mismatch * Enhanced exception context through ESR encoding for precise failure analysis * Tamper-resistant type ID storage with ARM exception infrastructure integration * Support for cross-compilation and accurate register allocation across targets Build and run tested with Linux kernel ARCH=arm64. Signed-off-by: Kees Cook <[email protected]>
1 parent f7c1640 commit 9af7c9c

File tree

4 files changed

+260
-1
lines changed

4 files changed

+260
-1
lines changed

gcc/config/aarch64/aarch64-protos.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,4 +1285,8 @@ extern unsigned aarch64_stack_alignment (const_tree exp, unsigned align);
12851285
extern rtx aarch64_gen_compare_zero_and_branch (rtx_code code, rtx x,
12861286
rtx_code_label *label);
12871287

1288+
/* KCFI support. */
1289+
extern void aarch64_kcfi_init (void);
1290+
extern void kcfi_emit_trap_with_section (FILE *file, rtx trap_label_rtx);
1291+
12881292
#endif /* GCC_AARCH64_PROTOS_H */

gcc/config/aarch64/aarch64.cc

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
#include "rtlanal.h"
8484
#include "tree-dfa.h"
8585
#include "asan.h"
86+
#include "kcfi.h"
8687
#include "aarch64-elf-metadata.h"
8788
#include "aarch64-feature-deps.h"
8889
#include "config/arm/aarch-common.h"
@@ -19521,6 +19522,9 @@ aarch64_override_options (void)
1952119522

1952219523
aarch64_override_options_internal (&global_options);
1952319524

19525+
/* Initialize KCFI target hooks for AArch64. */
19526+
aarch64_kcfi_init ();
19527+
1952419528
/* Save these options as the default ones in case we push and pop them later
1952519529
while processing functions with potential target attributes. */
1952619530
target_option_default_node = target_option_current_node
@@ -25578,9 +25582,13 @@ aarch64_declare_function_name (FILE *stream, const char* name,
2557825582

2557925583
aarch64_asm_output_variant_pcs (stream, fndecl, name);
2558025584

25585+
/* Emit KCFI preamble for non-patchable functions. */
25586+
kcfi_emit_preamble_if_needed (stream, fndecl, false, 0, name);
25587+
2558125588
/* Don't forget the type directive for ELF. */
2558225589
ASM_OUTPUT_TYPE_DIRECTIVE (stream, name, "function");
25583-
ASM_OUTPUT_FUNCTION_LABEL (stream, name, fndecl);
25590+
/* Output function label directly to avoid recursion with our ASM_OUTPUT_FUNCTION_LABEL macro. */
25591+
assemble_function_label_raw (stream, name);
2558425592

2558525593
cfun->machine->label_is_assembled = true;
2558625594
}
@@ -32811,6 +32819,111 @@ aarch64_libgcc_floating_mode_supported_p
3281132819
#undef TARGET_DOCUMENTATION_NAME
3281232820
#define TARGET_DOCUMENTATION_NAME "AArch64"
3281332821

32822+
32823+
/* AArch64 doesn't need prefix NOPs (instructions are already 4-byte aligned) */
32824+
static int
32825+
aarch64_kcfi_calculate_prefix_nops (HOST_WIDE_INT prefix_nops ATTRIBUTE_UNUSED)
32826+
{
32827+
/* AArch64 instructions are 4-byte aligned, no prefix NOPs needed for KCFI preamble. */
32828+
return 0;
32829+
}
32830+
32831+
/* Emit AArch64-specific type ID instruction. */
32832+
static void
32833+
aarch64_kcfi_emit_type_id_instruction (FILE *file, uint32_t type_id)
32834+
{
32835+
/* Emit type ID as a 32-bit word. */
32836+
fprintf (file, "\t.word 0x%08x\n", type_id);
32837+
}
32838+
32839+
32840+
32841+
/* Generate AArch64 KCFI checked call bundle. */
32842+
static rtx
32843+
aarch64_kcfi_gen_checked_call (rtx call_insn, rtx target_reg, uint32_t expected_type,
32844+
HOST_WIDE_INT prefix_nops)
32845+
{
32846+
/* For AArch64, we create an RTL bundle that combines the KCFI check
32847+
with the call instruction in an atomic sequence. */
32848+
32849+
if (!REG_P (target_reg))
32850+
{
32851+
/* If not a register, load it into x16. */
32852+
rtx temp = gen_rtx_REG (Pmode, 16);
32853+
emit_move_insn (temp, target_reg);
32854+
target_reg = temp;
32855+
}
32856+
32857+
/* Generate the bundled KCFI check + call pattern. */
32858+
rtx pattern;
32859+
if (CALL_P (call_insn))
32860+
{
32861+
rtx call_pattern = PATTERN (call_insn);
32862+
32863+
/* Create labels used by both call and sibcall patterns. */
32864+
rtx pass_label = gen_label_rtx ();
32865+
rtx trap_label = gen_label_rtx ();
32866+
32867+
/* Check if it's a sibling call. */
32868+
if (find_reg_note (call_insn, REG_NORETURN, NULL_RTX)
32869+
|| (GET_CODE (call_pattern) == PARALLEL
32870+
&& GET_CODE (XVECEXP (call_pattern, 0, XVECLEN (call_pattern, 0) - 1)) == RETURN))
32871+
{
32872+
/* Generate sibling call bundle. */
32873+
pattern = gen_aarch64_kcfi_checked_sibcall (target_reg,
32874+
gen_int_mode (expected_type, SImode),
32875+
gen_int_mode (prefix_nops, SImode),
32876+
pass_label,
32877+
trap_label);
32878+
}
32879+
else
32880+
{
32881+
/* Generate regular call bundle. */
32882+
pattern = gen_aarch64_kcfi_checked_call (target_reg,
32883+
gen_int_mode (expected_type, SImode),
32884+
gen_int_mode (prefix_nops, SImode),
32885+
pass_label,
32886+
trap_label);
32887+
}
32888+
}
32889+
else
32890+
{
32891+
error ("KCFI: Expected call instruction");
32892+
return NULL_RTX;
32893+
}
32894+
32895+
return pattern;
32896+
}
32897+
32898+
/* Add AArch64-specific register clobbers for KCFI calls. */
32899+
static void
32900+
aarch64_kcfi_add_clobbers (rtx_insn *call_insn)
32901+
{
32902+
/* AArch64 KCFI uses w16 and w17 (x16 and x17) as scratch registers. */
32903+
rtx usage = CALL_INSN_FUNCTION_USAGE (call_insn);
32904+
32905+
/* Add w16 (x16) clobber. */
32906+
clobber_reg (&usage, gen_rtx_REG (SImode, 16));
32907+
32908+
/* Add w17 (x17) clobber. */
32909+
clobber_reg (&usage, gen_rtx_REG (SImode, 17));
32910+
32911+
CALL_INSN_FUNCTION_USAGE (call_insn) = usage;
32912+
}
32913+
32914+
/* Initialize AArch64 KCFI target hooks. */
32915+
void
32916+
aarch64_kcfi_init (void)
32917+
{
32918+
if (flag_sanitize & SANITIZE_KCFI)
32919+
{
32920+
kcfi_target.gen_kcfi_checked_call = aarch64_kcfi_gen_checked_call;
32921+
kcfi_target.add_kcfi_clobbers = aarch64_kcfi_add_clobbers;
32922+
kcfi_target.calculate_prefix_nops = aarch64_kcfi_calculate_prefix_nops;
32923+
kcfi_target.emit_type_id_instruction = aarch64_kcfi_emit_type_id_instruction;
32924+
}
32925+
}
32926+
3281432927
struct gcc_target targetm = TARGET_INITIALIZER;
3281532928

3281632929
#include "gt-aarch64.h"

gcc/config/aarch64/aarch64.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,11 @@ enum class aarch64_tristate_mode : int { NO, YES, MAYBE };
16651665
applied. */
16661666
#define HARDREG_PRE_REGNOS { FPM_REGNUM, 0 }
16671667

1668+
/* Intercept all function label output including cold partitions for KCFI preamble emission. */
1669+
#undef ASM_OUTPUT_FUNCTION_LABEL
1670+
#define ASM_OUTPUT_FUNCTION_LABEL(FILE, NAME, DECL) \
1671+
aarch64_declare_function_name ((FILE), (NAME), (DECL))
1672+
16681673
#endif
16691674

16701675
#endif /* GCC_AARCH64_H */

gcc/config/aarch64/aarch64.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@
417417
UNSPECV_TCANCEL ; Represent transaction cancel.
418418
UNSPEC_RNDR ; Represent RNDR
419419
UNSPEC_RNDRRS ; Represent RNDRRS
420+
UNSPECV_KCFI_CHECK ; Represent KCFI check bundled with call
420421
]
421422
)
422423

@@ -1316,6 +1317,142 @@
13161317
"brk #1000"
13171318
[(set_attr "type" "trap")])
13181319

1320+
;; KCFI bundled check and call patterns
1321+
;; These combine the KCFI check with the call in an atomic sequence
1322+
1323+
(define_insn "aarch64_kcfi_checked_call"
1324+
[(parallel [(call (mem:DI (match_operand:DI 0 "register_operand" "r"))
1325+
(const_int 0))
1326+
(unspec:DI [(const_int 0)] UNSPEC_CALLEE_ABI)
1327+
(unspec_volatile:DI [(match_operand:SI 1 "const_int_operand" "n") ; type_id
1328+
(match_operand:SI 2 "const_int_operand" "n") ; prefix_nops
1329+
(label_ref (match_operand 3)) ; pass label
1330+
(label_ref (match_operand 4))] ; trap label
1331+
UNSPECV_KCFI_CHECK)
1332+
(clobber (reg:DI LR_REGNUM))
1333+
(clobber (reg:SI 16)) ; w16 - scratch for loaded type
1334+
(clobber (reg:SI 17))])] ; w17 - scratch for expected type
1335+
"flag_sanitize & SANITIZE_KCFI"
1336+
"*
1337+
{
1338+
uint32_t type_id = INTVAL (operands[1]);
1339+
HOST_WIDE_INT prefix_nops = INTVAL (operands[2]);
1340+
HOST_WIDE_INT offset = -(4 + prefix_nops);
1341+
1342+
/* AArch64 KCFI check sequence:
1343+
1. Load actual type from function preamble
1344+
2. Load expected type
1345+
3. Compare and branch if equal
1346+
4. Trap if mismatch
1347+
5. Call target. */
1348+
1349+
static char ldur_buffer[64];
1350+
sprintf (ldur_buffer, \"ldur\\tw16, [%%0, #%ld]\", offset);
1351+
output_asm_insn (ldur_buffer, operands);
1352+
1353+
/* Load expected type - may need multiple instructions for large constants. */
1354+
if ((type_id & 0xffff0000) == 0)
1355+
{
1356+
static char mov_buffer[64];
1357+
sprintf (mov_buffer, \"mov\\tw17, #%u\", type_id);
1358+
output_asm_insn (mov_buffer, operands);
1359+
}
1360+
else
1361+
{
1362+
static char mov_buffer[64], movk_buffer[64];
1363+
sprintf (mov_buffer, \"mov\\tw17, #%u\", type_id & 0xffff);
1364+
output_asm_insn (mov_buffer, operands);
1365+
sprintf (movk_buffer, \"movk\\tw17, #%u, lsl #16\", (type_id >> 16) & 0xffff);
1366+
output_asm_insn (movk_buffer, operands);
1367+
}
1368+
1369+
output_asm_insn (\"cmp\\tw16, w17\", operands);
1370+
output_asm_insn (\"b.eq\\t%l3\", operands);
1371+
1372+
/* Generate unique trap ID and emit trap. */
1373+
output_asm_insn (\"%l4:\", operands);
1374+
1375+
/* Calculate and emit BRK with ESR encoding. */
1376+
unsigned type_index = 17; /* w17 contains expected type. */
1377+
unsigned addr_index = REGNO (operands[0]) - R0_REGNUM;
1378+
unsigned esr_value = 0x8000 | ((type_index & 31) << 5) | (addr_index & 31);
1379+
1380+
static char brk_buffer[32];
1381+
sprintf (brk_buffer, \"brk\\t#%u\", esr_value);
1382+
output_asm_insn (brk_buffer, operands);
1383+
1384+
output_asm_insn (\"%l3:\", operands);
1385+
output_asm_insn (\"\\tblr\\t%0\", operands);
1386+
1387+
return \"\";
1388+
}"
1389+
[(set_attr "type" "call")
1390+
(set_attr "length" "24")])
1391+
1392+
(define_insn "aarch64_kcfi_checked_sibcall"
1393+
[(parallel [(call (mem:DI (match_operand:DI 0 "register_operand" "r"))
1394+
(const_int 0))
1395+
(unspec:DI [(const_int 0)] UNSPEC_CALLEE_ABI)
1396+
(unspec_volatile:DI [(match_operand:SI 1 "const_int_operand" "n") ; type_id
1397+
(match_operand:SI 2 "const_int_operand" "n") ; prefix_nops
1398+
(label_ref (match_operand 3)) ; pass label
1399+
(label_ref (match_operand 4))] ; trap label
1400+
UNSPECV_KCFI_CHECK)
1401+
(return)
1402+
(clobber (reg:SI 16)) ; w16 - scratch for loaded type
1403+
(clobber (reg:SI 17))])] ; w17 - scratch for expected type
1404+
"flag_sanitize & SANITIZE_KCFI"
1405+
"*
1406+
{
1407+
uint32_t type_id = INTVAL (operands[1]);
1408+
HOST_WIDE_INT prefix_nops = INTVAL (operands[2]);
1409+
HOST_WIDE_INT offset = -(4 + prefix_nops);
1410+
1411+
/* AArch64 KCFI check sequence for sibling calls. */
1412+
1413+
static char ldur_buffer[64];
1414+
sprintf (ldur_buffer, \"ldur\\tw16, [%%0, #%ld]\", offset);
1415+
output_asm_insn (ldur_buffer, operands);
1416+
1417+
/* Load expected type. */
1418+
if ((type_id & 0xffff0000) == 0)
1419+
{
1420+
static char mov_buffer[64];
1421+
sprintf (mov_buffer, \"mov\\tw17, #%u\", type_id);
1422+
output_asm_insn (mov_buffer, operands);
1423+
}
1424+
else
1425+
{
1426+
static char mov_buffer[64], movk_buffer[64];
1427+
sprintf (mov_buffer, \"mov\\tw17, #%u\", type_id & 0xffff);
1428+
output_asm_insn (mov_buffer, operands);
1429+
sprintf (movk_buffer, \"movk\\tw17, #%u, lsl #16\", (type_id >> 16) & 0xffff);
1430+
output_asm_insn (movk_buffer, operands);
1431+
}
1432+
1433+
output_asm_insn (\"cmp\\tw16, w17\", operands);
1434+
output_asm_insn (\"b.eq\\t%l3\", operands);
1435+
1436+
/* Generate unique trap ID and emit trap. */
1437+
output_asm_insn (\"%l4:\", operands);
1438+
1439+
/* Calculate and emit BRK with ESR encoding. */
1440+
unsigned type_index = 17; /* w17 contains expected type. */
1441+
unsigned addr_index = REGNO (operands[0]) - R0_REGNUM;
1442+
unsigned esr_value = 0x8000 | ((type_index & 31) << 5) | (addr_index & 31);
1443+
1444+
static char brk_buffer[32];
1445+
sprintf (brk_buffer, \"brk\\t#%u\", esr_value);
1446+
output_asm_insn (brk_buffer, operands);
1447+
1448+
output_asm_insn (\"%l3:\", operands);
1449+
output_asm_insn (\"\\tbr\\t%0\", operands);
1450+
1451+
return \"\";
1452+
}"
1453+
[(set_attr "type" "branch")
1454+
(set_attr "length" "24")])
1455+
13191456
(define_expand "prologue"
13201457
[(clobber (const_int 0))]
13211458
""

0 commit comments

Comments
 (0)