diff --git a/Makefile b/Makefile
index 43e08c9f95e9..7c74dac57aa4 100644
--- a/Makefile
+++ b/Makefile
@@ -926,6 +926,12 @@ KBUILD_CFLAGS	+= $(CC_FLAGS_CFI)
 export CC_FLAGS_CFI
 endif
 
+ifdef CONFIG_CFI_GCC
+CC_FLAGS_CFI	:= -fsanitize=cfi
+KBUILD_CFLAGS	+= $(CC_FLAGS_CFI)
+export CC_FLAGS_CFI
+endif
+
 ifdef CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_64B
 KBUILD_CFLAGS += -falign-functions=64
 endif
diff --git a/arch/Kconfig b/arch/Kconfig
index 1c1eca0c0019..8b43a9fd3b54 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -756,9 +756,31 @@ config CFI_CLANG
 
 	    https://clang.llvm.org/docs/ControlFlowIntegrity.html
 
+config ARCH_SUPPORTS_CFI_GCC
+	bool
+	help
+	  An architecture should select this option if it can support GCC's
+	  Control-Flow Integrity (CFI) checking.
+
+config CFI_GCC
+	bool "Use GCC's Control Flow Integrity (CFI)"
+	depends on ARCH_SUPPORTS_CFI_GCC
+	depends on $(cc-option,-fsanitize=cfi)
+	help
+	  This option enables GCC’s forward-edge Control Flow Integrity
+	  (CFI) checking, where the compiler injects a runtime check to each
+	  indirect function call to ensure the target is a valid function with
+	  the correct static type. This restricts possible call targets and
+	  makes it more difficult for an attacker to exploit bugs that allow
+	  the modification of stored function pointers. More information can be
+	  found from the compiler's documentation:
+
+	  - Clang: https://clang.llvm.org/docs/ControlFlowIntegrity.html
+	  - GCC: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#Instrumentation-Options
+
 config CFI_PERMISSIVE
 	bool "Use CFI in permissive mode"
-	depends on CFI_CLANG
+	depends on CFI_CLANG || CFI_GCC
 	help
 	  When selected, Control Flow Integrity (CFI) violations result in a
 	  warning instead of a kernel panic. This option should only be used
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 9fb9fff08c94..60fdfb01cecb 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -89,6 +89,7 @@ config ARM64
 	select ARCH_SUPPORTS_LTO_CLANG if CPU_LITTLE_ENDIAN
 	select ARCH_SUPPORTS_LTO_CLANG_THIN
 	select ARCH_SUPPORTS_CFI_CLANG
+	select ARCH_SUPPORTS_CFI_GCC
 	select ARCH_SUPPORTS_ATOMIC_RMW
 	select ARCH_SUPPORTS_INT128 if CC_HAS_INT128
 	select ARCH_SUPPORTS_NUMA_BALANCING
diff --git a/include/linux/cfi_types.h b/include/linux/cfi_types.h
index 6b8713675765..1c3b7ea6a79f 100644
--- a/include/linux/cfi_types.h
+++ b/include/linux/cfi_types.h
@@ -8,18 +8,25 @@
 #ifdef __ASSEMBLY__
 #include <linux/linkage.h>
 
-#ifdef CONFIG_CFI_CLANG
+#if defined(CONFIG_CFI_CLANG) || defined(CONFIG_CFI_GCC)
 /*
- * Use the __kcfi_typeid_<function> type identifier symbol to
+ * Use the __[k]cfi_typeid_<function> type identifier symbol to
  * annotate indirectly called assembly functions. The compiler emits
  * these symbols for all address-taken function declarations in C
  * code.
  */
 #ifndef __CFI_TYPE
+
+#ifdef CONFIG_CFI_GCC
+#define __CFI_TYPE(name)				\
+	.4byte __cfi_typeid_##name
+#else
 #define __CFI_TYPE(name)				\
 	.4byte __kcfi_typeid_##name
 #endif
 
+#endif
+
 #define SYM_TYPED_ENTRY(name, linkage, align...)	\
 	linkage(name) ASM_NL				\
 	align ASM_NL					\
