On Thu, Nov 20, 2025 at 02:57:49PM -0800, Andrew Pinski wrote:
> On Thu, Nov 20, 2025 at 2:29 PM Kees Cook <[email protected]> wrote:
> >
> > Implement AArch64-specific KCFI backend.
> >
> > - 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,
> >   which already makes x16/x17 available through existing clobbers.
> >
> > - Complementary with BTI (which uses a separate pass system to inject
> >   landing instructions where needed).
> >
> > - Does not interfere with SME, which uses attributes not function
> >   prototypes for distinguishing functions.
> >
> > 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 #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.
> >
> > gcc/testsuite/ChangeLog:
> >
> >         * gcc.dg/kcfi/kcfi-adjacency.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-basics.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-call-sharing.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-complex-addressing.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-move-preservation.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-no-sanitize-inline.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-no-sanitize.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-offset-validation.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-patchable-entry-only.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-patchable-large.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-patchable-medium.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-patchable-prefix-only.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-tail-calls.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-trap-section.c: Add aarch64 patterns.
> >         * gcc.dg/kcfi/kcfi-trap-encoding.c: New test.
> >
> > Signed-off-by: Kees Cook <[email protected]>
> > ---
> >  gcc/config/aarch64/aarch64-protos.h           |   5 +
> >  gcc/config/aarch64/aarch64.md                 |  66 +++++++++--
> >  gcc/config/aarch64/aarch64.cc                 | 105 ++++++++++++++++++
> >  gcc/doc/invoke.texi                           |  14 +++
> >  gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c    |  15 +++
> >  gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c       |  21 ++++
> >  gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c |   4 +
> >  .../gcc.dg/kcfi/kcfi-complex-addressing.c     |  16 +++
> >  .../gcc.dg/kcfi/kcfi-move-preservation.c      |  20 ++++
> >  .../gcc.dg/kcfi/kcfi-no-sanitize-inline.c     |   5 +
> >  gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c  |   1 +
> >  .../gcc.dg/kcfi/kcfi-offset-validation.c      |   3 +
> >  .../gcc.dg/kcfi/kcfi-patchable-entry-only.c   |  12 ++
> >  .../gcc.dg/kcfi/kcfi-patchable-large.c        |  12 ++
> >  .../gcc.dg/kcfi/kcfi-patchable-medium.c       |  12 ++
> >  .../gcc.dg/kcfi/kcfi-patchable-prefix-only.c  |  12 ++
> >  gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c   |  19 ++++
> >  .../gcc.dg/kcfi/kcfi-trap-encoding.c          |  41 +++++++
> >  gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c |   4 +
> >  19 files changed, 379 insertions(+), 8 deletions(-)
> >  create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c
> >
> > diff --git a/gcc/config/aarch64/aarch64-protos.h 
> > b/gcc/config/aarch64/aarch64-protos.h
> > index a9e407ba340e..c32d454fe277 100644
> > --- a/gcc/config/aarch64/aarch64-protos.h
> > +++ b/gcc/config/aarch64/aarch64-protos.h
> > @@ -1272,6 +1272,7 @@ tree aarch64_resolve_overloaded_builtin_general 
> > (location_t, tree, void *);
> >
> >  const char *aarch64_sls_barrier (int);
> >  const char *aarch64_indirect_call_asm (rtx);
> > +const char *aarch64_indirect_branch_asm (rtx);
> >  extern bool aarch64_harden_sls_retbr_p (void);
> >  extern bool aarch64_harden_sls_blr_p (void);
> >
> > @@ -1295,4 +1296,8 @@ extern unsigned aarch64_stack_alignment (const_tree 
> > exp, unsigned align);
> >  extern rtx aarch64_gen_compare_zero_and_branch (rtx_code code, rtx x,
> >                                                 rtx_code_label *label);
> >
> > +/* KCFI support.  */
> > +extern void kcfi_emit_trap_with_section (FILE *file, rtx trap_label_rtx);
> > +extern const char *aarch64_output_kcfi_insn (rtx_insn *insn, rtx 
> > *operands);
> > +
> >  #endif /* GCC_AARCH64_PROTOS_H */
> > diff --git a/gcc/config/aarch64/aarch64.md b/gcc/config/aarch64/aarch64.md
> > index 855df791bae7..84894816509c 100644
> > --- a/gcc/config/aarch64/aarch64.md
> > +++ b/gcc/config/aarch64/aarch64.md
> > @@ -1506,6 +1506,19 @@
> >    }"
> >  )
> >
> > +;; KCFI indirect call
> > +(define_insn "*call_insn"
> > +  [(kcfi (call (mem:DI (match_operand:DI 0 "aarch64_call_insn_operand" 
> > "Ucr"))
> > +              (match_operand 1 "" ""))
> > +        (match_operand 3 "const_int_operand"))
> > +   (unspec:DI [(match_operand:DI 2 "const_int_operand")] UNSPEC_CALLEE_ABI)
> > +   (clobber (reg:DI LR_REGNUM))]
> > +  "!SIBLING_CALL_P (insn)"
> > +{
> > +  return aarch64_output_kcfi_insn (insn, operands);
> > +}
> > +  [(set_attr "type" "call")])
> > +
> >  (define_insn "*call_insn"
> >    [(call (mem:DI (match_operand:DI 0 "aarch64_call_insn_operand"))
> >          (match_operand 1 "" ""))
> > @@ -1533,6 +1546,21 @@
> >    }"
> >  )
> >
> > +;; KCFI call with return value
> > +(define_insn "*call_value_insn"
> > +  [(set (match_operand 0 "" "")
> > +       (kcfi (call (mem:DI (match_operand:DI 1 "aarch64_call_insn_operand"
> > +                            "Ucr"))
> > +                   (match_operand 2 "" ""))
> > +             (match_operand 4 "const_int_operand")))
> > +   (unspec:DI [(match_operand:DI 3 "const_int_operand")] UNSPEC_CALLEE_ABI)
> > +   (clobber (reg:DI LR_REGNUM))]
> > +  "!SIBLING_CALL_P (insn)"
> > +{
> > +  return aarch64_output_kcfi_insn (insn, &operands[1]);
> > +}
> > +  [(set_attr "type" "call")])
> > +
> >  (define_insn "*call_value_insn"
> >    [(set (match_operand 0 "" "")
> >         (call (mem:DI (match_operand:DI 1 "aarch64_call_insn_operand"))
> > @@ -1573,6 +1601,19 @@
> >    }
> >  )
> >
> > +;; KCFI sibling call
> > +(define_insn "*sibcall_insn"
> > +  [(kcfi (call (mem:DI (match_operand:DI 0 "aarch64_call_insn_operand" 
> > "Ucs"))
> > +              (match_operand 1 ""))
> > +        (match_operand 3 "const_int_operand"))
> > +   (unspec:DI [(match_operand:DI 2 "const_int_operand")] UNSPEC_CALLEE_ABI)
> > +   (return)]
> > +  "SIBLING_CALL_P (insn)"
> > +{
> > +  return aarch64_output_kcfi_insn (insn, operands);
> > +}
> > +  [(set_attr "type" "branch")])
> > +
> >  (define_insn "*sibcall_insn"
> >    [(call (mem:DI (match_operand:DI 0 "aarch64_call_insn_operand" "Ucs, 
> > Usf"))
> >          (match_operand 1 ""))
> > @@ -1581,16 +1622,28 @@
> >    "SIBLING_CALL_P (insn)"
> >    {
> >      if (which_alternative == 0)
> > -      {
> > -       output_asm_insn ("br\\t%0", operands);
> > -       return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
> > -      }
> > +      return aarch64_indirect_branch_asm (operands[0]);
> 
> Can you extract aarch64_indirect_branch_asm into a different patch?
> Since it is only slightly related to this.
> The main reason is because applying that can happen before the rest of
> the KFCI patches are approved. And I will handling applying that once
> it has been extracted out.

Sure! I'll extract it.

> 
> >      return "b\\t%c0";
> >    }
> >    [(set_attr "type" "branch, branch")
> >     (set_attr "sls_length" "retbr,none")]
> >  )
> >
> > +;; KCFI sibling call with return value
> > +(define_insn "*sibcall_value_insn"
> > +  [(set (match_operand 0 "")
> > +       (kcfi (call (mem:DI (match_operand:DI 1 "aarch64_call_insn_operand"
> > +                            "Ucs"))
> > +                   (match_operand 2 ""))
> > +             (match_operand 4 "const_int_operand")))
> > +   (unspec:DI [(match_operand:DI 3 "const_int_operand")] UNSPEC_CALLEE_ABI)
> > +   (return)]
> > +  "SIBLING_CALL_P (insn)"
> > +{
> > +  return aarch64_output_kcfi_insn (insn, &operands[1]);
> > +}
> > +  [(set_attr "type" "branch")])
> > +
> >  (define_insn "*sibcall_value_insn"
> >    [(set (match_operand 0 "")
> >         (call (mem:DI
> > @@ -1601,10 +1654,7 @@
> >    "SIBLING_CALL_P (insn)"
> >    {
> >      if (which_alternative == 0)
> > -      {
> > -       output_asm_insn ("br\\t%1", operands);
> > -       return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
> > -      }
> > +      return aarch64_indirect_branch_asm (operands[1]);
> >      return "b\\t%c1";
> >    }
> >    [(set_attr "type" "branch, branch")
> > diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> > index 6dfdaa4fb9b0..86e3af992d2a 100644
> > --- a/gcc/config/aarch64/aarch64.cc
> > +++ b/gcc/config/aarch64/aarch64.cc
> > @@ -83,6 +83,7 @@
> >  #include "rtlanal.h"
> >  #include "tree-dfa.h"
> >  #include "asan.h"
> > +#include "kcfi.h"
> >  #include "aarch64-elf-metadata.h"
> >  #include "aarch64-feature-deps.h"
> >  #include "config/arm/aarch-common.h"
> > @@ -11965,6 +11966,16 @@ aarch64_expand_call (rtx result, rtx mem, rtx 
> > cookie, bool sibcall)
> >
> >    call = gen_rtx_CALL (VOIDmode, mem, const0_rtx);
> >
> > +  /* Only indirect calls need KCFI instrumentation.  */
> > +  bool is_direct_call = SYMBOL_REF_P (XEXP (mem, 0));
> > +  rtx kcfi_type_rtx = is_direct_call ? NULL_RTX
> > +    : kcfi_get_type_id_for_expanding_gimple_call ();
> > +  if (kcfi_type_rtx)
> > +    {
> > +      /* Wrap call in KCFI.  */
> > +      call = gen_rtx_KCFI (VOIDmode, call, kcfi_type_rtx);
> > +    }
> > +
> >    if (result != NULL_RTX)
> >      call = gen_rtx_SET (result, call);
> >
> > @@ -19225,6 +19236,9 @@ aarch64_override_options_internal (struct 
> > gcc_options *opts)
> >  #endif
> >      }
> >
> > +  if ((flag_sanitize & SANITIZE_KCFI) && TARGET_ILP32)
> > +    sorry ("%<-fsanitize=kcfi%> is not supported for %<-mabi=ilp32%>");
> > +
> >    aarch64_feature_flags isa_flags = aarch64_get_isa_flags (opts);
> >    if ((isa_flags & (AARCH64_FL_SM_ON | AARCH64_FL_ZA_ON))
> >        && !(isa_flags & AARCH64_FL_SME))
> > @@ -30822,6 +30836,18 @@ aarch64_indirect_call_asm (rtx addr)
> >    return "";
> >  }
> >
> > +/* Generate assembly for AArch64 indirect branch instruction.  ADDR is the
> > +   target address register.  Returns any additional barrier instructions
> > +   needed for SLS (Straight Line Speculation) mitigation.  */
> > +
> > +const char *
> > +aarch64_indirect_branch_asm (rtx addr)
> > +{
> > +  gcc_assert (REG_P (addr));
> > +  output_asm_insn ("br\t%0", &addr);
> > +  return aarch64_sls_barrier (aarch64_harden_sls_retbr_p ());
> > +}
> > +
> >  /* Emit the assembly instruction to load the thread pointer into DEST.
> >     Select between different tpidr_elN registers depending on -mtp= 
> > setting.  */
> >
> > @@ -33089,6 +33115,85 @@ aarch64_libgcc_floating_mode_supported_p
> >  #undef TARGET_DOCUMENTATION_NAME
> >  #define TARGET_DOCUMENTATION_NAME "AArch64"
> >
> > +/* Output the assembly for a KCFI checked call instruction.  INSN is the
> > +   RTL instruction being processed.  OPERANDS is the array of RTL operands
> > +   where operands[0] is the call target register, operands[3] is the KCFI
> > +   type ID constant.  Returns the appropriate call instruction string.  */
> > +
> > +const char *
> > +aarch64_output_kcfi_insn (rtx_insn *insn, rtx *operands)
> > +{
> > +  /* Target register is operands[0].  */
> > +  rtx target_reg = operands[0];
> > +  gcc_assert (REG_P (target_reg));
> > +
> > +  /* Get KCFI type ID from operand[3].  */
> > +  uint32_t type_id = (uint32_t) INTVAL (operands[3]);
> > +
> > +  /* Calculate typeid offset from call target.  */
> > +  HOST_WIDE_INT offset = -kcfi_get_typeid_offset ();
> > +
> > +  /* Get unique label number for this KCFI check.  */
> > +  int labelno = kcfi_next_labelno ();
> > +
> > +  /* Generate custom label names.  */
> > +  char trap_name[32];
> > +  char call_name[32];
> > +  ASM_GENERATE_INTERNAL_LABEL (trap_name, "Lkcfi_trap", labelno);
> > +  ASM_GENERATE_INTERNAL_LABEL (call_name, "Lkcfi_call", labelno);
> > +
> > +  rtx temp_operands[3];
> > +
> > +  /* Load actual type into w16 from memory at offset using ldur.  */
> > +  temp_operands[0] = gen_rtx_REG (SImode, R16_REGNUM);
> > +  temp_operands[1] = target_reg;
> > +  temp_operands[2] = GEN_INT (offset);
> > +  output_asm_insn ("ldur\t%w0, [%1, #%2]", temp_operands);
> 
> Why not just:
>   temp_operands[0] = target_reg;
>   temp_operands[0] = GEN_INT (offset);
>   output_asm_insn ("ldur\tw16, [%0, #%1]", temp_operands);

Oops, yes. I have rewritten these handlers so many times I've lost my
mind. :)

> 
> > +
> > +  /* Load expected type low 16 bits into w17.  */
> > +  temp_operands[0] = gen_rtx_REG (SImode, R17_REGNUM);
> > +  temp_operands[1] = GEN_INT (type_id & 0xFFFF);
> > +  output_asm_insn ("mov\t%w0, #%1", temp_operands);
> Likewise.
> Also why not use fprintf here?
> Like:
> fprintf(asm_out_file, "mov\t%w0, #%d\n", type_id & 0xFFFF);

It seemed like directly using fprintf() was frowned on, and the
output_asm_insn with an operands array was the "correct" way to do all
of these? I'm happy to do it however! :)

> > +#undef TARGET_KCFI_SUPPORTED
> > +#define TARGET_KCFI_SUPPORTED hook_bool_void_true
> 
> Why return true always for it being supported? I thought this is not
> supported with ilp32, it is definitely not supported for mingw. Is it
> supported with -elf targets rather than -linux-gnu targets?

Ah, it seemed like the only place I could do the checking for ilp32,
etc was in aarch64_override_options_internal. I'll see if I can figure
out how to move that into the TARGET hooks, but all the examples I could
find where in the override_options functions...

Thanks for the review! I'll do more fixing. :)

-Kees

-- 
Kees Cook

Reply via email to