public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Peter Zijlstra <peterz@infradead.org>
To: x86@kernel.org
Cc: linux-kernel@vger.kernel.org, peterz@infradead.org,
	alyssa.milburn@intel.com, scott.d.constable@intel.com,
	joao@overdrivepizza.com, andrew.cooper3@citrix.com,
	jpoimboe@kernel.org, jose.marchesi@oracle.com,
	hjl.tools@gmail.com, ndesaulniers@google.com,
	samitolvanen@google.com, nathan@kernel.org, ojeda@kernel.org,
	kees@kernel.org, alexei.starovoitov@gmail.com,
	mhiramat@kernel.org, jmill@asu.edu
Subject: [PATCH v4 07/10] x86/ibt: Add paranoid FineIBT mode
Date: Mon, 24 Feb 2025 13:37:10 +0100	[thread overview]
Message-ID: <20250224124200.598033084@infradead.org> (raw)
In-Reply-To: 20250224123703.843199044@infradead.org

Due to concerns about circumvention attacks against FineIBT on 'naked'
ENDBR, add an additional caller side hash check to FineIBT. This
should make it impossible to pivot over such a 'naked' ENDBR
instruction at the cost of an additional load.

The specific pivot reported was against the SYSCALL entry site and
FRED will have all those holes fixed up.

  https://lore.kernel.org/linux-hardening/Z60NwR4w%2F28Z7XUa@ubun/

This specific fineibt_paranoid_start[] sequence was concocted by
Scott.

Reported-by: Jennifer Miller <jmill@asu.edu>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
---
 arch/x86/kernel/alternative.c |  144 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 138 insertions(+), 6 deletions(-)

