diff options
author | Laura Abbott <lauraa@codeaurora.org> | 2015-01-21 20:36:05 -0500 |
---|---|---|
committer | Catalin Marinas <catalin.marinas@arm.com> | 2015-01-22 06:50:56 -0500 |
commit | 2f896d5866107e2926dcdec34a7d40bc56dd2951 (patch) | |
tree | 45269256d0e928bcc51838ce63f2913abe252480 | |
parent | 6083fe74b7bfffc2c7be8c711596608bda0cda6e (diff) |
arm64: use fixmap for text patching
When kernel text is marked as read only, it cannot be modified directly.
Use a fixmap to modify the text instead in a similar manner to
x86 and arm.
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Kees Cook <keescook@chromium.org>
Tested-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Laura Abbott <lauraa@codeaurora.org>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
-rw-r--r-- | arch/arm64/include/asm/fixmap.h | 1 | ||||
-rw-r--r-- | arch/arm64/kernel/insn.c | 47 |
2 files changed, 47 insertions, 1 deletions
diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h index 9ef6eca905ca..defa0ff98250 100644 --- a/arch/arm64/include/asm/fixmap.h +++ b/arch/arm64/include/asm/fixmap.h | |||
@@ -49,6 +49,7 @@ enum fixed_addresses { | |||
49 | 49 | ||
50 | FIX_BTMAP_END = __end_of_permanent_fixed_addresses, | 50 | FIX_BTMAP_END = __end_of_permanent_fixed_addresses, |
51 | FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1, | 51 | FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1, |
52 | FIX_TEXT_POKE0, | ||
52 | __end_of_fixed_addresses | 53 | __end_of_fixed_addresses |
53 | }; | 54 | }; |
54 | 55 | ||
diff --git a/arch/arm64/kernel/insn.c b/arch/arm64/kernel/insn.c index 7e9327a0986d..27d4864577e5 100644 --- a/arch/arm64/kernel/insn.c +++ b/arch/arm64/kernel/insn.c | |||
@@ -17,14 +17,19 @@ | |||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | */ | 18 | */ |
19 | #include <linux/bitops.h> | 19 | #include <linux/bitops.h> |
20 | #include <linux/bug.h> | ||
20 | #include <linux/compiler.h> | 21 | #include <linux/compiler.h> |
21 | #include <linux/kernel.h> | 22 | #include <linux/kernel.h> |
23 | #include <linux/mm.h> | ||
22 | #include <linux/smp.h> | 24 | #include <linux/smp.h> |
25 | #include <linux/spinlock.h> | ||
23 | #include <linux/stop_machine.h> | 26 | #include <linux/stop_machine.h> |
27 | #include <linux/types.h> | ||
24 | #include <linux/uaccess.h> | 28 | #include <linux/uaccess.h> |
25 | 29 | ||
26 | #include <asm/cacheflush.h> | 30 | #include <asm/cacheflush.h> |
27 | #include <asm/debug-monitors.h> | 31 | #include <asm/debug-monitors.h> |
32 | #include <asm/fixmap.h> | ||
28 | #include <asm/insn.h> | 33 | #include <asm/insn.h> |
29 | 34 | ||
30 | #define AARCH64_INSN_SF_BIT BIT(31) | 35 | #define AARCH64_INSN_SF_BIT BIT(31) |
@@ -72,6 +77,29 @@ bool __kprobes aarch64_insn_is_nop(u32 insn) | |||
72 | } | 77 | } |
73 | } | 78 | } |
74 | 79 | ||
80 | static DEFINE_SPINLOCK(patch_lock); | ||
81 | |||
82 | static void __kprobes *patch_map(void *addr, int fixmap) | ||
83 | { | ||
84 | unsigned long uintaddr = (uintptr_t) addr; | ||
85 | bool module = !core_kernel_text(uintaddr); | ||
86 | struct page *page; | ||
87 | |||
88 | if (module && IS_ENABLED(CONFIG_DEBUG_SET_MODULE_RONX)) | ||
89 | page = vmalloc_to_page(addr); | ||
90 | else | ||
91 | page = virt_to_page(addr); | ||
92 | |||
93 | BUG_ON(!page); | ||
94 | set_fixmap(fixmap, page_to_phys(page)); | ||
95 | |||
96 | return (void *) (__fix_to_virt(fixmap) + (uintaddr & ~PAGE_MASK)); | ||
97 | } | ||
98 | |||
99 | static void __kprobes patch_unmap(int fixmap) | ||
100 | { | ||
101 | clear_fixmap(fixmap); | ||
102 | } | ||
75 | /* | 103 | /* |
76 | * In ARMv8-A, A64 instructions have a fixed length of 32 bits and are always | 104 | * In ARMv8-A, A64 instructions have a fixed length of 32 bits and are always |
77 | * little-endian. | 105 | * little-endian. |
@@ -88,10 +116,27 @@ int __kprobes aarch64_insn_read(void *addr, u32 *insnp) | |||
88 | return ret; | 116 | return ret; |
89 | } | 117 | } |
90 | 118 | ||
119 | static int __kprobes __aarch64_insn_write(void *addr, u32 insn) | ||
120 | { | ||
121 | void *waddr = addr; | ||
122 | unsigned long flags = 0; | ||
123 | int ret; | ||
124 | |||
125 | spin_lock_irqsave(&patch_lock, flags); | ||
126 | waddr = patch_map(addr, FIX_TEXT_POKE0); | ||
127 | |||
128 | ret = probe_kernel_write(waddr, &insn, AARCH64_INSN_SIZE); | ||
129 | |||
130 | patch_unmap(FIX_TEXT_POKE0); | ||
131 | spin_unlock_irqrestore(&patch_lock, flags); | ||
132 | |||
133 | return ret; | ||
134 | } | ||
135 | |||
91 | int __kprobes aarch64_insn_write(void *addr, u32 insn) | 136 | int __kprobes aarch64_insn_write(void *addr, u32 insn) |
92 | { | 137 | { |
93 | insn = cpu_to_le32(insn); | 138 | insn = cpu_to_le32(insn); |
94 | return probe_kernel_write(addr, &insn, AARCH64_INSN_SIZE); | 139 | return __aarch64_insn_write(addr, insn); |
95 | } | 140 | } |
96 | 141 | ||
97 | static bool __kprobes __aarch64_insn_hotpatch_safe(u32 insn) | 142 | static bool __kprobes __aarch64_insn_hotpatch_safe(u32 insn) |