@@ -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
@@ -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;
}
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(-)