All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kees Cook <kees@kernel.org>
To: Qing Zhao <qing.zhao@oracle.com>
Cc: Kees Cook <kees@kernel.org>,
	gcc-patches@gcc.gnu.org, Joseph Myers <josmyers@redhat.com>,
	Richard Biener <rguenther@suse.de>, Jan Hubicka <hubicka@ucw.cz>,
	Richard Earnshaw <richard.earnshaw@arm.com>,
	Richard Sandiford <richard.sandiford@arm.com>,
	Marcus Shawcroft <marcus.shawcroft@arm.com>,
	Kyrylo Tkachov <kyrylo.tkachov@arm.com>,
	Kito Cheng <kito.cheng@gmail.com>,
	Palmer Dabbelt <palmer@dabbelt.com>,
	Andrew Waterman <andrew@sifive.com>,
	Jim Wilson <jim.wilson.gcc@gmail.com>,
	Peter Zijlstra <peterz@infradead.org>,
	Dan Li <ashimida.1990@gmail.com>,
	linux-hardening@vger.kernel.org
Subject: [RFC PATCH 6/7] riscv: Add RISC-V Kernel Control Flow Integrity implementation
Date: Thu, 21 Aug 2025 00:26:39 -0700	[thread overview]
Message-ID: <20250821072708.3109244-6-kees@kernel.org> (raw)
In-Reply-To: <20250821064202.work.893-kees@kernel.org>

Implement RISC-V-specific KCFI backend.

- Function preamble generation using .word directives for type ID storage
  at offset from function entry point (no prefix NOPs needed due to
  natural 4-byte instruction alignment).

- Scratch register allocation using t1/t2 (x6/x7) following RISC-V
  procedure call standard for temporary registers.

- Support for both regular calls (JALR) and sibling calls (JR) with
  appropriate register usage and jump instructions.

- Integration with .kcfi_traps section for debugger/runtime metadata
  (like x86_64).

- Atomic bundled KCFI check + call/jump sequences using UNSPECV_KCFI_CHECK
  to prevent optimizer separation and maintain security properties.

Assembly Code Pattern for RISC-V:
  lw      t1, -4(target_reg)        ; Load actual type ID from preamble
  lui     t2, %hi(expected_type)    ; Load expected type (upper 20 bits)
  addiw   t2, t2, %lo(expected_type) ; Add lower 12 bits (sign-extended)
  beq     t1, t2, .Lpass            ; Branch if types match
  .Ltrap: ebreak                    ; Environment break trap on mismatch
  .Lpass: jalr/jr target_reg        ; Execute validated indirect transfer

Build tested with Linux kernel ARCH=riscv (I am still building a proper
risc-v emulation setup). Run tested via userspace binaries.

Signed-off-by: Kees Cook <kees@kernel.org>
---
 gcc/config/riscv/riscv-protos.h |   1 +
 gcc/config/riscv/riscv.cc       | 157 ++++++++++++++++++++++++++++++++
 gcc/config/riscv/riscv.md       |  49 ++++++++++
 gcc/doc/invoke.texi             |  13 +++
 4 files changed, 220 insertions(+)

diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index 539321ff95b8..1d343c529934 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -126,6 +126,7 @@ extern bool riscv_split_64bit_move_p (rtx, rtx);
 extern void riscv_split_doubleword_move (rtx, rtx);
 extern const char *riscv_output_move (rtx, rtx);
 extern const char *riscv_output_return ();
+extern const char *riscv_output_kcfi_checked_call (uint32_t, HOST_WIDE_INT, bool);
 extern void riscv_declare_function_name (FILE *, const char *, tree);
 extern void riscv_declare_function_size (FILE *, const char *, tree);
 extern void riscv_asm_output_alias (FILE *, const tree, const tree);
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 0a9fcef37029..5daa5427568d 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -81,6 +81,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cgraph.h"
 #include "langhooks.h"
 #include "gimplify.h"
+#include "kcfi.h"
 
 /* This file should be included last.  */
 #include "target-def.h"
@@ -11156,6 +11157,9 @@ riscv_declare_function_name (FILE *stream, const char *name, tree fndecl)
 	    fprintf (stream, "\t# tune = %s\n", local_tune_str);
 	}
     }
+
+  /* Emit KCFI preamble for non-patchable functions.  */
+  kcfi_emit_preamble_if_needed (stream, fndecl, false, 0, name);
 }
 
 void
@@ -11418,6 +11422,147 @@ riscv_convert_vector_chunks (struct gcc_options *opts)
     return 1;
 }
 
+/* KCFI (Kernel Control Flow Integrity) support.  */
+
+/* Generate KCFI checked call RTL pattern following AArch64 approach. */
+static rtx
+riscv_kcfi_gen_checked_call (rtx call_insn, rtx target_reg, uint32_t expected_type,
+                             HOST_WIDE_INT prefix_nops)
+{
+  /* For RISC-V, we create an RTL bundle that combines the KCFI check
+     with the call instruction in an atomic sequence.  */
+
+  if (!REG_P (target_reg))
+    {
+      /* If not a register, load it into t1.  */
+      rtx temp = gen_rtx_REG (Pmode, T1_REGNUM);
+      emit_move_insn (temp, target_reg);
+      target_reg = temp;
+    }
+
+  /* Generate the bundled KCFI check + call pattern.  */
+  rtx pattern;
+  if (CALL_P (call_insn))
+    {
+      rtx call_pattern = PATTERN (call_insn);
+
+      /* Create labels used by both call and sibcall patterns.  */
+      rtx pass_label = gen_label_rtx ();
+      rtx trap_label = gen_label_rtx ();
+
+      /* Check if it's a sibling call.  */
+      if (find_reg_note (call_insn, REG_NORETURN, NULL_RTX)
+          || (GET_CODE (call_pattern) == PARALLEL
+              && GET_CODE (XVECEXP (call_pattern, 0, XVECLEN (call_pattern, 0) - 1)) == RETURN))
+        {
+          /* Generate sibling call bundle.  */
+          pattern = gen_riscv_kcfi_checked_sibcall (target_reg,
+                                                    gen_int_mode (expected_type, SImode),
+                                                    gen_int_mode (prefix_nops, SImode),
+                                                    pass_label,
+                                                    trap_label);
+        }
+      else
+        {
+          /* Generate regular call bundle.  */
+          pattern = gen_riscv_kcfi_checked_call (target_reg,
+                                                 gen_int_mode (expected_type, SImode),
+                                                 gen_int_mode (prefix_nops, SImode),
+                                                 pass_label,
+                                                 trap_label);
+        }
+    }
+  else
+    {
+      error ("KCFI: Expected call instruction");
+      gcc_unreachable ();
+    }
+
+  return pattern;
+}
+
+/* Add RISC-V specific register clobbers for KCFI instrumentation.  */
+static void
+riscv_kcfi_add_clobbers (rtx_insn *call_insn)
+{
+  /* Add t1/t2 clobbers so register allocator knows they'll be used.  */
+  rtx usage = CALL_INSN_FUNCTION_USAGE (call_insn);
+  clobber_reg (&usage, gen_rtx_REG (DImode, T1_REGNUM));
+  clobber_reg (&usage, gen_rtx_REG (DImode, T2_REGNUM));
+  CALL_INSN_FUNCTION_USAGE (call_insn) = usage;
+}
+
+/* Calculate prefix NOPs (RISC-V doesn't need additional NOPs).  */
+static int
+riscv_kcfi_calculate_prefix_nops (HOST_WIDE_INT prefix_nops ATTRIBUTE_UNUSED)
+{
+  /* RISC-V instructions are 4-byte aligned, no additional NOPs needed.  */
+  return 0;
+}
+
+/* Emit RISC-V type ID instruction.  */
+static void
+riscv_kcfi_emit_type_id_instruction (FILE *file, uint32_t type_id)
+{
+  /* Emit .word directive with type ID.  */
+  fprintf (file, "\t.word\t0x%08x\n", type_id);
+}
+
+/* Output KCFI checked call instruction sequence.  */
+const char *
+riscv_output_kcfi_checked_call (uint32_t expected_type, HOST_WIDE_INT prefix_nops, bool sibling_call)
+{
+  static char buf[512];
+  
+  /* Calculate offset for type ID load, accounting for prefix NOPs.  */
+  HOST_WIDE_INT offset = -(4 + prefix_nops);
+  
+  /* Generate unique labels.  */
+  static int label_counter = 0;
+  int pass_label_num = ++label_counter;
+  int trap_label_num = ++label_counter;
+  
+  /* Generate the KCFI check sequence:
+     lw      t1, -4(target_reg)           # Load actual type from function[-4]
+     lui     t2, %hi(expected_type_id)    # Load upper 20 bits of expected type
+     addiw   t2, t2, %lo(expected_type_id) # Add lower 12 bits (sign-extended)
+     beq     t1, t2, .Lpass               # Branch if types match
+     .Ltrap:
+     ebreak                               # Environment break (trap on mismatch)
+     .Lpass:
+     jalr    target_reg                   # Execute indirect function call
+  */
+  
+  /* Manually split expected_type as required by agentic/kcfi-riscv.md:
+     - Upper 20 bits for lui instruction
+     - Lower 12 bits for addiw instruction (sign-extended) */
+  uint32_t hi20 = (expected_type >> 12) & 0xFFFFF;  /* Upper 20 bits */
+  int32_t lo12 = ((int32_t)(expected_type << 20)) >> 20;  /* Lower 12 bits, sign-extended */
+
+  snprintf (buf, sizeof (buf),
+    "lw\tt1, %ld(%%0)\n"
+    "\tlui\tt2, %u\n"
+    "\taddiw\tt2, t2, %d\n"
+    "\tbeq\tt1, t2, .Lkcfi_pass_%d\n"
+    ".Lkcfi_trap_%d:\n"
+    "\tebreak\n"
+    "\t.pushsection\t.kcfi_traps,\"ao\",@progbits,.text\n"
+    ".Lkcfi_trap_entry_%d:\n"
+    "\t.word\t.Lkcfi_trap_%d - .Lkcfi_trap_entry_%d\n"
+    "\t.popsection\n"
+    ".Lkcfi_pass_%d:\n"
+    "\t%s\t%%0",
+    offset, hi20, lo12,
+    pass_label_num,
+    trap_label_num,
+    trap_label_num,
+    trap_label_num, trap_label_num,
+    pass_label_num,
+    sibling_call ? "jr" : "jalr");
+    
+  return buf;
+}
+
 /* 'Unpack' up the internal tuning structs and update the options
     in OPTS.  The caller must have set up selected_tune and selected_arch
     as all the other target-specific codegen decisions are
@@ -11525,6 +11670,7 @@ riscv_override_options_internal (struct gcc_options *opts)
       opts->x_flag_cf_protection
       = (cf_protection_level) (opts->x_flag_cf_protection | CF_SET);
     }
+
 }
 
 /* Implement TARGET_OPTION_OVERRIDE.  */