@@ -29,12 +36,12 @@
 #define SYM_TYPED_START(name, linkage, align...)	\
 	SYM_TYPED_ENTRY(name, linkage, align)
 
-#else /* CONFIG_CFI_CLANG */
+#else /* defined(CONFIG_CFI_CLANG) || defined(CONFIG_CFI_GCC) */
 
 #define SYM_TYPED_START(name, linkage, align...)	\
 	SYM_START(name, linkage, align)
 
-#endif /* CONFIG_CFI_CLANG */
+#endif /* defined(CONFIG_CFI_CLANG) || defined(CONFIG_CFI_GCC) */
 
 #ifndef SYM_TYPED_FUNC_START
 #define SYM_TYPED_FUNC_START(name) 			\
diff --git a/include/linux/compiler-gcc.h b/include/linux/compiler-gcc.h
index 9b157b71036f..aec1ce327b1a 100644
--- a/include/linux/compiler-gcc.h
+++ b/include/linux/compiler-gcc.h
@@ -82,6 +82,10 @@
 #define __noscs __attribute__((__no_sanitize__("shadow-call-stack")))
 #endif
 
+#ifdef CONFIG_CFI_GCC
+#define __nocfi __attribute__((no_sanitize("cfi")))
+#endif
+
 #if __has_attribute(__no_sanitize_address__)
 #define __no_sanitize_address __attribute__((no_sanitize_address))
 #else
diff --git a/kernel/Makefile b/kernel/Makefile
index 318789c728d3..923d3e060852 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -114,6 +114,7 @@ obj-$(CONFIG_SHADOW_CALL_STACK) += scs.o
 obj-$(CONFIG_HAVE_STATIC_CALL) += static_call.o
 obj-$(CONFIG_HAVE_STATIC_CALL_INLINE) += static_call_inline.o
 obj-$(CONFIG_CFI_CLANG) += cfi.o
+obj-$(CONFIG_CFI_GCC) += cfi.o
 
 obj-$(CONFIG_PERF_EVENTS) += events/
 
diff --git a/kernel/cfi.c b/kernel/cfi.c
index 08caad776717..9bff35736756 100644
--- a/kernel/cfi.c
+++ b/kernel/cfi.c
@@ -25,6 +25,7 @@ enum bug_trap_type report_cfi_failure(struct pt_regs *regs, unsigned long addr,
 	return BUG_TRAP_TYPE_BUG;
 }
 
+#ifdef CONFIG_CFI_CLANG
 #ifdef CONFIG_ARCH_USES_CFI_TRAPS
 static inline unsigned long trap_address(s32 *p)
 {
@@ -99,3 +100,25 @@ bool is_cfi_trap(unsigned long addr)
 	return is_module_cfi_trap(addr);
 }
 #endif /* CONFIG_ARCH_USES_CFI_TRAPS */
+#endif /* CONFIG_CFI_CLANG */
+
+
+#ifdef CONFIG_CFI_GCC
+void cfi_check_failed(u32 caller_hash, u32 callee_hash, void *callee_addr)
+{
+	unsigned long pc, target;
+
+	pc = (unsigned long)__builtin_return_address(0);
+	target = (unsigned long)callee_addr;
+
+	switch (report_cfi_failure(NULL, pc, &target, caller_hash)) {
+	case BUG_TRAP_TYPE_WARN:
+		break;
+
+	default:
+		panic("Oops - CFI");
+	}
+}
+EXPORT_SYMBOL(cfi_check_failed);
+
+#endif /* CONFIG_CFI_GCC */
diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
index ccdf0c897f31..ed8db513b918 100644
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -119,7 +119,9 @@ static bool is_ignored_symbol(const char *name, char type)
 		"__ThumbV7PILongThunk_",
 		"__LA25Thunk_",		/* mips lld */
 		"__microLA25Thunk_",
-		"__kcfi_typeid_",	/* CFI type identifiers */
+		"__kcfi_typeid_",	/* CFI type identifiers in Clang */
+		"__cfi_",		/* CFI type identifiers in GCC */
+		"__pi___cfi",		/* CFI type identifiers in GCC */
 		NULL
 	};
 
