patch-scs.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Copyright (C) 2022 - Google LLC
  4. * Author: Ard Biesheuvel <[email protected]>
  5. */
  6. #include <linux/bug.h>
  7. #include <linux/errno.h>
  8. #include <linux/init.h>
  9. #include <linux/linkage.h>
  10. #include <linux/printk.h>
  11. #include <linux/types.h>
  12. #include <asm/cacheflush.h>
  13. #include <asm/scs.h>
  14. //
  15. // This minimal DWARF CFI parser is partially based on the code in
  16. // arch/arc/kernel/unwind.c, and on the document below:
  17. // https://refspecs.linuxbase.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
  18. //
  19. #define DW_CFA_nop 0x00
  20. #define DW_CFA_set_loc 0x01
  21. #define DW_CFA_advance_loc1 0x02
  22. #define DW_CFA_advance_loc2 0x03
  23. #define DW_CFA_advance_loc4 0x04
  24. #define DW_CFA_offset_extended 0x05
  25. #define DW_CFA_restore_extended 0x06
  26. #define DW_CFA_undefined 0x07
  27. #define DW_CFA_same_value 0x08
  28. #define DW_CFA_register 0x09
  29. #define DW_CFA_remember_state 0x0a
  30. #define DW_CFA_restore_state 0x0b
  31. #define DW_CFA_def_cfa 0x0c
  32. #define DW_CFA_def_cfa_register 0x0d
  33. #define DW_CFA_def_cfa_offset 0x0e
  34. #define DW_CFA_def_cfa_expression 0x0f
  35. #define DW_CFA_expression 0x10
  36. #define DW_CFA_offset_extended_sf 0x11
  37. #define DW_CFA_def_cfa_sf 0x12
  38. #define DW_CFA_def_cfa_offset_sf 0x13
  39. #define DW_CFA_val_offset 0x14
  40. #define DW_CFA_val_offset_sf 0x15
  41. #define DW_CFA_val_expression 0x16
  42. #define DW_CFA_lo_user 0x1c
  43. #define DW_CFA_negate_ra_state 0x2d
  44. #define DW_CFA_GNU_args_size 0x2e
  45. #define DW_CFA_GNU_negative_offset_extended 0x2f
  46. #define DW_CFA_hi_user 0x3f
  47. extern const u8 __eh_frame_start[], __eh_frame_end[];
  48. enum {
  49. PACIASP = 0xd503233f,
  50. AUTIASP = 0xd50323bf,
  51. SCS_PUSH = 0xf800865e,
  52. SCS_POP = 0xf85f8e5e,
  53. };
  54. static void __always_inline scs_patch_loc(u64 loc)
  55. {
  56. u32 insn = le32_to_cpup((void *)loc);
  57. switch (insn) {
  58. case PACIASP:
  59. *(u32 *)loc = cpu_to_le32(SCS_PUSH);
  60. break;
  61. case AUTIASP:
  62. *(u32 *)loc = cpu_to_le32(SCS_POP);
  63. break;
  64. default:
  65. /*
  66. * While the DW_CFA_negate_ra_state directive is guaranteed to
  67. * appear right after a PACIASP/AUTIASP instruction, it may
  68. * also appear after a DW_CFA_restore_state directive that
  69. * restores a state that is only partially accurate, and is
  70. * followed by DW_CFA_negate_ra_state directive to toggle the
  71. * PAC bit again. So we permit other instructions here, and ignore
  72. * them.
  73. */
  74. return;
  75. }
  76. dcache_clean_pou(loc, loc + sizeof(u32));
  77. }
  78. /*
  79. * Skip one uleb128/sleb128 encoded quantity from the opcode stream. All bytes
  80. * except the last one have bit #7 set.
  81. */
  82. static int __always_inline skip_xleb128(const u8 **opcode, int size)
  83. {
  84. u8 c;
  85. do {
  86. c = *(*opcode)++;
  87. size--;
  88. } while (c & BIT(7));
  89. return size;
  90. }
  91. struct eh_frame {
  92. /*
  93. * The size of this frame if 0 < size < U32_MAX, 0 terminates the list.
  94. */
  95. u32 size;
  96. /*
  97. * The first frame is a Common Information Entry (CIE) frame, followed
  98. * by one or more Frame Description Entry (FDE) frames. In the former
  99. * case, this field is 0, otherwise it is the negated offset relative
  100. * to the associated CIE frame.
  101. */
  102. u32 cie_id_or_pointer;
  103. union {
  104. struct { // CIE
  105. u8 version;
  106. u8 augmentation_string[];
  107. };
  108. struct { // FDE
  109. s32 initial_loc;
  110. s32 range;
  111. u8 opcodes[];
  112. };
  113. };
  114. };
  115. static int noinstr scs_handle_fde_frame(const struct eh_frame *frame,
  116. bool fde_has_augmentation_data,
  117. int code_alignment_factor)
  118. {
  119. int size = frame->size - offsetof(struct eh_frame, opcodes) + 4;
  120. u64 loc = (u64)offset_to_ptr(&frame->initial_loc);
  121. const u8 *opcode = frame->opcodes;
  122. if (fde_has_augmentation_data) {
  123. int l;
  124. // assume single byte uleb128_t
  125. if (WARN_ON(*opcode & BIT(7)))
  126. return -ENOEXEC;
  127. l = *opcode++;
  128. opcode += l;
  129. size -= l + 1;
  130. }
  131. /*
  132. * Starting from 'loc', apply the CFA opcodes that advance the location
  133. * pointer, and identify the locations of the PAC instructions.
  134. */
  135. while (size-- > 0) {
  136. switch (*opcode++) {
  137. case DW_CFA_nop:
  138. case DW_CFA_remember_state:
  139. case DW_CFA_restore_state:
  140. break;
  141. case DW_CFA_advance_loc1:
  142. loc += *opcode++ * code_alignment_factor;
  143. size--;
  144. break;
  145. case DW_CFA_advance_loc2:
  146. loc += *opcode++ * code_alignment_factor;
  147. loc += (*opcode++ << 8) * code_alignment_factor;
  148. size -= 2;
  149. break;
  150. case DW_CFA_def_cfa:
  151. case DW_CFA_offset_extended:
  152. size = skip_xleb128(&opcode, size);
  153. fallthrough;
  154. case DW_CFA_def_cfa_offset:
  155. case DW_CFA_def_cfa_offset_sf:
  156. case DW_CFA_def_cfa_register:
  157. case DW_CFA_same_value:
  158. case DW_CFA_restore_extended:
  159. case 0x80 ... 0xbf:
  160. size = skip_xleb128(&opcode, size);
  161. break;
  162. case DW_CFA_negate_ra_state:
  163. scs_patch_loc(loc - 4);
  164. break;
  165. case 0x40 ... 0x7f:
  166. // advance loc
  167. loc += (opcode[-1] & 0x3f) * code_alignment_factor;
  168. break;
  169. case 0xc0 ... 0xff:
  170. break;
  171. default:
  172. pr_err("unhandled opcode: %02x in FDE frame %lx\n", opcode[-1], (uintptr_t)frame);
  173. return -ENOEXEC;
  174. }
  175. }
  176. return 0;
  177. }
  178. int noinstr scs_patch(const u8 eh_frame[], int size)
  179. {
  180. const u8 *p = eh_frame;
  181. while (size > 4) {
  182. const struct eh_frame *frame = (const void *)p;
  183. bool fde_has_augmentation_data = true;
  184. int code_alignment_factor = 1;
  185. int ret;
  186. if (frame->size == 0 ||
  187. frame->size == U32_MAX ||
  188. frame->size > size)
  189. break;
  190. if (frame->cie_id_or_pointer == 0) {
  191. const u8 *p = frame->augmentation_string;
  192. /* a 'z' in the augmentation string must come first */
  193. fde_has_augmentation_data = *p == 'z';
  194. /*
  195. * The code alignment factor is a uleb128 encoded field
  196. * but given that the only sensible values are 1 or 4,
  197. * there is no point in decoding the whole thing.
  198. */
  199. p += strlen(p) + 1;
  200. if (!WARN_ON(*p & BIT(7)))
  201. code_alignment_factor = *p;
  202. } else {
  203. ret = scs_handle_fde_frame(frame,
  204. fde_has_augmentation_data,
  205. code_alignment_factor);
  206. if (ret)
  207. return ret;
  208. }
  209. p += sizeof(frame->size) + frame->size;
  210. size -= sizeof(frame->size) + frame->size;
  211. }
  212. return 0;
  213. }
  214. asmlinkage void __init scs_patch_vmlinux(void)
  215. {
  216. if (!should_patch_pac_into_scs())
  217. return;
  218. WARN_ON(scs_patch(__eh_frame_start, __eh_frame_end - __eh_frame_start));
  219. icache_inval_all_pou();
  220. isb();
  221. }