Skip to content

Commit 080c1ec

Browse files
committed
aarch64: Add AArch64 Kernel Control Flow Integrity implementation
Implement AArch64-specific KCFI backend. - Function preamble generation using .word directives for type ID storage at offset from function entry point (no default alignment NOPs needed due to fixed 4-byte instruction size). - Trap debugging through ESR (Exception Syndrome Register) encoding in BRK instruction immediate values. - Scratch register allocation using w16/w17 (x16/x17) following AArch64 procedure call standard for intra-procedure-call registers. 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 Build and run tested with Linux kernel ARCH=arm64. gcc/ChangeLog: config/aarch64/aarch64-protos.h: Declare aarch64_indirect_branch_asm, and KCFI helpers. config/aarch64/aarch64.cc (aarch64_expand_call): Wrap CALLs in KCFI, with clobbers. (aarch64_indirect_branch_asm): New function, extract common logic for branch asm, like existing call asm helper. (aarch64_output_kcfi_insn): Emit KCFI assembly. config/aarch64/aarch64.md: Add KCFI RTL patterns and replace open-coded branch emission with aarch64_indirect_branch_asm. doc/invoke.texi: Document aarch64 nuances. Signed-off-by: Kees Cook <[email protected]>
1 parent bb9f046 commit 080c1ec

File tree

4 files changed

+190
-8
lines changed

4 files changed

+190
-8
lines changed

gcc/config/aarch64/aarch64-protos.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,7 @@ tree aarch64_resolve_overloaded_builtin_general (location_t, tree, void *);
12611261

12621262
const char *aarch64_sls_barrier (int);
12631263
const char *aarch64_indirect_call_asm (rtx);
1264+
const char *aarch64_indirect_branch_asm (rtx);
12641265
extern bool aarch64_harden_sls_retbr_p (void);
12651266
extern bool aarch64_harden_sls_blr_p (void);
12661267

@@ -1284,4 +1285,8 @@ extern unsigned aarch64_stack_alignment (const_tree exp, unsigned align);
12841285
extern rtx aarch64_gen_compare_zero_and_branch (rtx_code code, rtx x,
12851286
rtx_code_label *label);
12861287

1288+
/* KCFI support. */
1289+
extern void kcfi_emit_trap_with_section (FILE *file, rtx trap_label_rtx);
1290+
extern const char *aarch64_output_kcfi_insn (rtx_insn *insn, rtx *operands);
1291+
12871292
#endif /* GCC_AARCH64_PROTOS_H */

gcc/config/aarch64/aarch64.cc

Lines changed: 115 additions & 0 deletions
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"
@@ -11848,6 +11849,15 @@ aarch64_expand_call (rtx result, rtx mem, rtx cookie, bool sibcall)
1184811849

1184911850
call = gen_rtx_CALL (VOIDmode, mem, const0_rtx);
1185011851

11852+
/* Only indirect calls need KCFI instrumentation. */
11853+
bool is_direct_call = SYMBOL_REF_P (XEXP (mem, 0));
11854+
rtx kcfi_type_rtx = is_direct_call ? NULL_RTX : kcfi_get_call_type_id ();
11855+
if (kcfi_type_rtx)
11856+
{
11857+
/* Wrap call in KCFI. */
11858+
call = gen_rtx_KCFI (VOIDmode, call, kcfi_type_rtx);
11859+
}
11860+
1185111861
if (result != NULL_RTX)
1185211862
call = gen_rtx_SET (result, call);
1185311863

@@ -11864,6 +11874,16 @@ aarch64_expand_call (rtx result, rtx mem, rtx cookie, bool sibcall)
1186411874

1186511875
auto call_insn = aarch64_emit_call_insn (call);
1186611876

