1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (C) 2022 - Google LLC
4 * Author: Ard Biesheuvel <ardb@google.com>
7 #include <linux/errno.h>
8 #include <linux/init.h>
9 #include <linux/linkage.h>
10 #include <linux/types.h>
16 bool dynamic_scs_is_enabled;
19 // This minimal DWARF CFI parser is partially based on the code in
20 // arch/arc/kernel/unwind.c, and on the document below:
21 // https://refspecs.linuxbase.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
24 #define DW_CFA_nop 0x00
25 #define DW_CFA_set_loc 0x01
26 #define DW_CFA_advance_loc1 0x02
27 #define DW_CFA_advance_loc2 0x03
28 #define DW_CFA_advance_loc4 0x04
29 #define DW_CFA_offset_extended 0x05
30 #define DW_CFA_restore_extended 0x06
31 #define DW_CFA_undefined 0x07
32 #define DW_CFA_same_value 0x08
33 #define DW_CFA_register 0x09
34 #define DW_CFA_remember_state 0x0a
35 #define DW_CFA_restore_state 0x0b
36 #define DW_CFA_def_cfa 0x0c
37 #define DW_CFA_def_cfa_register 0x0d
38 #define DW_CFA_def_cfa_offset 0x0e
39 #define DW_CFA_def_cfa_expression 0x0f
40 #define DW_CFA_expression 0x10
41 #define DW_CFA_offset_extended_sf 0x11
42 #define DW_CFA_def_cfa_sf 0x12
43 #define DW_CFA_def_cfa_offset_sf 0x13
44 #define DW_CFA_val_offset 0x14
45 #define DW_CFA_val_offset_sf 0x15
46 #define DW_CFA_val_expression 0x16
47 #define DW_CFA_lo_user 0x1c
48 #define DW_CFA_negate_ra_state 0x2d
49 #define DW_CFA_GNU_args_size 0x2e
50 #define DW_CFA_GNU_negative_offset_extended 0x2f
51 #define DW_CFA_hi_user 0x3f
56 SCS_PUSH = 0xf800865e,
60 static void __always_inline scs_patch_loc(u64 loc)
62 u32 insn = le32_to_cpup((void *)loc);
66 *(u32 *)loc = cpu_to_le32(SCS_PUSH);
69 *(u32 *)loc = cpu_to_le32(SCS_POP);
73 * While the DW_CFA_negate_ra_state directive is guaranteed to
74 * appear right after a PACIASP/AUTIASP instruction, it may
75 * also appear after a DW_CFA_restore_state directive that
76 * restores a state that is only partially accurate, and is
77 * followed by DW_CFA_negate_ra_state directive to toggle the
78 * PAC bit again. So we permit other instructions here, and ignore
83 if (IS_ENABLED(CONFIG_ARM64_WORKAROUND_CLEAN_CACHE))
84 asm("dc civac, %0" :: "r"(loc));
86 asm(ALTERNATIVE("dc cvau, %0", "nop", ARM64_HAS_CACHE_IDC)
91 * Skip one uleb128/sleb128 encoded quantity from the opcode stream. All bytes
92 * except the last one have bit #7 set.
94 static int __always_inline skip_xleb128(const u8 **opcode, int size)
101 } while (c & BIT(7));
108 * The size of this frame if 0 < size < U32_MAX, 0 terminates the list.
113 * The first frame is a Common Information Entry (CIE) frame, followed
114 * by one or more Frame Description Entry (FDE) frames. In the former
115 * case, this field is 0, otherwise it is the negated offset relative
116 * to the associated CIE frame.
118 u32 cie_id_or_pointer;
123 u8 augmentation_string[];
134 static int scs_handle_fde_frame(const struct eh_frame *frame,
135 bool fde_has_augmentation_data,
136 int code_alignment_factor,
139 int size = frame->size - offsetof(struct eh_frame, opcodes) + 4;
140 u64 loc = (u64)offset_to_ptr(&frame->initial_loc);
141 const u8 *opcode = frame->opcodes;
143 if (fde_has_augmentation_data) {
146 // assume single byte uleb128_t
147 if (WARN_ON(*opcode & BIT(7)))
156 * Starting from 'loc', apply the CFA opcodes that advance the location
157 * pointer, and identify the locations of the PAC instructions.
162 case DW_CFA_remember_state:
163 case DW_CFA_restore_state:
166 case DW_CFA_advance_loc1:
167 loc += *opcode++ * code_alignment_factor;
171 case DW_CFA_advance_loc2:
172 loc += *opcode++ * code_alignment_factor;
173 loc += (*opcode++ << 8) * code_alignment_factor;
178 case DW_CFA_offset_extended:
179 size = skip_xleb128(&opcode, size);
181 case DW_CFA_def_cfa_offset:
182 case DW_CFA_def_cfa_offset_sf:
183 case DW_CFA_def_cfa_register:
184 case DW_CFA_same_value:
185 case DW_CFA_restore_extended:
187 size = skip_xleb128(&opcode, size);
190 case DW_CFA_negate_ra_state:
192 scs_patch_loc(loc - 4);
197 loc += (opcode[-1] & 0x3f) * code_alignment_factor;
210 int scs_patch(const u8 eh_frame[], int size)
212 const u8 *p = eh_frame;
215 const struct eh_frame *frame = (const void *)p;
216 bool fde_has_augmentation_data = true;
217 int code_alignment_factor = 1;
220 if (frame->size == 0 ||
221 frame->size == U32_MAX ||
225 if (frame->cie_id_or_pointer == 0) {
226 const u8 *p = frame->augmentation_string;
228 /* a 'z' in the augmentation string must come first */
229 fde_has_augmentation_data = *p == 'z';
232 * The code alignment factor is a uleb128 encoded field
233 * but given that the only sensible values are 1 or 4,
234 * there is no point in decoding the whole thing.
237 if (!WARN_ON(*p & BIT(7)))
238 code_alignment_factor = *p;
240 ret = scs_handle_fde_frame(frame,
241 fde_has_augmentation_data,
242 code_alignment_factor,
246 scs_handle_fde_frame(frame, fde_has_augmentation_data,
247 code_alignment_factor, false);
250 p += sizeof(frame->size) + frame->size;
251 size -= sizeof(frame->size) + frame->size;