@@ -11715,6 +11861,16 @@ riscv_option_override (void)
 
   riscv_override_options_internal (&global_options);
 
+  /* Initialize KCFI hooks if KCFI is enabled.  */
+  if (flag_sanitize & SANITIZE_KCFI)
+    {
+      kcfi_target.gen_kcfi_checked_call = riscv_kcfi_gen_checked_call;
+      kcfi_target.add_kcfi_clobbers = riscv_kcfi_add_clobbers;
+      kcfi_target.calculate_prefix_nops = riscv_kcfi_calculate_prefix_nops;
+      kcfi_target.emit_type_id_instruction = riscv_kcfi_emit_type_id_instruction;
+      /* Note: mask_type_id is NULL - no masking needed for RISC-V.  */
+    }
+
   /* Save these options as the default ones in case we push and pop them later
      while processing functions with potential target attributes.  */
   target_option_default_node = target_option_current_node
@@ -15795,6 +15951,7 @@ synthesize_and (rtx operands[3])
 #define TARGET_VECTORIZE_BUILTIN_VECTORIZATION_COST \
   riscv_builtin_vectorization_cost
 
+
 #undef TARGET_VECTORIZE_CREATE_COSTS
 #define TARGET_VECTORIZE_CREATE_COSTS riscv_vectorize_create_costs
 
diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
index 578dd43441e2..6e9545e9d003 100644
--- a/gcc/config/riscv/riscv.md
+++ b/gcc/config/riscv/riscv.md
@@ -152,6 +152,9 @@
   ;; XTheadInt unspec
   UNSPECV_XTHEADINT_PUSH
   UNSPECV_XTHEADINT_POP
