[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250905002418.464643-6-kees@kernel.org>
Date: Thu, 4 Sep 2025 17:24:14 -0700
From: Kees Cook <kees@...nel.org>
To: Qing Zhao <qing.zhao@...cle.com>
Cc: Kees Cook <kees@...nel.org>,
Andrew Pinski <pinskia@...il.com>,
Richard Biener <rguenther@...e.de>,
Joseph Myers <josmyers@...hat.com>,
Jan Hubicka <hubicka@....cz>,
Richard Earnshaw <richard.earnshaw@....com>,
Richard Sandiford <richard.sandiford@....com>,
Marcus Shawcroft <marcus.shawcroft@....com>,
Kyrylo Tkachov <kyrylo.tkachov@....com>,
Kito Cheng <kito.cheng@...il.com>,
Palmer Dabbelt <palmer@...belt.com>,
Andrew Waterman <andrew@...ive.com>,
Jim Wilson <jim.wilson.gcc@...il.com>,
Peter Zijlstra <peterz@...radead.org>,
Dan Li <ashimida.1990@...il.com>,
Sami Tolvanen <samitolvanen@...gle.com>,
Ramon de C Valle <rcvalle@...gle.com>,
Joao Moreira <joao@...rdrivepizza.com>,
Nathan Chancellor <nathan@...nel.org>,
Bill Wendling <morbo@...gle.com>,
gcc-patches@....gnu.org,
linux-hardening@...r.kernel.org
Subject: [PATCH v2 6/7] riscv: Add RISC-V Kernel Control Flow Integrity implementation
Implement RISC-V-specific KCFI backend.
- Function preamble generation using .word directives for type ID storage
at offset from function entry point (no alignment NOPs needed due to
fix 4-byte instruction size).
- Scratch register allocation using t1/t2 (x6/x7) following RISC-V
procedure call standard for temporary registers.
- Integration with .kcfi_traps section for debugger/runtime metadata
(like x86_64).
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, .Lkcfi_call ; Branch if types match
.Lkcfi_trap: ebreak ; Environment break trap on mismatch
.Lkcfi_call: jalr/jr target_reg ; Execute validated indirect transfer
Build and run tested with Linux kernel ARCH=riscv.
gcc/ChangeLog:
config/riscv/riscv-protos.h: Declare KCFI helpers.
config/riscv/riscv.cc (riscv_maybe_wrap_call_with_kcfi): New
function, to wrap calls.
(riscv_maybe_wrap_call_value_with_kcfi): New function, to
wrap calls with return values.
(riscv_output_kcfi_insn): New function to emit KCFI assembly.
config/riscv/riscv.md: Add KCFI RTL patterns and hook expansion.
doc/invoke.texi: Document riscv nuances.
Signed-off-by: Kees Cook <kees@...nel.org>
---
gcc/config/riscv/riscv-protos.h | 3 +
gcc/config/riscv/riscv.cc | 147 ++++++++++++++++++++++++++++++++
gcc/config/riscv/riscv.md | 74 ++++++++++++++--
gcc/doc/invoke.texi | 13 +++
4 files changed, 231 insertions(+), 6 deletions(-)
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index 2d60a0ad44b3..0e916fbdde13 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -126,6 +126,9 @@ 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 rtx riscv_maybe_wrap_call_with_kcfi (rtx, rtx);
+extern rtx riscv_maybe_wrap_call_value_with_kcfi (rtx, rtx);
+extern const char *riscv_output_kcfi_insn (rtx_insn *, rtx *);
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 41ee81b93acf..8dc54ffb19fe 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"
@@ -11346,6 +11347,149 @@ riscv_convert_vector_chunks (struct gcc_options *opts)
return 1;
}
+/* Apply KCFI wrapping to call pattern if needed. */
+rtx
+riscv_maybe_wrap_call_with_kcfi (rtx pat, rtx addr)
+{
+ /* Only indirect calls need KCFI instrumentation. */
+ bool is_direct_call = SYMBOL_REF_P (addr);
+ if (!is_direct_call)
+ {
+ rtx kcfi_type_rtx = kcfi_get_call_type_id ();
+ if (kcfi_type_rtx)
+ {
+ /* Extract the CALL from the PARALLEL and wrap it with KCFI */
+ rtx call_rtx = XVECEXP (pat, 0, 0);
+ rtx kcfi_call = gen_rtx_KCFI (VOIDmode, call_rtx, kcfi_type_rtx);
+
+ /* Replace the CALL in the PARALLEL with the KCFI-wrapped call */
+ XVECEXP (pat, 0, 0) = kcfi_call;
+ }
+ }
+ return pat;
+}
+
+/* Apply KCFI wrapping to call_value pattern if needed. */
+rtx
+riscv_maybe_wrap_call_value_with_kcfi (rtx pat, rtx addr)
+{
+ /* Only indirect calls need KCFI instrumentation. */
+ bool is_direct_call = SYMBOL_REF_P (addr);
+ if (!is_direct_call)
+ {
+ rtx kcfi_type_rtx = kcfi_get_call_type_id ();
+ if (kcfi_type_rtx)
+ {
+ /* Extract the SET from the PARALLEL and wrap its CALL with KCFI */
+ rtx set_rtx = XVECEXP (pat, 0, 0);
+ rtx call_rtx = SET_SRC (set_rtx);
+ rtx kcfi_call = gen_rtx_KCFI (VOIDmode, call_rtx, kcfi_type_rtx);
+
+ /* Replace the CALL in the SET with the KCFI-wrapped call */
+ SET_SRC (set_rtx) = kcfi_call;
+ }
+ }
+ return pat;
+}
+
+/* Output the assembly for a KCFI checked call instruction. */
+const char *
+riscv_output_kcfi_insn (rtx_insn *insn, rtx *operands)
+{
+ /* Target register. */
+ rtx target_reg = operands[0];
+ gcc_assert (REG_P (target_reg));
+
+ /* Get KCFI type ID. */
+ uint32_t expected_type = (uint32_t) INTVAL (operands[3]);
+
+ /* Calculate typeid offset from call target. */
+ HOST_WIDE_INT offset = -(4 + kcfi_patchable_entry_prefix_nops);
+
+ /* Choose scratch registers that don't conflict with target. */
+ unsigned temp1_regnum = T1_REGNUM;
+ unsigned temp2_regnum = T2_REGNUM;
+
+ if (REGNO (target_reg) == T1_REGNUM)
+ temp1_regnum = T3_REGNUM;
+ else if (REGNO (target_reg) == T2_REGNUM)
+ temp2_regnum = T3_REGNUM;
+
+ /* Generate labels internally. */
+ rtx trap_label = gen_label_rtx ();
+ rtx call_label = gen_label_rtx ();
+
+ /* Get label numbers for custom naming. */
+ int trap_labelno = CODE_LABEL_NUMBER (trap_label);
+ int call_labelno = CODE_LABEL_NUMBER (call_label);
+
+ /* Generate custom label names. */
+ char trap_name[32];
+ char call_name[32];
+ ASM_GENERATE_INTERNAL_LABEL (trap_name, "Lkcfi_trap", trap_labelno);
+ ASM_GENERATE_INTERNAL_LABEL (call_name, "Lkcfi_call", call_labelno);
+
+ /* Split expected_type for RISC-V immediate encoding.
+ If bit 11 is set, increment upper 20 bits to compensate for sign extension. */
+ int32_t lo12 = ((int32_t)(expected_type << 20)) >> 20;
+ uint32_t hi20 = ((expected_type >> 12) + ((expected_type & 0x800) ? 1 : 0)) & 0xFFFFF;
+
+ rtx temp_operands[3];
+
+ /* Load actual type from memory at offset. */
+ temp_operands[0] = gen_rtx_REG (SImode, temp1_regnum);
+ temp_operands[1] = gen_rtx_MEM (SImode,
+ gen_rtx_PLUS (DImode, target_reg,
+ GEN_INT (offset)));
+ output_asm_insn ("lw\t%0, %1", temp_operands);
+
+ /* Load expected type using lui + addiw for proper sign extension. */
+ temp_operands[0] = gen_rtx_REG (SImode, temp2_regnum);
+ temp_operands[1] = GEN_INT (hi20);
+ output_asm_insn ("lui\t%0, %1", temp_operands);
+
+ temp_operands[0] = gen_rtx_REG (SImode, temp2_regnum);
+ temp_operands[1] = gen_rtx_REG (SImode, temp2_regnum);
+ temp_operands[2] = GEN_INT (lo12);
+ output_asm_insn ("addiw\t%0, %1, %2", temp_operands);
+
+ /* Output conditional branch to call label. */
+ fprintf (asm_out_file, "\tbeq\t%s, %s, ", reg_names[temp1_regnum], reg_names[temp2_regnum]);
+ assemble_name (asm_out_file, call_name);
+ fputc ('\n', asm_out_file);
+
+ /* Output trap label and ebreak instruction. */
+ ASM_OUTPUT_LABEL (asm_out_file, trap_name);
+ output_asm_insn ("ebreak", operands);
+
+ /* Use common helper for trap section entry. */
+ rtx trap_label_sym = gen_rtx_SYMBOL_REF (Pmode, trap_name);
+ kcfi_emit_traps_section (asm_out_file, trap_label_sym);
+
+ /* Output pass/call label. */
+ ASM_OUTPUT_LABEL (asm_out_file, call_name);
+
+ /* Execute the indirect call. */
+ if (SIBLING_CALL_P (insn))
+ {
+ /* Tail call uses x0 (zero register) to avoid saving return address. */
+ temp_operands[0] = gen_rtx_REG (DImode, 0); /* x0 */
+ temp_operands[1] = target_reg; /* target register */
+ temp_operands[2] = const0_rtx;
+ output_asm_insn ("jalr\t%0, %1, %2", temp_operands);
+ }
+ else
+ {
+ /* Regular call uses x1 (return address register). */
+ temp_operands[0] = gen_rtx_REG (DImode, RETURN_ADDR_REGNUM); /* x1 */
+ temp_operands[1] = target_reg; /* target register */
+ temp_operands[2] = const0_rtx;
+ output_asm_insn ("jalr\t%0, %1, %2", temp_operands);
+ }
+
+ return "";
+}
+
/* '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
@@ -15898,6 +16042,9 @@ riscv_prefetch_offset_address_p (rtx x, machine_mode mode)
#define TARGET_GET_FUNCTION_VERSIONS_DISPATCHER \
riscv_get_function_versions_dispatcher
+#undef TARGET_KCFI_SUPPORTED
+#define TARGET_KCFI_SUPPORTED hook_bool_void_true
+
#undef TARGET_DOCUMENTATION_NAME
#define TARGET_DOCUMENTATION_NAME "RISC-V"
diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
index 4718a75598a6..9a9524a5e46f 100644
--- a/gcc/config/riscv/riscv.md
+++ b/gcc/config/riscv/riscv.md
@@ -3982,10 +3982,25 @@
""
{
rtx target = riscv_legitimize_call_address (XEXP (operands[0], 0));
- emit_call_insn (gen_sibcall_internal (target, operands[1], operands[2]));
+ rtx pat = gen_sibcall_internal (target, operands[1], operands[2]);
+ pat = riscv_maybe_wrap_call_with_kcfi (pat, target);
+ emit_call_insn (pat);
DONE;
})
+;; KCFI sibling call - matches KCFI wrapper RTL
+(define_insn "*kcfi_sibcall_insn"
+ [(kcfi (call (mem:SI (match_operand:DI 0 "call_insn_operand" "l"))
+ (match_operand 1 ""))
+ (match_operand 3 "const_int_operand"))
+ (use (unspec:SI [(match_operand 2 "const_int_operand")] UNSPEC_CALLEE_CC))]
+ "SIBLING_CALL_P (insn)"
+{
+ return riscv_output_kcfi_insn (insn, operands);
+}
+ [(set_attr "type" "call")
+ (set_attr "length" "24")])
+
(define_insn "sibcall_internal"
[(call (mem:SI (match_operand 0 "call_insn_operand" "j,S,U"))
(match_operand 1 "" ""))
@@ -4009,11 +4024,26 @@
""
{
rtx target = riscv_legitimize_call_address (XEXP (operands[1], 0));
- emit_call_insn (gen_sibcall_value_internal (operands[0], target, operands[2],
- operands[3]));
+ rtx pat = gen_sibcall_value_internal (operands[0], target, operands[2], operands[3]);
+ pat = riscv_maybe_wrap_call_value_with_kcfi (pat, target);
+ emit_call_insn (pat);
DONE;
})
+;; KCFI sibling call with return value - matches KCFI wrapper RTL
+(define_insn "*kcfi_sibcall_value_insn"
+ [(set (match_operand 0 "")
+ (kcfi (call (mem:SI (match_operand:DI 1 "call_insn_operand" "l"))
+ (match_operand 2 ""))
+ (match_operand 4 "const_int_operand")))
+ (use (unspec:SI [(match_operand 3 "const_int_operand")] UNSPEC_CALLEE_CC))]
+ "SIBLING_CALL_P (insn)"
+{
+ return riscv_output_kcfi_insn (insn, &operands[1]);
+}
+ [(set_attr "type" "call")
+ (set_attr "length" "24")])
+
(define_insn "sibcall_value_internal"
[(set (match_operand 0 "" "")
(call (mem:SI (match_operand 1 "call_insn_operand" "j,S,U"))
@@ -4037,10 +4067,26 @@
""
{
rtx target = riscv_legitimize_call_address (XEXP (operands[0], 0));
- emit_call_insn (gen_call_internal (target, operands[1], operands[2]));
+ rtx pat = gen_call_internal (target, operands[1], operands[2]);
+ pat = riscv_maybe_wrap_call_with_kcfi (pat, target);
+ emit_call_insn (pat);
DONE;
})
+;; KCFI indirect call - matches KCFI wrapper RTL
+(define_insn "*kcfi_call_internal"
+ [(kcfi (call (mem:SI (match_operand:DI 0 "call_insn_operand" "l"))
+ (match_operand 1 "" ""))
+ (match_operand 3 "const_int_operand"))
+ (use (unspec:SI [(match_operand 2 "const_int_operand")] UNSPEC_CALLEE_CC))
+ (clobber (reg:SI RETURN_ADDR_REGNUM))]
+ "!SIBLING_CALL_P (insn)"
+{
+ return riscv_output_kcfi_insn (insn, operands);
+}
+ [(set_attr "type" "call")
+ (set_attr "length" "24")])
+
(define_insn "call_internal"
[(call (mem:SI (match_operand 0 "call_insn_operand" "l,S,U"))
(match_operand 1 "" ""))
@@ -4065,11 +4111,27 @@
""
{
rtx target = riscv_legitimize_call_address (XEXP (operands[1], 0));
- emit_call_insn (gen_call_value_internal (operands[0], target, operands[2],
- operands[3]));
+ rtx pat = gen_call_value_internal (operands[0], target, operands[2], operands[3]);
+ pat = riscv_maybe_wrap_call_value_with_kcfi (pat, target);
+ emit_call_insn (pat);
DONE;
})
+;; KCFI call with return value - matches KCFI wrapper RTL
+(define_insn "*kcfi_call_value_insn"
+ [(set (match_operand 0 "" "")
+ (kcfi (call (mem:SI (match_operand:DI 1 "call_insn_operand" "l"))
+ (match_operand 2 "" ""))
+ (match_operand 4 "const_int_operand")))
+ (use (unspec:SI [(match_operand 3 "const_int_operand")] UNSPEC_CALLEE_CC))
+ (clobber (reg:SI RETURN_ADDR_REGNUM))]
+ "!SIBLING_CALL_P (insn)"
+{
+ return riscv_output_kcfi_insn (insn, &operands[1]);
+}
+ [(set_attr "type" "call")
+ (set_attr "length" "24")])
+
(define_insn "call_value_internal"
[(set (match_operand 0 "" "")
(call (mem:SI (match_operand 1 "call_insn_operand" "l,S,U"))
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 25ee82c9cba7..43e86f4bc5b4 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -18458,6 +18458,19 @@ 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
+@...e{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
Powered by blists - more mailing lists