--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -741,6 +741,11 @@ void __init_or_module noinline apply_ret
 		op2 = insn.opcode.bytes[1];
 
 		switch (op1) {
+		case 0x70 ... 0x7f:	/* Jcc.d8 */
+			/* See cfi_paranoid. */
+			WARN_ON_ONCE(cfi_mode != CFI_FINEIBT);
+			continue;
+
 		case CALL_INSN_OPCODE:
 		case JMP32_INSN_OPCODE:
 			break;
@@ -994,6 +999,8 @@ u32 cfi_get_func_hash(void *func)
 static bool cfi_rand __ro_after_init = true;
 static u32  cfi_seed __ro_after_init;
 
+static bool cfi_paranoid __ro_after_init = false;
+
 /*
  * Re-hash the CFI hash with a boot-time seed while making sure the result is
  * not a valid ENDBR instruction.
@@ -1036,6 +1043,12 @@ static __init int cfi_parse_cmdline(char
 		} else if (!strcmp(str, "warn")) {
 			pr_alert("CFI mismatch non-fatal!\n");
 			cfi_warn = true;
+		} else if (!strcmp(str, "paranoid")) {
+			if (cfi_mode == CFI_FINEIBT) {
+				cfi_paranoid = true;
+			} else {
+				pr_err("Ignoring paranoid; depends on fineibt.\n");
+			}
 		} else {
 			pr_err("Ignoring unknown cfi option (%s).", str);
 		}
@@ -1124,6 +1137,52 @@ extern u8 fineibt_caller_end[];
 
 #define fineibt_caller_jmp (fineibt_caller_size - 2)
 
+/*
+ * Since FineIBT does hash validation on the callee side it is prone to
+ * circumvention attacks where a 'naked' ENDBR instruction exists that
+ * is not part of the fineibt_preamble sequence.
+ *
+ * Notably the x86 entry points must be ENDBR and equally cannot be
+ * fineibt_preamble.
+ *
+ * The fineibt_paranoid caller sequence adds additional caller side
+ * hash validation. This stops such circumvention attacks dead, but at the cost
+ * of adding a load.
+ *
+ * <fineibt_paranoid_start>:
+ *  0:   41 ba 78 56 34 12       mov    $0x12345678, %r10d
+ *  6:   45 3b 53 f7             cmp    -0x9(%r11), %r10d
+ *  a:   4d 8d 5b <f0>           lea    -0x10(%r11), %r11
+ *  e:   75 fd                   jne    d <fineibt_paranoid_start+0xd>
+ * 10:   41 ff d3                call   *%r11
+ * 13:   90                      nop
+ *
+ * Notably LEA does not modify flags and can be reordered with the CMP,
+ * avoiding a dependency. Again, using a non-taken (backwards) branch
+ * for the failure case, abusing LEA's immediate 0xf0 as LOCK prefix for the
+ * Jcc.d8, causing #UD.
+ */
+asm(	".pushsection .rodata				\n"
+	"fineibt_paranoid_start:			\n"
+	"	movl	$0x12345678, %r10d		\n"
+	"	cmpl	-9(%r11), %r10d			\n"
+	"	lea	-0x10(%r11), %r11		\n"
+	"	jne	fineibt_paranoid_start+0xd	\n"
+	"fineibt_paranoid_ind:				\n"
+	"	call	*%r11				\n"
+	"	nop					\n"
+	"fineibt_paranoid_end:				\n"
+	".popsection					\n"
+);
+
+extern u8 fineibt_paranoid_start[];
+extern u8 fineibt_paranoid_ind[];
+extern u8 fineibt_paranoid_end[];
+
+#define fineibt_paranoid_size (fineibt_paranoid_end - fineibt_paranoid_start)
+#define fineibt_paranoid_ind  (fineibt_paranoid_ind - fineibt_paranoid_start)
+#define fineibt_paranoid_ud   0xd
+
 static u32 decode_preamble_hash(void *addr)
 {
 	u8 *p = addr;
@@ -1287,18 +1346,48 @@ static int cfi_rewrite_callers(s32 *star
 {
 	s32 *s;
 
+	BUG_ON(fineibt_paranoid_size != 20);
+
 	for (s = start; s < end; s++) {
 		void *addr = (void *)s + *s;
+		struct insn insn;
+		u8 bytes[20];
 		u32 hash;
+		int ret;
+		u8 op;
 
 		addr -= fineibt_caller_size;
 		hash = decode_caller_hash(addr);
-		if (hash) {
+		if (!hash)
+			continue;
+
+		if (!cfi_paranoid) {
 			text_poke_early(addr, fineibt_caller_start, fineibt_caller_size);
 			WARN_ON(*(u32 *)(addr + fineibt_caller_hash) != 0x12345678);
 			text_poke_early(addr + fineibt_caller_hash, &hash, 4);
+			/* rely on apply_retpolines() */
+			continue;
+		}
+
+		/* cfi_paranoid */
+		ret = insn_decode_kernel(&insn, addr + fineibt_caller_size);
+		if (WARN_ON_ONCE(ret < 0))
+			continue;
+
+		op = insn.opcode.bytes[0];
+		if (op != CALL_INSN_OPCODE && op != JMP32_INSN_OPCODE) {
+			WARN_ON_ONCE(1);
+			continue;
 		}
-		/* rely on apply_retpolines() */
+
+		memcpy(bytes, fineibt_paranoid_start, fineibt_paranoid_size);
+		memcpy(bytes + fineibt_caller_hash, &hash, 4);
+
+		ret = emit_indirect(op, 11, bytes + fineibt_paranoid_ind);
+		if (WARN_ON_ONCE(ret != 3))
+			continue;
+
+		text_poke_early(addr, bytes, fineibt_paranoid_size);
 	}
 
 	return 0;
@@ -1315,8 +1404,15 @@ static void __apply_fineibt(s32 *start_r
 
 	if (cfi_mode == CFI_AUTO) {
 		cfi_mode = CFI_KCFI;
-		if (HAS_KERNEL_IBT && cpu_feature_enabled(X86_FEATURE_IBT))
+		if (HAS_KERNEL_IBT && cpu_feature_enabled(X86_FEATURE_IBT)) {
+			/*
+			 * FRED has much saner context on exception entry and
+			 * is less easy to take advantage of.
+			 */
+			if (!cpu_feature_enabled(X86_FEATURE_FRED))
+				cfi_paranoid = true;
 			cfi_mode = CFI_FINEIBT;
+		}
 	}
 
 	/*
@@ -1373,8 +1469,10 @@ static void __apply_fineibt(s32 *start_r
 		/* now that nobody targets func()+0, remove ENDBR there */
 		cfi_rewrite_endbr(start_cfi, end_cfi);
 
-		if (builtin)
-			pr_info("Using FineIBT CFI\n");
+		if (builtin) {
+			pr_info("Using %sFineIBT CFI\n",
+				cfi_paranoid ? "paranoid " : "");
+		}
 		return;
 
 	default:
@@ -1447,7 +1545,7 @@ static void poison_cfi(void *addr)
  * We check the preamble by checking for the ENDBR instruction relative to the
  * 0xEA instruction.
  */
-bool decode_fineibt_insn(struct pt_regs *regs, unsigned long *target, u32 *type)
+static bool decode_fineibt_preamble(struct pt_regs *regs, unsigned long *target, u32 *type)
 {
 	unsigned long addr = regs->ip - fineibt_preamble_ud;
 	u32 hash;
@@ -1472,6 +1570,40 @@ bool decode_fineibt_insn(struct pt_regs
 	return false;
 }
 
+/*
+ * regs->ip points to a LOCK Jcc.d8 instruction from the fineibt_paranoid_start[]
+ * sequence.
+ */
+static bool decode_fineibt_paranoid(struct pt_regs *regs, unsigned long *target, u32 *type)
+{
+	unsigned long addr = regs->ip - fineibt_paranoid_ud;
+	u32 hash;
+
+	if (!cfi_paranoid || !is_cfi_trap(addr + fineibt_caller_size - LEN_UD2))
+		return false;
+
+	__get_kernel_nofault(&hash, addr + fineibt_caller_hash, u32, Efault);
+	*target = regs->r11 + fineibt_preamble_size;
+	*type = regs->r10;
+
+	/*
+	 * Since the trapping instruction is the exact, but LOCK prefixed,
+	 * Jcc.d8 that got us here, the normal fixup will work.
+	 */
+	return true;
+
+Efault:
+	return false;
+}
+
+bool decode_fineibt_insn(struct pt_regs *regs, unsigned long *target, u32 *type)
+{
+	if (decode_fineibt_paranoid(regs, target, type))
+		return true;
+
+	return decode_fineibt_preamble(regs, target, type);
+}
+
 #else
 
 static void __apply_fineibt(s32 *start_retpoline, s32 *end_retpoline,



  parent reply	other threads:[~2025-02-24 12:46 UTC|newest]

Thread overview: 56+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-24 12:37 [PATCH v4 00/10] x86/ibt: FineIBT-BHI Peter Zijlstra
2025-02-24 12:37 ` [PATCH v4 01/10] x86/cfi: Add warn option Peter Zijlstra
2025-02-24 18:57   ` Kees Cook
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` [tip: x86/core] x86/cfi: Add 'cfi=warn' boot option tip-bot2 for Peter Zijlstra
2025-02-24 12:37 ` [PATCH v4 02/10] x86/ibt: Add exact_endbr() helper Peter Zijlstra
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` tip-bot2 for Peter Zijlstra
2025-02-24 12:37 ` [PATCH v4 03/10] x86/traps: Decode 0xEA #UD Peter Zijlstra
2025-02-24 18:58   ` Kees Cook
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` [tip: x86/core] x86/traps: Decode 0xEA instructions as #UD tip-bot2 for Peter Zijlstra
2025-02-24 12:37 ` [PATCH v4 04/10] x86/traps: Allow custom fixups in handle_bug() Peter Zijlstra
2025-02-24 18:59   ` Kees Cook
2025-02-25  8:54     ` Peter Zijlstra
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` tip-bot2 for Peter Zijlstra
2025-02-24 12:37 ` [PATCH v4 05/10] x86/ibt: Optimize FineIBT sequence Peter Zijlstra
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` [tip: x86/core] x86/ibt: Optimize the FineIBT instruction sequence tip-bot2 for Peter Zijlstra
2025-02-24 12:37 ` [PATCH v4 06/10] x86/traps: Decode LOCK Jcc.d8 #UD Peter Zijlstra
2025-02-24 21:46   ` David Laight
2025-02-25 18:33     ` Kees Cook
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` [tip: x86/core] x86/traps: Decode LOCK Jcc.d8 as #UD tip-bot2 for Peter Zijlstra
2025-02-24 12:37 ` Peter Zijlstra [this message]
2025-02-24 19:00   ` [PATCH v4 07/10] x86/ibt: Add paranoid FineIBT mode Kees Cook
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` tip-bot2 for Peter Zijlstra
2025-02-24 12:37 ` [PATCH v4 08/10] x86: BHI stubs Peter Zijlstra
2025-02-24 19:01   ` Kees Cook
2025-02-25  8:52     ` Peter Zijlstra
2025-02-25 18:31       ` Kees Cook
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` [tip: x86/core] x86/bhi: Add " tip-bot2 for Peter Zijlstra
2025-02-26 12:54   ` tip-bot2 for Peter Zijlstra
2025-02-24 12:37 ` [PATCH v4 09/10] x86/ibt: Implement FineIBT-BHI mitigation Peter Zijlstra
2025-02-25  9:12   ` Peter Zijlstra
2025-02-26  0:04     ` Constable, Scott D
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` tip-bot2 for Peter Zijlstra
2025-02-26 12:54   ` tip-bot2 for Peter Zijlstra
2025-02-26 19:53     ` Peter Zijlstra
2025-03-10  8:55       ` Peter Zijlstra
2025-03-10 16:00       ` Miguel Ojeda
2025-03-10 16:02         ` Peter Zijlstra
2025-03-11 19:09           ` Ramon de C Valle
2025-03-11 19:41             ` Miguel Ojeda
2025-03-11 20:23               ` Ramon de C Valle
2025-03-12  9:16                 ` Peter Zijlstra
2025-03-12 11:36                   ` Miguel Ojeda
2025-03-19  0:04                     ` Nathan Chancellor
2025-02-24 12:37 ` [PATCH v4 10/10] x86/ibt: Optimize fineibt-bhi arity 1 case Peter Zijlstra
2025-02-26 10:54   ` [tip: x86/core] " tip-bot2 for Peter Zijlstra
2025-02-26 12:04   ` [tip: x86/core] x86/ibt: Optimize the " tip-bot2 for Peter Zijlstra
2025-02-26 12:54   ` tip-bot2 for Peter Zijlstra

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=20250224124200.598033084@infradead.org \
    --to=peterz@infradead.org \
    --cc=alexei.starovoitov@gmail.com \
    --cc=alyssa.milburn@intel.com \
    --cc=andrew.cooper3@citrix.com \
    --cc=hjl.tools@gmail.com \
    --cc=jmill@asu.edu \
    --cc=joao@overdrivepizza.com \
    --cc=jose.marchesi@oracle.com \
    --cc=jpoimboe@kernel.org \
    --cc=kees@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mhiramat@kernel.org \
    --cc=nathan@kernel.org \
    --cc=ndesaulniers@google.com \
    --cc=ojeda@kernel.org \
    --cc=samitolvanen@google.com \
    --cc=scott.d.constable@intel.com \
    --cc=x86@kernel.org \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox