| From 27b4ba663f36650c5a72ce5e874df2e6833941af Mon Sep 17 00:00:00 2001 |
| From: Nadav Amit <namit@vmware.com> |
| Date: Thu, 25 Apr 2019 17:11:31 -0700 |
| Subject: x86/modules: Avoid breaking W^X while loading modules |
| |
| [ Upstream commit f2c65fb3221adc6b73b0549fc7ba892022db9797 ] |
| |
| When modules and BPF filters are loaded, there is a time window in |
| which some memory is both writable and executable. An attacker that has |
| already found another vulnerability (e.g., a dangling pointer) might be |
| able to exploit this behavior to overwrite kernel code. Prevent having |
| writable executable PTEs in this stage. |
| |
| In addition, avoiding having W+X mappings can also slightly simplify the |
| patching of modules code on initialization (e.g., by alternatives and |
| static-key), as would be done in the next patch. This was actually the |
| main motivation for this patch. |
| |
| To avoid having W+X mappings, set them initially as RW (NX) and after |
| they are set as RO set them as X as well. Setting them as executable is |
| done as a separate step to avoid one core in which the old PTE is cached |
| (hence writable), and another which sees the updated PTE (executable), |
| which would break the W^X protection. |
| |
| Suggested-by: Thomas Gleixner <tglx@linutronix.de> |
| Suggested-by: Andy Lutomirski <luto@amacapital.net> |
| Signed-off-by: Nadav Amit <namit@vmware.com> |
| Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com> |
| Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> |
| Cc: <akpm@linux-foundation.org> |
| Cc: <ard.biesheuvel@linaro.org> |
| Cc: <deneen.t.dock@intel.com> |
| Cc: <kernel-hardening@lists.openwall.com> |
| Cc: <kristen@linux.intel.com> |
| Cc: <linux_dti@icloud.com> |
| Cc: <will.deacon@arm.com> |
| Cc: Andy Lutomirski <luto@kernel.org> |
| Cc: Borislav Petkov <bp@alien8.de> |
| Cc: Dave Hansen <dave.hansen@intel.com> |
| Cc: H. Peter Anvin <hpa@zytor.com> |
| Cc: Jessica Yu <jeyu@kernel.org> |
| Cc: Kees Cook <keescook@chromium.org> |
| Cc: Linus Torvalds <torvalds@linux-foundation.org> |
| Cc: Masami Hiramatsu <mhiramat@kernel.org> |
| Cc: Rik van Riel <riel@surriel.com> |
| Link: https://lkml.kernel.org/r/20190426001143.4983-12-namit@vmware.com |
| Signed-off-by: Ingo Molnar <mingo@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| arch/x86/kernel/alternative.c | 28 +++++++++++++++++++++------- |
| arch/x86/kernel/module.c | 2 +- |
| include/linux/filter.h | 1 + |
| kernel/module.c | 5 +++++ |
| 4 files changed, 28 insertions(+), 8 deletions(-) |
| |
| diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c |
| index 9a79c7808f9cc..d7df79fc448cd 100644 |
| --- a/arch/x86/kernel/alternative.c |
| +++ b/arch/x86/kernel/alternative.c |
| @@ -667,15 +667,29 @@ void __init alternative_instructions(void) |
| * handlers seeing an inconsistent instruction while you patch. |
| */ |
| void *__init_or_module text_poke_early(void *addr, const void *opcode, |
| - size_t len) |
| + size_t len) |
| { |
| unsigned long flags; |
| - local_irq_save(flags); |
| - memcpy(addr, opcode, len); |
| - local_irq_restore(flags); |
| - sync_core(); |
| - /* Could also do a CLFLUSH here to speed up CPU recovery; but |
| - that causes hangs on some VIA CPUs. */ |
| + |
| + if (boot_cpu_has(X86_FEATURE_NX) && |
| + is_module_text_address((unsigned long)addr)) { |
| + /* |
| + * Modules text is marked initially as non-executable, so the |
| + * code cannot be running and speculative code-fetches are |
| + * prevented. Just change the code. |
| + */ |
| + memcpy(addr, opcode, len); |
| + } else { |
| + local_irq_save(flags); |
| + memcpy(addr, opcode, len); |
| + local_irq_restore(flags); |
| + sync_core(); |
| + |
| + /* |
| + * Could also do a CLFLUSH here to speed up CPU recovery; but |
| + * that causes hangs on some VIA CPUs. |
| + */ |
| + } |
| return addr; |
| } |
| |
| diff --git a/arch/x86/kernel/module.c b/arch/x86/kernel/module.c |
| index b052e883dd8cc..cfa3106faee42 100644 |
| --- a/arch/x86/kernel/module.c |
| +++ b/arch/x86/kernel/module.c |
| @@ -87,7 +87,7 @@ void *module_alloc(unsigned long size) |
| p = __vmalloc_node_range(size, MODULE_ALIGN, |
| MODULES_VADDR + get_module_load_offset(), |
| MODULES_END, GFP_KERNEL, |
| - PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE, |
| + PAGE_KERNEL, 0, NUMA_NO_NODE, |
| __builtin_return_address(0)); |
| if (p && (kasan_module_alloc(p, size) < 0)) { |
| vfree(p); |
| diff --git a/include/linux/filter.h b/include/linux/filter.h |
| index 6074aa064b540..14ec3bdad9a90 100644 |
| --- a/include/linux/filter.h |
| +++ b/include/linux/filter.h |
| @@ -746,6 +746,7 @@ static inline void bpf_prog_unlock_ro(struct bpf_prog *fp) |
| static inline void bpf_jit_binary_lock_ro(struct bpf_binary_header *hdr) |
| { |
| set_memory_ro((unsigned long)hdr, hdr->pages); |
| + set_memory_x((unsigned long)hdr, hdr->pages); |
| } |
| |
| static inline void bpf_jit_binary_unlock_ro(struct bpf_binary_header *hdr) |
| diff --git a/kernel/module.c b/kernel/module.c |
| index 0b9aa8ab89f08..2b2845ae983ed 100644 |
| --- a/kernel/module.c |
| +++ b/kernel/module.c |
| @@ -1950,8 +1950,13 @@ void module_enable_ro(const struct module *mod, bool after_init) |
| return; |
| |
| frob_text(&mod->core_layout, set_memory_ro); |
| + frob_text(&mod->core_layout, set_memory_x); |
| + |
| frob_rodata(&mod->core_layout, set_memory_ro); |
| + |
| frob_text(&mod->init_layout, set_memory_ro); |
| + frob_text(&mod->init_layout, set_memory_x); |
| + |
| frob_rodata(&mod->init_layout, set_memory_ro); |
| |
| if (after_init) |
| -- |
| 2.20.1 |
| |