All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ard Biesheuvel <ardb@kernel.org>
To: linux-arm-kernel@lists.infradead.org
Cc: Ard Biesheuvel <ardb@kernel.org>, Marc Zyngier <maz@kernel.org>,
	Will Deacon <will@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	Kees Cook <keescook@chromium.org>,
	Catalin Marinas <catalin.marinas@arm.com>,
	Mark Brown <broonie@kernel.org>
Subject: [PATCH v3 2/4] arm64: assembler: Protect return addresses in asm routines
Date: Fri,  9 Dec 2022 16:20:46 +0100	[thread overview]
Message-ID: <20221209152048.3517080-3-ardb@kernel.org> (raw)
In-Reply-To: <20221209152048.3517080-1-ardb@kernel.org>

Introduce a set of macros that can be invoked to protect and restore the
return address when it is being spilled to memory. Just like ordinary C
code, the chosen method will be based on CONFIG_ARM64_PTR_AUTH_KERNEL,
CONFIG_SHADOW_CALL_STACK and CONFIG_DYNAMIC_SCS, and may involve boot
time patching depending on the runtime capabilities of the system (and
potential command line overrides)

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 arch/arm64/include/asm/assembler.h | 75 ++++++++++++++++++++
 arch/arm64/kernel/patch-scs.c      | 70 +++++++++++++-----
 2 files changed, 127 insertions(+), 18 deletions(-)

diff --git a/arch/arm64/include/asm/assembler.h b/arch/arm64/include/asm/assembler.h
index 1c04701e4fda8458..8b4afa2aaa9b0600 100644
--- a/arch/arm64/include/asm/assembler.h
+++ b/arch/arm64/include/asm/assembler.h
@@ -698,6 +698,79 @@ alternative_endif
 #endif
 	.endm
 
+	/*
+	 * protect_return_address - protect the return address value in
+	 * register @reg, either by signing it using PAC and/or storing it on
+	 * the shadow call stack. When dynamic shadow call stack is enabled,
+	 * unwind directives are emitted so that the patching logic can find
+	 * the instructions.
+	 *
+	 * These macros must not be used with reg != x30 in functions marked as
+	 * SYM_FUNC, as in that case, each occurrence of this macro needs its
+	 * own SYM_FUNC_CFI_START/_END section, and so these have to be emitted
+	 * explicitly rather than via SYM_FUNC_START/_END. This is required to
+	 * encode the return address correctly (which can only be encoded once
+	 * per function)
+	 */
+	.macro		protect_return_address, reg=x30
+#ifdef CONFIG_ARM64_PTR_AUTH_KERNEL
+#ifdef CONFIG_UNWIND_TABLES
+	.cfi_startproc
+#endif
+	.arch_extension	pauth
+	.ifnc		\reg, x30
+alternative_if_not ARM64_HAS_ADDRESS_AUTH
+	// NOP encoding with bit #22 cleared (for patching to STR)
+	orr		xzr, xzr, xzr, lsl #0
+alternative_else
+	pacia		\reg, sp
+alternative_endif
+	.else
+	paciasp
+	.endif
+#ifdef CONFIG_UNWIND_TABLES
+	.cfi_return_column	\reg
+	.cfi_negate_ra_state
+	.cfi_endproc
+#endif
+#endif
+#if defined(CONFIG_SHADOW_CALL_STACK) && !defined(CONFIG_DYNAMIC_SCS)
+	str		\reg, [x18], #8
+#endif
+	.endm
+
+	/*
+	 * restore_return_address - restore the return address value in
+	 * register @reg, either by authenticating it using PAC and/or
+	 * reloading it from the shadow call stack.
+	 */
+	.macro		restore_return_address, reg=x30
+#if defined(CONFIG_SHADOW_CALL_STACK) && !defined(CONFIG_DYNAMIC_SCS)
+	ldr		\reg, [x18, #-8]!
+#endif
+#ifdef CONFIG_ARM64_PTR_AUTH_KERNEL
+#ifdef CONFIG_UNWIND_TABLES
+	.cfi_startproc
+#endif
+	.arch_extension	pauth
+	.ifnc		\reg, x30
+alternative_if_not ARM64_HAS_ADDRESS_AUTH
+	// NOP encoding with bit #22 set (for patching to LDR)
+	orr		xzr, xzr, xzr, lsr #0
+alternative_else
+	autia		\reg, sp
+alternative_endif
+	.else
+	autiasp
+	.endif
+#ifdef CONFIG_UNWIND_TABLES
+	.cfi_return_column	\reg
+	.cfi_negate_ra_state
+	.cfi_endproc
+#endif
+#endif
+	.endm
+
 	/*
 	 * frame_push - Push @regcount callee saved registers to the stack,
 	 *              starting at x19, as well as x29/x30, and set x29 to
@@ -705,6 +778,7 @@ alternative_endif
 	 *              for locals.
 	 */
 	.macro		frame_push, regcount:req, extra
+	protect_return_address
 	__frame		st, \regcount, \extra
 	.endm
 
@@ -716,6 +790,7 @@ alternative_endif
 	 */
 	.macro		frame_pop
 	__frame		ld
+	restore_return_address
 	.endm
 
 	.macro		__frame_regs, reg1, reg2, op, num
diff --git a/arch/arm64/kernel/patch-scs.c b/arch/arm64/kernel/patch-scs.c
index 1b3da02d5b741bc3..d7319d10ca799167 100644
--- a/arch/arm64/kernel/patch-scs.c
+++ b/arch/arm64/kernel/patch-scs.c
@@ -54,20 +54,33 @@ extern const u8 __eh_frame_start[], __eh_frame_end[];
 enum {
 	PACIASP		= 0xd503233f,
 	AUTIASP		= 0xd50323bf,
-	SCS_PUSH	= 0xf800865e,
-	SCS_POP		= 0xf85f8e5e,
+	SCS_PUSH	= 0xf8008640,
+	SCS_POP		= 0xf85f8e40,
+
+	// Special NOP encodings to identify locations where a register other
+	// than x30 is being used to carry the return address
+	NOP_PUSH	= 0xaa1f03ff,	// orr xzr, xzr, xzr, lsl #0
+	NOP_POP		= 0xaa5f03ff,	// orr xzr, xzr, xzr, lsr #0
 };
 
-static void __always_inline scs_patch_loc(u64 loc)
+static void __always_inline scs_patch_loc(u64 loc, int ra_reg)
 {
 	u32 insn = le32_to_cpup((void *)loc);
 
 	switch (insn) {
+	case NOP_PUSH:
+		if (WARN_ON(ra_reg == 30))
+			break;
+		fallthrough;
 	case PACIASP:
-		*(u32 *)loc = cpu_to_le32(SCS_PUSH);
+		*(u32 *)loc = cpu_to_le32(SCS_PUSH | ra_reg);
 		break;
+	case NOP_POP:
+		if (WARN_ON(ra_reg == 30))
+			break;
+		fallthrough;
 	case AUTIASP:
-		*(u32 *)loc = cpu_to_le32(SCS_POP);
+		*(u32 *)loc = cpu_to_le32(SCS_POP | ra_reg);
 		break;
 	default:
 		/*
@@ -76,9 +89,12 @@ static void __always_inline scs_patch_loc(u64 loc)
 		 * also appear after a DW_CFA_restore_state directive that
 		 * restores a state that is only partially accurate, and is
 		 * followed by DW_CFA_negate_ra_state directive to toggle the
-		 * PAC bit again. So we permit other instructions here, and ignore
-		 * them.
+		 * PAC bit again. So we permit other instructions here, and
+		 * ignore them (unless they appear in handwritten assembly
+		 * using a different return address register, where this should
+		 * never happen).
 		 */
+		WARN_ON(ra_reg != 30);
 		return;
 	}
 	dcache_clean_pou(loc, loc + sizeof(u32));
@@ -130,7 +146,8 @@ struct eh_frame {
 
 static int noinstr scs_handle_fde_frame(const struct eh_frame *frame,
 					bool fde_has_augmentation_data,
-					int code_alignment_factor)
+					int code_alignment_factor,
+					int ra_reg)
 {
 	int size = frame->size - offsetof(struct eh_frame, opcodes) + 4;
 	u64 loc = (u64)offset_to_ptr(&frame->initial_loc);
@@ -184,7 +201,7 @@ static int noinstr scs_handle_fde_frame(const struct eh_frame *frame,
 			break;
 
 		case DW_CFA_negate_ra_state:
-			scs_patch_loc(loc - 4);
+			scs_patch_loc(loc - 4, ra_reg);
 			break;
 
 		case 0x40 ... 0x7f:
@@ -206,6 +223,7 @@ static int noinstr scs_handle_fde_frame(const struct eh_frame *frame,
 int noinstr scs_patch(const u8 eh_frame[], int size)
 {
 	const u8 *p = eh_frame;
+	int ra_reg = 30;
 
 	while (size > 4) {
 		const struct eh_frame *frame = (const void *)p;
@@ -219,23 +237,39 @@ int noinstr scs_patch(const u8 eh_frame[], int size)
 			break;
 
 		if (frame->cie_id_or_pointer == 0) {
-			const u8 *p = frame->augmentation_string;
+			const u8 *as = frame->augmentation_string;
 
 			/* a 'z' in the augmentation string must come first */
-			fde_has_augmentation_data = *p == 'z';
+			fde_has_augmentation_data = *as == 'z';
+			as += strlen(as) + 1;
+
+			/* check for at least 3 more bytes in the frame */
+			if (as - (u8 *)&frame->cie_id_or_pointer + 3 > frame->size)
+				return -ENOEXEC;
 
 			/*
-			 * The code alignment factor is a uleb128 encoded field
-			 * but given that the only sensible values are 1 or 4,
-			 * there is no point in decoding the whole thing.
+			 * The code and data alignment factors are uleb128
+			 * encoded fields but given that the only sensible
+			 * values are 1 or 4, there is no point in decoding
+			 * them entirely. The return address register number is
+			 * a single byte in version 1 and a uleb128 in newer
+			 * versions.
 			 */
-			p += strlen(p) + 1;
-			if (!WARN_ON(*p & BIT(7)))
-				code_alignment_factor = *p;
+			if (WARN_ON(as[0] & BIT(7) || as[1] & BIT(7) ||
+				    (as[2] & BIT(7)) && frame->version > 1))
+				return -ENOEXEC;
+
+			code_alignment_factor = as[0];
+
+			// Grab the return address register
+			ra_reg = as[2];
+			if (WARN_ON(ra_reg > 30))
+				return -ENOEXEC;
 		} else {
 			ret = scs_handle_fde_frame(frame,
 						   fde_has_augmentation_data,
-						   code_alignment_factor);
+						   code_alignment_factor,
+						   ra_reg);
 			if (ret)
 				return ret;
 		}
-- 
2.35.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

  parent reply	other threads:[~2022-12-09 15:22 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-12-09 15:20 [PATCH v3 0/4] arm64: Add return address protection to asm code Ard Biesheuvel
2022-12-09 15:20 ` [PATCH v3 1/4] arm64: assembler: Force error on misuse of .Lframe_local_offset Ard Biesheuvel
2022-12-09 15:20 ` Ard Biesheuvel [this message]
2022-12-09 15:20 ` [PATCH v3 3/4] arm64: ftrace: Preserve original link register value in ftrace_regs Ard Biesheuvel
2022-12-09 15:20 ` [PATCH v3 4/4] arm64: ftrace: Add return address protection Ard Biesheuvel

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=20221209152048.3517080-3-ardb@kernel.org \
    --to=ardb@kernel.org \
    --cc=broonie@kernel.org \
    --cc=catalin.marinas@arm.com \
    --cc=keescook@chromium.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=mark.rutland@arm.com \
    --cc=maz@kernel.org \
    --cc=will@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 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.