|
18 | 18 | #include <linux/mmu_context.h> |
19 | 19 | #include <linux/bsearch.h> |
20 | 20 | #include <linux/sync_core.h> |
| 21 | +#include <linux/execmem.h> |
21 | 22 | #include <asm/text-patching.h> |
22 | 23 | #include <asm/alternative.h> |
23 | 24 | #include <asm/sections.h> |
|
32 | 33 | #include <asm/asm-prototypes.h> |
33 | 34 | #include <asm/cfi.h> |
34 | 35 | #include <asm/ibt.h> |
| 36 | +#include <asm/set_memory.h> |
35 | 37 |
|
36 | 38 | int __read_mostly alternatives_patched; |
37 | 39 |
|
@@ -125,6 +127,121 @@ const unsigned char * const x86_nops[ASM_NOP_MAX+1] = |
125 | 127 | #endif |
126 | 128 | }; |
127 | 129 |
|
| 130 | +#ifdef CONFIG_MITIGATION_ITS |
| 131 | + |
| 132 | +static struct module *its_mod; |
| 133 | +static void *its_page; |
| 134 | +static unsigned int its_offset; |
| 135 | + |
| 136 | +/* Initialize a thunk with the "jmp *reg; int3" instructions. */ |
| 137 | +static void *its_init_thunk(void *thunk, int reg) |
| 138 | +{ |
| 139 | + u8 *bytes = thunk; |
| 140 | + int i = 0; |
| 141 | + |
| 142 | + if (reg >= 8) { |
| 143 | + bytes[i++] = 0x41; /* REX.B prefix */ |
| 144 | + reg -= 8; |
| 145 | + } |
| 146 | + bytes[i++] = 0xff; |
| 147 | + bytes[i++] = 0xe0 + reg; /* jmp *reg */ |
| 148 | + bytes[i++] = 0xcc; |
| 149 | + |
| 150 | + return thunk; |
| 151 | +} |
| 152 | + |
| 153 | +void its_init_mod(struct module *mod) |
| 154 | +{ |
| 155 | + if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS)) |
| 156 | + return; |
| 157 | + |
| 158 | + mutex_lock(&text_mutex); |
| 159 | + its_mod = mod; |
| 160 | + its_page = NULL; |
| 161 | +} |
| 162 | + |
| 163 | +void its_fini_mod(struct module *mod) |
| 164 | +{ |
| 165 | + if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS)) |
| 166 | + return; |
| 167 | + |
| 168 | + WARN_ON_ONCE(its_mod != mod); |
| 169 | + |
| 170 | + its_mod = NULL; |
| 171 | + its_page = NULL; |
| 172 | + mutex_unlock(&text_mutex); |
| 173 | + |
| 174 | + for (int i = 0; i < mod->its_num_pages; i++) { |
| 175 | + void *page = mod->its_page_array[i]; |
| 176 | + execmem_restore_rox(page, PAGE_SIZE); |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +void its_free_mod(struct module *mod) |
| 181 | +{ |
| 182 | + if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS)) |
| 183 | + return; |
| 184 | + |
| 185 | + for (int i = 0; i < mod->its_num_pages; i++) { |
| 186 | + void *page = mod->its_page_array[i]; |
| 187 | + execmem_free(page); |
| 188 | + } |
| 189 | + kfree(mod->its_page_array); |
| 190 | +} |
| 191 | + |
| 192 | +static void *its_alloc(void) |
| 193 | +{ |
| 194 | + void *page __free(execmem) = execmem_alloc(EXECMEM_MODULE_TEXT, PAGE_SIZE); |
| 195 | + |
| 196 | + if (!page) |
| 197 | + return NULL; |
| 198 | + |
| 199 | + if (its_mod) { |
| 200 | + void *tmp = krealloc(its_mod->its_page_array, |
| 201 | + (its_mod->its_num_pages+1) * sizeof(void *), |
| 202 | + GFP_KERNEL); |
| 203 | + if (!tmp) |
| 204 | + return NULL; |
| 205 | + |
| 206 | + its_mod->its_page_array = tmp; |
| 207 | + its_mod->its_page_array[its_mod->its_num_pages++] = page; |
| 208 | + |
| 209 | + execmem_make_temp_rw(page, PAGE_SIZE); |
| 210 | + } |
| 211 | + |
| 212 | + return no_free_ptr(page); |
| 213 | +} |
| 214 | + |
| 215 | +static void *its_allocate_thunk(int reg) |
| 216 | +{ |
| 217 | + int size = 3 + (reg / 8); |
| 218 | + void *thunk; |
| 219 | + |
| 220 | + if (!its_page || (its_offset + size - 1) >= PAGE_SIZE) { |
| 221 | + its_page = its_alloc(); |
| 222 | + if (!its_page) { |
| 223 | + pr_err("ITS page allocation failed\n"); |
| 224 | + return NULL; |
| 225 | + } |
| 226 | + memset(its_page, INT3_INSN_OPCODE, PAGE_SIZE); |
| 227 | + its_offset = 32; |
| 228 | + } |
| 229 | + |
| 230 | + /* |
| 231 | + * If the indirect branch instruction will be in the lower half |
| 232 | + * of a cacheline, then update the offset to reach the upper half. |
| 233 | + */ |
| 234 | + if ((its_offset + size - 1) % 64 < 32) |
| 235 | + its_offset = ((its_offset - 1) | 0x3F) + 33; |
| 236 | + |
| 237 | + thunk = its_page + its_offset; |
| 238 | + its_offset += size; |
| 239 | + |
| 240 | + return its_init_thunk(thunk, reg); |
| 241 | +} |
| 242 | + |
| 243 | +#endif |
| 244 | + |
128 | 245 | /* |
129 | 246 | * Nomenclature for variable names to simplify and clarify this code and ease |
130 | 247 | * any potential staring at it: |
@@ -637,9 +754,13 @@ static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8 |
637 | 754 | #ifdef CONFIG_MITIGATION_ITS |
638 | 755 | static int emit_its_trampoline(void *addr, struct insn *insn, int reg, u8 *bytes) |
639 | 756 | { |
640 | | - return __emit_trampoline(addr, insn, bytes, |
641 | | - __x86_indirect_its_thunk_array[reg], |
642 | | - __x86_indirect_its_thunk_array[reg]); |
| 757 | + u8 *thunk = __x86_indirect_its_thunk_array[reg]; |
| 758 | + u8 *tmp = its_allocate_thunk(reg); |
| 759 | + |
| 760 | + if (tmp) |
| 761 | + thunk = tmp; |
| 762 | + |
| 763 | + return __emit_trampoline(addr, insn, bytes, thunk, thunk); |
643 | 764 | } |
644 | 765 |
|
645 | 766 | /* Check if an indirect branch is at ITS-unsafe address */ |
|
0 commit comments