nospec-branch.c 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. // SPDX-License-Identifier: GPL-2.0
  2. #include <linux/module.h>
  3. #include <asm/nospec-branch.h>
  4. int nospec_call_disable = IS_ENABLED(CONFIG_EXPOLINE_OFF);
  5. int nospec_return_disable = !IS_ENABLED(CONFIG_EXPOLINE_FULL);
  6. static int __init nospectre_v2_setup_early(char *str)
  7. {
  8. nospec_call_disable = 1;
  9. nospec_return_disable = 1;
  10. return 0;
  11. }
  12. early_param("nospectre_v2", nospectre_v2_setup_early);
  13. static int __init spectre_v2_setup_early(char *str)
  14. {
  15. if (str && !strncmp(str, "on", 2)) {
  16. nospec_call_disable = 0;
  17. nospec_return_disable = 0;
  18. }
  19. if (str && !strncmp(str, "off", 3)) {
  20. nospec_call_disable = 1;
  21. nospec_return_disable = 1;
  22. }
  23. if (str && !strncmp(str, "auto", 4)) {
  24. nospec_call_disable = 0;
  25. nospec_return_disable = 1;
  26. }
  27. return 0;
  28. }
  29. early_param("spectre_v2", spectre_v2_setup_early);
  30. static void __init_or_module __nospec_revert(s32 *start, s32 *end)
  31. {
  32. enum { BRCL_EXPOLINE, BRASL_EXPOLINE } type;
  33. u8 *instr, *thunk, *br;
  34. u8 insnbuf[6];
  35. s32 *epo;
  36. /* Second part of the instruction replace is always a nop */
  37. memcpy(insnbuf + 2, (char[]) { 0x47, 0x00, 0x00, 0x00 }, 4);
  38. for (epo = start; epo < end; epo++) {
  39. instr = (u8 *) epo + *epo;
  40. if (instr[0] == 0xc0 && (instr[1] & 0x0f) == 0x04)
  41. type = BRCL_EXPOLINE; /* brcl instruction */
  42. else if (instr[0] == 0xc0 && (instr[1] & 0x0f) == 0x05)
  43. type = BRASL_EXPOLINE; /* brasl instruction */
  44. else
  45. continue;
  46. thunk = instr + (*(int *)(instr + 2)) * 2;
  47. if (thunk[0] == 0xc6 && thunk[1] == 0x00)
  48. /* exrl %r0,<target-br> */
  49. br = thunk + (*(int *)(thunk + 2)) * 2;
  50. else if (thunk[0] == 0xc0 && (thunk[1] & 0x0f) == 0x00 &&
  51. thunk[6] == 0x44 && thunk[7] == 0x00 &&
  52. (thunk[8] & 0x0f) == 0x00 && thunk[9] == 0x00 &&
  53. (thunk[1] & 0xf0) == (thunk[8] & 0xf0))
  54. /* larl %rx,<target br> + ex %r0,0(%rx) */
  55. br = thunk + (*(int *)(thunk + 2)) * 2;
  56. else
  57. continue;
  58. if (br[0] != 0x07 || (br[1] & 0xf0) != 0xf0)
  59. continue;
  60. switch (type) {
  61. case BRCL_EXPOLINE:
  62. /* brcl to thunk, replace with br + nop */
  63. insnbuf[0] = br[0];
  64. insnbuf[1] = (instr[1] & 0xf0) | (br[1] & 0x0f);
  65. break;
  66. case BRASL_EXPOLINE:
  67. /* brasl to thunk, replace with basr + nop */
  68. insnbuf[0] = 0x0d;
  69. insnbuf[1] = (instr[1] & 0xf0) | (br[1] & 0x0f);
  70. break;
  71. }
  72. s390_kernel_write(instr, insnbuf, 6);
  73. }
  74. }
  75. void __init_or_module nospec_call_revert(s32 *start, s32 *end)
  76. {
  77. if (nospec_call_disable)
  78. __nospec_revert(start, end);
  79. }
  80. void __init_or_module nospec_return_revert(s32 *start, s32 *end)
  81. {
  82. if (nospec_return_disable)
  83. __nospec_revert(start, end);
  84. }
  85. extern s32 __nospec_call_start[], __nospec_call_end[];
  86. extern s32 __nospec_return_start[], __nospec_return_end[];
  87. void __init nospec_init_branches(void)
  88. {
  89. nospec_call_revert(__nospec_call_start, __nospec_call_end);
  90. nospec_return_revert(__nospec_return_start, __nospec_return_end);
  91. }