+
+  ;; KCFI unspec
+  UNSPECV_KCFI_CHECK
 ])
 
 (define_constants
@@ -4078,6 +4081,52 @@
   DONE;
 })
 
+;; KCFI checked call patterns
+
+(define_insn "riscv_kcfi_checked_call"
+  [(parallel [(call (mem:DI (match_operand:DI 0 "register_operand" "r"))
+                    (const_int 0))
+              (unspec:DI [(const_int 0)] UNSPEC_CALLEE_CC)
+              (unspec_volatile:DI [(match_operand:SI 1 "const_int_operand" "n")  ; type_id
+                                   (match_operand:SI 2 "const_int_operand" "n")  ; prefix_nops
+                                   (label_ref (match_operand 3))  ; pass label
+                                   (label_ref (match_operand 4))] ; trap label
+                                  UNSPECV_KCFI_CHECK)
+              (clobber (reg:DI RETURN_ADDR_REGNUM))
+              (clobber (reg:DI T1_REGNUM))  ; t1 - scratch for loaded type
+              (clobber (reg:DI T2_REGNUM))])] ; t2 - scratch for expected type
+  "flag_sanitize & SANITIZE_KCFI"
+  "*
+  {
+    uint32_t type_id = INTVAL (operands[1]);
+    HOST_WIDE_INT prefix_nops = INTVAL (operands[2]);
+    return riscv_output_kcfi_checked_call (type_id, prefix_nops, false);
+  }"
+  [(set_attr "type" "call")
+   (set_attr "length" "24")])
+
+(define_insn "riscv_kcfi_checked_sibcall"
+  [(parallel [(call (mem:DI (match_operand:DI 0 "register_operand" "r"))
+                    (const_int 0))
+              (unspec:DI [(const_int 0)] UNSPEC_CALLEE_CC)
+              (unspec_volatile:DI [(match_operand:SI 1 "const_int_operand" "n")  ; type_id
+                                   (match_operand:SI 2 "const_int_operand" "n")  ; prefix_nops
+                                   (label_ref (match_operand 3))  ; pass label
+                                   (label_ref (match_operand 4))] ; trap label
+                                  UNSPECV_KCFI_CHECK)
+              (return)
+              (clobber (reg:DI T1_REGNUM))  ; t1 - scratch for loaded type
+              (clobber (reg:DI T2_REGNUM))])] ; t2 - scratch for expected type
+  "flag_sanitize & SANITIZE_KCFI"
+  "*
+  {
+    uint32_t type_id = INTVAL (operands[1]);
+    HOST_WIDE_INT prefix_nops = INTVAL (operands[2]);
+    return riscv_output_kcfi_checked_call (type_id, prefix_nops, true);
+  }"
+  [(set_attr "type" "call")
+   (set_attr "length" "24")])
+
 (define_insn "nop"
   [(const_int 0)]
   ""
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 161c7024f842..f82d0464590d 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -18350,6 +18350,19 @@ trap is taken, allowing the kernel to identify both the KCFI violation
 and the involved registers for detailed diagnostics (eliminating the need
 for a separate @code{.kcfi_traps} section as used on x86_64).
 
+On RISC-V, KCFI type identifiers are emitted as a @code{.word ID}
+directive (a 32-bit constant) before the function entry, similar to AArch64.
+RISC-V's natural 4-byte instruction alignment eliminates the need for
+additional padding NOPs.  When used with @option{-fpatchable-function-entry},
+the type identifier is placed before any patchable NOPs.  The runtime check
+loads the actual type using @code{lw t1, OFFSET(target_reg)}, where the
+offset accounts for any prefix NOPs, constructs the expected type using
+@code{lui} and @code{addiw} instructions into @code{t2}, and compares them
+with @code{beq}.  Type mismatches trigger an @code{ebreak} instruction.
+Like x86_64, RISC-V uses a @code{.kcfi_traps} section to map trap locations
+to their corresponding function entry points for debugging (RISC-V lacks
+ESR-style trap encoding unlike AArch64).
+
 KCFI is intended primarily for kernel code and may not be suitable
 for user-space applications that rely on techniques incompatible
 with strict type checking of indirect calls.
-- 
2.34.1


  parent reply	other threads:[~2025-08-21  7:27 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-21  7:26 [RFC PATCH 0/7] Introduce Kernel Control Flow Integrity ABI [PR107048] Kees Cook
2025-08-21  7:26 ` [RFC PATCH 1/7] sanitizer: Expand sanitizer flag from 32-bit to 64-bit Kees Cook
2025-08-21  7:26 ` [RFC PATCH 2/7] mangle: Introduce C typeinfo mangling API Kees Cook
     [not found]   ` <CALvbMcAPV1eB6nocPAS=qR8SCiQyU43v911R8S7Ah_=G7yK-+g@mail.gmail.com>
2025-08-21  8:29     ` Andrew Pinski
2025-08-21 16:16     ` Kees Cook
2025-08-21 16:24       ` Andrew Pinski
2025-08-21 19:14       ` Qing Zhao
2025-08-21 21:29         ` Kees Cook
2025-08-22 15:11           ` Qing Zhao
2025-08-22 19:02             ` Kees Cook
2025-08-22 20:29               ` Qing Zhao
2025-08-22 22:29                 ` Kees Cook
2025-08-25  8:13                   ` Peter Zijlstra
2025-08-25 13:56                     ` Qing Zhao
2025-08-21  7:26 ` [RFC PATCH 3/7] kcfi: Add core Kernel Control Flow Integrity infrastructure Kees Cook
     [not found]   ` <CALvbMcA+8iHo+zCCvs4UdAg9PVQVtgOno-rtMS4i5YajrjkyGw@mail.gmail.com>
2025-08-21  9:12     ` Peter Zijlstra
2025-08-21 11:01       ` Richard Biener
2025-08-21 14:25         ` Peter Zijlstra
2025-08-21 18:09           ` Qing Zhao
2025-08-22  5:15             ` Kees Cook
2025-08-22 10:03               ` Peter Zijlstra
2025-08-21 19:57         ` Kees Cook
2025-08-22  6:53           ` Richard Biener
2025-08-22 19:23             ` Kees Cook
     [not found]       ` <CA+=Sn1koTTQaXDnAVWtVU6ACWwhD08NR5nDJO236Pmcoi2X9qA@mail.gmail.com>
2025-08-22  7:51         ` Peter Zijlstra
2025-08-22  8:24           ` Peter Zijlstra
2025-08-22  8:47             ` Kees Cook
2025-08-22  5:10     ` Kees Cook
2025-08-22  5:27       ` Andrew Pinski
2025-08-28 14:57   ` Qing Zhao
2025-09-04  4:24     ` Kees Cook
2025-09-04  7:16       ` Peter Zijlstra
2025-09-04 14:41       ` Qing Zhao
2025-08-21  7:26 ` [RFC PATCH 4/7] x86: Add x86_64 Kernel Control Flow Integrity implementation Kees Cook
2025-08-21  9:29   ` Peter Zijlstra
2025-08-21 18:46     ` Kees Cook
2025-08-21 19:03       ` Kees Cook
2025-08-22  8:19       ` Peter Zijlstra
2025-08-22  8:36         ` Kees Cook
2025-08-22  8:55           ` Peter Zijlstra
2025-08-21  7:26 ` [RFC PATCH 5/7] aarch64: Add AArch64 " Kees Cook
2025-08-21  7:26 ` Kees Cook [this message]
2025-08-21  7:26 ` [RFC PATCH 7/7] kcfi: Add regression test suite Kees Cook

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250821072708.3109244-6-kees@kernel.org \
    --to=kees@kernel.org \
    --cc=andrew@sifive.com \
    --cc=ashimida.1990@gmail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=hubicka@ucw.cz \
    --cc=jim.wilson.gcc@gmail.com \
    --cc=josmyers@redhat.com \
    --cc=kito.cheng@gmail.com \
    --cc=kyrylo.tkachov@arm.com \
    --cc=linux-hardening@vger.kernel.org \
    --cc=marcus.shawcroft@arm.com \
    --cc=palmer@dabbelt.com \
    --cc=peterz@infradead.org \
    --cc=qing.zhao@oracle.com \
    --cc=rguenther@suse.de \
    --cc=richard.earnshaw@arm.com \
    --cc=richard.sandiford@arm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.