11877+
/* Add KCFI clobbers for indirect calls. */
11878+
if (kcfi_type_rtx)
11879+
{
11880+
rtx usage = CALL_INSN_FUNCTION_USAGE (call_insn);
11881+
/* Add X16 and X17 clobbers for AArch64 KCFI scratch registers. */
11882+
clobber_reg (&usage, gen_rtx_REG (DImode, 16));
11883+
clobber_reg (&usage, gen_rtx_REG (DImode, 17));
11884+
CALL_INSN_FUNCTION_USAGE (call_insn) = usage;
11885+
}
11886+
1186711887
/* Check whether the call requires a change to PSTATE.SM. We can't
1186811888
emit the instructions to change PSTATE.SM yet, since they involve
1186911889
a change in vector length and a change in instruction set, which
@@ -30630,6 +30650,14 @@ aarch64_indirect_call_asm (rtx addr)
3063030650
return "";
3063130651
}
3063230652

30653+
const char *
30654+
aarch64_indirect_branch_asm (rtx addr)
30655+
{
30656+
gcc_assert (REG_P (addr));
30657+
output_asm_insn ("br\t%0", &addr);
30658+
return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
30659+
}
30660+
3063330661
/* Emit the assembly instruction to load the thread pointer into DEST.
3063430662
Select between different tpidr_elN registers depending on -mtp= setting. */
3063530663

@@ -32823,6 +32851,93 @@ aarch64_libgcc_floating_mode_supported_p
3282332851
#undef TARGET_DOCUMENTATION_NAME
3282432852
#define TARGET_DOCUMENTATION_NAME "AArch64"
3282532853

32854+
/* Output the assembly for a KCFI checked call instruction. */
32855+
const char *
32856+
aarch64_output_kcfi_insn (rtx_insn *insn, rtx *operands)
32857+
{
32858+
/* Target register is operands[0]. */
32859+
rtx target_reg = operands[0];
32860+
gcc_assert (REG_P (target_reg));
32861+
32862+
/* Get KCFI type ID from operand[3]. */
32863+
uint32_t type_id = (uint32_t) INTVAL (operands[3]);
32864+
32865+
/* Calculate typeid offset from call target. */
32866+
HOST_WIDE_INT offset = -(4 + kcfi_patchable_entry_prefix_nops);
32867+
32868+
/* Generate labels internally. */
32869+
rtx trap_label = gen_label_rtx ();
32870+
rtx call_label = gen_label_rtx ();
32871+
32872+
/* Get label numbers for custom naming. */
32873+
int trap_labelno = CODE_LABEL_NUMBER (trap_label);
32874+
int call_labelno = CODE_LABEL_NUMBER (call_label);
32875+
32876+
/* Generate custom label names. */
32877+
char trap_name[32];
32878+
char call_name[32];
32879+
ASM_GENERATE_INTERNAL_LABEL (trap_name, "Lkcfi_trap", trap_labelno);
32880+
ASM_GENERATE_INTERNAL_LABEL (call_name, "Lkcfi_call", call_labelno);
32881+
32882+
/* AArch64 KCFI check sequence:
32883+
1. Load actual type from function preamble
32884+
2. Load expected type
32885+
3. Compare and branch if equal
32886+
4. Trap if mismatch
32887+
5. Call/branch to target. */
32888+
32889+
rtx temp_operands[3];
32890+
32891+
/* Load actual type from memory at offset using ldur. */
32892+
temp_operands[0] = gen_rtx_REG (SImode, R16_REGNUM); /* w16 */
32893+
temp_operands[1] = target_reg; /* x register */
32894+
temp_operands[2] = GEN_INT (offset); /* offset */
32895+
output_asm_insn ("ldur\t%w0, [%1, #%2]", temp_operands);
32896+
32897+
/* Load expected type low 16 bits into w17. */
32898+
temp_operands[0] = gen_rtx_REG (SImode, R17_REGNUM); /* w17 */
32899+
temp_operands[1] = GEN_INT (type_id & 0xFFFF);
32900+
output_asm_insn ("mov\t%w0, #%1", temp_operands);
32901+
32902+
/* Load expected type high 16 bits into w17. */
32903+
temp_operands[0] = gen_rtx_REG (SImode, R17_REGNUM); /* w17 */
32904+
temp_operands[1] = GEN_INT ((type_id >> 16) & 0xFFFF);
32905+
output_asm_insn ("movk\t%w0, #%1, lsl #16", temp_operands);
32906+
32907+
/* Compare types. */
32908+
temp_operands[0] = gen_rtx_REG (SImode, R16_REGNUM); /* w16 */
32909+
temp_operands[1] = gen_rtx_REG (SImode, R17_REGNUM); /* w17 */
32910+
output_asm_insn ("cmp\t%w0, %w1", temp_operands);
32911+
32912+
/* Output conditional branch to call label. */
32913+
fputs ("\tb.eq\t", asm_out_file);
32914+
assemble_name (asm_out_file, call_name);
32915+
fputc ('\n', asm_out_file);
32916+
32917+
/* Output trap label and BRK instruction. */
32918+
ASM_OUTPUT_LABEL (asm_out_file, trap_name);
32919+
32920+
/* Calculate and emit BRK with ESR encoding. */
32921+
unsigned type_index = 17; /* w17 contains expected type. */
32922+
unsigned addr_index = REGNO (operands[0]) - R0_REGNUM;
32923+
unsigned esr_value = 0x8000 | ((type_index & 31) << 5) | (addr_index & 31);
32924+
32925+
temp_operands[0] = GEN_INT (esr_value);
32926+
output_asm_insn ("brk\t#%0", temp_operands);
32927+
32928+
/* Output call label. */
32929+
ASM_OUTPUT_LABEL (asm_out_file, call_name);
32930+
32931+
/* Return appropriate call instruction based on SIBLING_CALL_P. */
32932+
if (SIBLING_CALL_P (insn))
32933+
return aarch64_indirect_branch_asm (operands[0]);
32934+
else
32935+
return aarch64_indirect_call_asm (operands[0]);
32936+
}
32937+
32938+
#undef TARGET_KCFI_SUPPORTED
32939+
#define TARGET_KCFI_SUPPORTED hook_bool_void_true
32940+
3282632941
struct gcc_target targetm = TARGET_INITIALIZER;
3282732942

3282832943
#include "gt-aarch64.h"

gcc/config/aarch64/aarch64.md

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,6 +1483,19 @@
14831483
}"
14841484
)
14851485

1486+
;; KCFI indirect call - matches KCFI wrapper RTL
1487+
(define_insn "*call_insn"
1488+
[(kcfi (call (mem:DI (match_operand:DI 0 "aarch64_call_insn_operand" "Ucr"))
1489+
(match_operand 1 "" ""))
1490+
(match_operand 3 "const_int_operand"))
1491+
(unspec:DI [(match_operand:DI 2 "const_int_operand")] UNSPEC_CALLEE_ABI)
1492+
(clobber (reg:DI LR_REGNUM))]
1493+
"!SIBLING_CALL_P (insn)"
1494+
{
1495+
return aarch64_output_kcfi_insn (insn, operands);
1496+
}
1497+
[(set_attr "type" "call")])
1498+
14861499
(define_insn "*call_insn"
14871500
[(call (mem:DI (match_operand:DI 0 "aarch64_call_insn_operand"))
14881501
(match_operand 1 "" ""))
@@ -1510,6 +1523,20 @@
15101523
}"
15111524
)
15121525

1526+
;; KCFI call with return value - matches KCFI wrapper RTL
1527+
(define_insn "*call_value_insn"
1528+
[(set (match_operand 0 "" "")
1529+
(kcfi (call (mem:DI (match_operand:DI 1 "aarch64_call_insn_operand" "Ucr"))
1530+
(match_operand 2 "" ""))
1531+
(match_operand 4 "const_int_operand")))
1532+
(unspec:DI [(match_operand:DI 3 "const_int_operand")] UNSPEC_CALLEE_ABI)
1533+
(clobber (reg:DI LR_REGNUM))]
1534+
"!SIBLING_CALL_P (insn)"
1535+
{
1536+
return aarch64_output_kcfi_insn (insn, &operands[1]);
1537+
}
1538+
[(set_attr "type" "call")])
1539+
15131540
(define_insn "*call_value_insn"
15141541
[(set (match_operand 0 "" "")
15151542
(call (mem:DI (match_operand:DI 1 "aarch64_call_insn_operand"))
@@ -1550,6 +1577,19 @@
15501577
}
15511578
)
15521579

1580+
;; KCFI sibling call - matches KCFI wrapper RTL
1581+
(define_insn "*sibcall_insn"
1582+
[(kcfi (call (mem:DI (match_operand:DI 0 "aarch64_call_insn_operand" "Ucs"))
1583+
(match_operand 1 ""))
1584+
(match_operand 3 "const_int_operand"))
1585+
(unspec:DI [(match_operand:DI 2 "const_int_operand")] UNSPEC_CALLEE_ABI)
1586+
(return)]
1587+
"SIBLING_CALL_P (insn)"
1588+
{
1589+
return aarch64_output_kcfi_insn (insn, operands);
1590+
}
1591+
[(set_attr "type" "branch")])
1592+
15531593
(define_insn "*sibcall_insn"
15541594
[(call (mem:DI (match_operand:DI 0 "aarch64_call_insn_operand" "Ucs, Usf"))
15551595
(match_operand 1 ""))
@@ -1558,16 +1598,27 @@
15581598
"SIBLING_CALL_P (insn)"
15591599
{
15601600
if (which_alternative == 0)
1561-
{
1562-
output_asm_insn ("br\\t%0", operands);
1563-
return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
1564-
}
1601+
return aarch64_indirect_branch_asm (operands[0]);
15651602
return "b\\t%c0";
15661603
}
15671604
[(set_attr "type" "branch, branch")
15681605
(set_attr "sls_length" "retbr,none")]
15691606
)
15701607

1608+
;; KCFI sibling call with return value - matches KCFI wrapper RTL
1609+
(define_insn "*sibcall_value_insn"
1610+
[(set (match_operand 0 "")
1611+
(kcfi (call (mem:DI (match_operand:DI 1 "aarch64_call_insn_operand" "Ucs"))
1612+
(match_operand 2 ""))
1613+
(match_operand 4 "const_int_operand")))
1614+
(unspec:DI [(match_operand:DI 3 "const_int_operand")] UNSPEC_CALLEE_ABI)
1615+
(return)]
1616+
"SIBLING_CALL_P (insn)"
1617+
{
1618+
return aarch64_output_kcfi_insn (insn, &operands[1]);
1619+
}
1620+
[(set_attr "type" "branch")])
1621+
15711622
(define_insn "*sibcall_value_insn"
15721623
[(set (match_operand 0 "")
15731624
(call (mem:DI
@@ -1578,10 +1629,7 @@
15781629
"SIBLING_CALL_P (insn)"
15791630
{
15801631
if (which_alternative == 0)
1581-
{
1582-
output_asm_insn ("br\\t%1", operands);
1583-
return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
1584-
}
1632+
return aarch64_indirect_branch_asm (operands[1]);
15851633
return "b\\t%c1";
15861634
}
15871635
[(set_attr "type" "branch, branch")

gcc/doc/invoke.texi

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18427,6 +18427,20 @@ check-call bundle are considered ABI, as the Linux kernel may
1842718427
optionally rewrite these areas at boot time to mitigate detected CPU
1842818428
errata.
1842918429

18430+
On AArch64, KCFI type identifiers are emitted as a @code{.word ID}
18431+
directive (a 32-bit constant) before the function entry. AArch64's
18432+
natural 4-byte instruction alignment eliminates the need for additional
18433+
padding NOPs. When used with @option{-fpatchable-function-entry}, the
18434+
type identifier is placed before any patchable NOPs. The runtime check
18435+
uses @code{x16} and @code{x17} as scratch registers. Type mismatches
18436+
trigger a @code{brk} instruction with an immediate value that encodes
18437+
both the expected type register index and the target address register
18438+
index in the format @code{0x8000 | (type_reg << 5) | addr_reg}. This
18439+
encoding is captured in the ESR (Exception Syndrome Register) when the
18440+
trap is taken, allowing the kernel to identify both the KCFI violation
18441+
and the involved registers for detailed diagnostics (eliminating the need
18442+
for a separate @code{.kcfi_traps} section as used on x86_64).
18443+
1843018444
KCFI is intended primarily for kernel code and may not be suitable
1843118445
for user-space applications that rely on techniques incompatible
1843218446
with strict type checking of indirect calls.

0 commit comments

Comments
 (0)