15f154c4eSJulien Thierry // SPDX-License-Identifier: GPL-2.0-only
25f154c4eSJulien Thierry #include <linux/kernel.h>
35f154c4eSJulien Thierry #include <linux/mm.h>
45f154c4eSJulien Thierry #include <linux/smp.h>
55f154c4eSJulien Thierry #include <linux/spinlock.h>
65f154c4eSJulien Thierry #include <linux/stop_machine.h>
75f154c4eSJulien Thierry #include <linux/uaccess.h>
85f154c4eSJulien Thierry
95f154c4eSJulien Thierry #include <asm/cacheflush.h>
105f154c4eSJulien Thierry #include <asm/fixmap.h>
113e00e39dSMark Rutland #include <asm/insn.h>
125f154c4eSJulien Thierry #include <asm/kprobes.h>
130c3beacfSMike Rapoport (Microsoft) #include <asm/text-patching.h>
145f154c4eSJulien Thierry #include <asm/sections.h>
155f154c4eSJulien Thierry
165f154c4eSJulien Thierry static DEFINE_RAW_SPINLOCK(patch_lock);
175f154c4eSJulien Thierry
is_exit_text(unsigned long addr)185f154c4eSJulien Thierry static bool is_exit_text(unsigned long addr)
195f154c4eSJulien Thierry {
205f154c4eSJulien Thierry /* discarded with init text/data */
215f154c4eSJulien Thierry return system_state < SYSTEM_RUNNING &&
225f154c4eSJulien Thierry addr >= (unsigned long)__exittext_begin &&
235f154c4eSJulien Thierry addr < (unsigned long)__exittext_end;
245f154c4eSJulien Thierry }
255f154c4eSJulien Thierry
is_image_text(unsigned long addr)265f154c4eSJulien Thierry static bool is_image_text(unsigned long addr)
275f154c4eSJulien Thierry {
285f154c4eSJulien Thierry return core_kernel_text(addr) || is_exit_text(addr);
295f154c4eSJulien Thierry }
305f154c4eSJulien Thierry
patch_map(void * addr,int fixmap)315f154c4eSJulien Thierry static void __kprobes *patch_map(void *addr, int fixmap)
325f154c4eSJulien Thierry {
33*8d09e2d5SMark Rutland phys_addr_t phys;
345f154c4eSJulien Thierry
35*8d09e2d5SMark Rutland if (is_image_text((unsigned long)addr)) {
36*8d09e2d5SMark Rutland phys = __pa_symbol(addr);
37*8d09e2d5SMark Rutland } else {
38*8d09e2d5SMark Rutland struct page *page = vmalloc_to_page(addr);
395f154c4eSJulien Thierry BUG_ON(!page);
40*8d09e2d5SMark Rutland phys = page_to_phys(page) + offset_in_page(addr);
41*8d09e2d5SMark Rutland }
42*8d09e2d5SMark Rutland
43*8d09e2d5SMark Rutland return (void *)set_fixmap_offset(fixmap, phys);
445f154c4eSJulien Thierry }
455f154c4eSJulien Thierry
patch_unmap(int fixmap)465f154c4eSJulien Thierry static void __kprobes patch_unmap(int fixmap)
475f154c4eSJulien Thierry {
485f154c4eSJulien Thierry clear_fixmap(fixmap);
495f154c4eSJulien Thierry }
505f154c4eSJulien Thierry /*
515f154c4eSJulien Thierry * In ARMv8-A, A64 instructions have a fixed length of 32 bits and are always
525f154c4eSJulien Thierry * little-endian.
535f154c4eSJulien Thierry */
aarch64_insn_read(void * addr,u32 * insnp)545f154c4eSJulien Thierry int __kprobes aarch64_insn_read(void *addr, u32 *insnp)
555f154c4eSJulien Thierry {
565f154c4eSJulien Thierry int ret;
575f154c4eSJulien Thierry __le32 val;
585f154c4eSJulien Thierry
595f154c4eSJulien Thierry ret = copy_from_kernel_nofault(&val, addr, AARCH64_INSN_SIZE);
605f154c4eSJulien Thierry if (!ret)
615f154c4eSJulien Thierry *insnp = le32_to_cpu(val);
625f154c4eSJulien Thierry
635f154c4eSJulien Thierry return ret;
645f154c4eSJulien Thierry }
655f154c4eSJulien Thierry
__aarch64_insn_write(void * addr,__le32 insn)665f154c4eSJulien Thierry static int __kprobes __aarch64_insn_write(void *addr, __le32 insn)
675f154c4eSJulien Thierry {
685f154c4eSJulien Thierry void *waddr = addr;
695f154c4eSJulien Thierry unsigned long flags = 0;
705f154c4eSJulien Thierry int ret;
715f154c4eSJulien Thierry
725f154c4eSJulien Thierry raw_spin_lock_irqsave(&patch_lock, flags);
735f154c4eSJulien Thierry waddr = patch_map(addr, FIX_TEXT_POKE0);
745f154c4eSJulien Thierry
755f154c4eSJulien Thierry ret = copy_to_kernel_nofault(waddr, &insn, AARCH64_INSN_SIZE);
765f154c4eSJulien Thierry
775f154c4eSJulien Thierry patch_unmap(FIX_TEXT_POKE0);
785f154c4eSJulien Thierry raw_spin_unlock_irqrestore(&patch_lock, flags);
795f154c4eSJulien Thierry
805f154c4eSJulien Thierry return ret;
815f154c4eSJulien Thierry }
825f154c4eSJulien Thierry
aarch64_insn_write(void * addr,u32 insn)835f154c4eSJulien Thierry int __kprobes aarch64_insn_write(void *addr, u32 insn)
845f154c4eSJulien Thierry {
855f154c4eSJulien Thierry return __aarch64_insn_write(addr, cpu_to_le32(insn));
865f154c4eSJulien Thierry }
875f154c4eSJulien Thierry
aarch64_insn_write_literal_u64(void * addr,u64 val)88e4ecbe83SMark Rutland noinstr int aarch64_insn_write_literal_u64(void *addr, u64 val)
89e4ecbe83SMark Rutland {
90e4ecbe83SMark Rutland u64 *waddr;
91e4ecbe83SMark Rutland unsigned long flags;
92e4ecbe83SMark Rutland int ret;
93e4ecbe83SMark Rutland
94e4ecbe83SMark Rutland raw_spin_lock_irqsave(&patch_lock, flags);
95e4ecbe83SMark Rutland waddr = patch_map(addr, FIX_TEXT_POKE0);
96e4ecbe83SMark Rutland
97e4ecbe83SMark Rutland ret = copy_to_kernel_nofault(waddr, &val, sizeof(val));
98e4ecbe83SMark Rutland
99e4ecbe83SMark Rutland patch_unmap(FIX_TEXT_POKE0);
100e4ecbe83SMark Rutland raw_spin_unlock_irqrestore(&patch_lock, flags);
101e4ecbe83SMark Rutland
102e4ecbe83SMark Rutland return ret;
103e4ecbe83SMark Rutland }
104e4ecbe83SMark Rutland
105451c3cabSPuranjay Mohan typedef void text_poke_f(void *dst, void *src, size_t patched, size_t len);
106451c3cabSPuranjay Mohan
__text_poke(text_poke_f func,void * addr,void * src,size_t len)107451c3cabSPuranjay Mohan static void *__text_poke(text_poke_f func, void *addr, void *src, size_t len)
108451c3cabSPuranjay Mohan {
109451c3cabSPuranjay Mohan unsigned long flags;
110451c3cabSPuranjay Mohan size_t patched = 0;
111451c3cabSPuranjay Mohan size_t size;
112451c3cabSPuranjay Mohan void *waddr;
113451c3cabSPuranjay Mohan void *ptr;
114451c3cabSPuranjay Mohan
115451c3cabSPuranjay Mohan raw_spin_lock_irqsave(&patch_lock, flags);
116451c3cabSPuranjay Mohan
117451c3cabSPuranjay Mohan while (patched < len) {
118451c3cabSPuranjay Mohan ptr = addr + patched;
119451c3cabSPuranjay Mohan size = min_t(size_t, PAGE_SIZE - offset_in_page(ptr),
120451c3cabSPuranjay Mohan len - patched);
121451c3cabSPuranjay Mohan
122451c3cabSPuranjay Mohan waddr = patch_map(ptr, FIX_TEXT_POKE0);
123451c3cabSPuranjay Mohan func(waddr, src, patched, size);
124451c3cabSPuranjay Mohan patch_unmap(FIX_TEXT_POKE0);
125451c3cabSPuranjay Mohan
126451c3cabSPuranjay Mohan patched += size;
127451c3cabSPuranjay Mohan }
128451c3cabSPuranjay Mohan raw_spin_unlock_irqrestore(&patch_lock, flags);
129451c3cabSPuranjay Mohan
130451c3cabSPuranjay Mohan flush_icache_range((uintptr_t)addr, (uintptr_t)addr + len);
131451c3cabSPuranjay Mohan
132451c3cabSPuranjay Mohan return addr;
133451c3cabSPuranjay Mohan }
134451c3cabSPuranjay Mohan
text_poke_memcpy(void * dst,void * src,size_t patched,size_t len)135451c3cabSPuranjay Mohan static void text_poke_memcpy(void *dst, void *src, size_t patched, size_t len)
136451c3cabSPuranjay Mohan {
137451c3cabSPuranjay Mohan copy_to_kernel_nofault(dst, src + patched, len);
138451c3cabSPuranjay Mohan }
139451c3cabSPuranjay Mohan
text_poke_memset(void * dst,void * src,size_t patched,size_t len)140451c3cabSPuranjay Mohan static void text_poke_memset(void *dst, void *src, size_t patched, size_t len)
141451c3cabSPuranjay Mohan {
142451c3cabSPuranjay Mohan u32 c = *(u32 *)src;
143451c3cabSPuranjay Mohan
144451c3cabSPuranjay Mohan memset32(dst, c, len / 4);
145451c3cabSPuranjay Mohan }
146451c3cabSPuranjay Mohan
147451c3cabSPuranjay Mohan /**
148451c3cabSPuranjay Mohan * aarch64_insn_copy - Copy instructions into (an unused part of) RX memory
149451c3cabSPuranjay Mohan * @dst: address to modify
150451c3cabSPuranjay Mohan * @src: source of the copy
151451c3cabSPuranjay Mohan * @len: length to copy
152451c3cabSPuranjay Mohan *
153451c3cabSPuranjay Mohan * Useful for JITs to dump new code blocks into unused regions of RX memory.
154451c3cabSPuranjay Mohan */
aarch64_insn_copy(void * dst,void * src,size_t len)155451c3cabSPuranjay Mohan noinstr void *aarch64_insn_copy(void *dst, void *src, size_t len)
156451c3cabSPuranjay Mohan {
157451c3cabSPuranjay Mohan /* A64 instructions must be word aligned */
158451c3cabSPuranjay Mohan if ((uintptr_t)dst & 0x3)
159451c3cabSPuranjay Mohan return NULL;
160451c3cabSPuranjay Mohan
161451c3cabSPuranjay Mohan return __text_poke(text_poke_memcpy, dst, src, len);
162451c3cabSPuranjay Mohan }
163451c3cabSPuranjay Mohan
164451c3cabSPuranjay Mohan /**
165451c3cabSPuranjay Mohan * aarch64_insn_set - memset for RX memory regions.
166451c3cabSPuranjay Mohan * @dst: address to modify
167451c3cabSPuranjay Mohan * @insn: value to set
168451c3cabSPuranjay Mohan * @len: length of memory region.
169451c3cabSPuranjay Mohan *
170451c3cabSPuranjay Mohan * Useful for JITs to fill regions of RX memory with illegal instructions.
171451c3cabSPuranjay Mohan */
aarch64_insn_set(void * dst,u32 insn,size_t len)172451c3cabSPuranjay Mohan noinstr void *aarch64_insn_set(void *dst, u32 insn, size_t len)
173451c3cabSPuranjay Mohan {
174451c3cabSPuranjay Mohan if ((uintptr_t)dst & 0x3)
175451c3cabSPuranjay Mohan return NULL;
176451c3cabSPuranjay Mohan
177451c3cabSPuranjay Mohan return __text_poke(text_poke_memset, dst, &insn, len);
178451c3cabSPuranjay Mohan }
179451c3cabSPuranjay Mohan
aarch64_insn_patch_text_nosync(void * addr,u32 insn)1805f154c4eSJulien Thierry int __kprobes aarch64_insn_patch_text_nosync(void *addr, u32 insn)
1815f154c4eSJulien Thierry {
1825f154c4eSJulien Thierry u32 *tp = addr;
1835f154c4eSJulien Thierry int ret;
1845f154c4eSJulien Thierry
1855f154c4eSJulien Thierry /* A64 instructions must be word aligned */
1865f154c4eSJulien Thierry if ((uintptr_t)tp & 0x3)
1875f154c4eSJulien Thierry return -EINVAL;
1885f154c4eSJulien Thierry
1895f154c4eSJulien Thierry ret = aarch64_insn_write(tp, insn);
1905f154c4eSJulien Thierry if (ret == 0)
191181a1269SWill Deacon caches_clean_inval_pou((uintptr_t)tp,
1925f154c4eSJulien Thierry (uintptr_t)tp + AARCH64_INSN_SIZE);
1935f154c4eSJulien Thierry
1945f154c4eSJulien Thierry return ret;
1955f154c4eSJulien Thierry }
1965f154c4eSJulien Thierry
1975f154c4eSJulien Thierry struct aarch64_insn_patch {
1985f154c4eSJulien Thierry void **text_addrs;
1995f154c4eSJulien Thierry u32 *new_insns;
2005f154c4eSJulien Thierry int insn_cnt;
2015f154c4eSJulien Thierry atomic_t cpu_count;
2025f154c4eSJulien Thierry };
2035f154c4eSJulien Thierry
aarch64_insn_patch_text_cb(void * arg)2045f154c4eSJulien Thierry static int __kprobes aarch64_insn_patch_text_cb(void *arg)
2055f154c4eSJulien Thierry {
2065f154c4eSJulien Thierry int i, ret = 0;
2075f154c4eSJulien Thierry struct aarch64_insn_patch *pp = arg;
2085f154c4eSJulien Thierry
20931a099dbSGuo Ren /* The last CPU becomes master */
21031a099dbSGuo Ren if (atomic_inc_return(&pp->cpu_count) == num_online_cpus()) {
2115f154c4eSJulien Thierry for (i = 0; ret == 0 && i < pp->insn_cnt; i++)
2125f154c4eSJulien Thierry ret = aarch64_insn_patch_text_nosync(pp->text_addrs[i],
2135f154c4eSJulien Thierry pp->new_insns[i]);
2145f154c4eSJulien Thierry /* Notify other processors with an additional increment. */
2155f154c4eSJulien Thierry atomic_inc(&pp->cpu_count);
2165f154c4eSJulien Thierry } else {
2175f154c4eSJulien Thierry while (atomic_read(&pp->cpu_count) <= num_online_cpus())
2185f154c4eSJulien Thierry cpu_relax();
2195f154c4eSJulien Thierry isb();
2205f154c4eSJulien Thierry }
2215f154c4eSJulien Thierry
2225f154c4eSJulien Thierry return ret;
2235f154c4eSJulien Thierry }
2245f154c4eSJulien Thierry
aarch64_insn_patch_text(void * addrs[],u32 insns[],int cnt)2255f154c4eSJulien Thierry int __kprobes aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt)
2265f154c4eSJulien Thierry {
2275f154c4eSJulien Thierry struct aarch64_insn_patch patch = {
2285f154c4eSJulien Thierry .text_addrs = addrs,
2295f154c4eSJulien Thierry .new_insns = insns,
2305f154c4eSJulien Thierry .insn_cnt = cnt,
2315f154c4eSJulien Thierry .cpu_count = ATOMIC_INIT(0),
2325f154c4eSJulien Thierry };
2335f154c4eSJulien Thierry
2345f154c4eSJulien Thierry if (cnt <= 0)
2355f154c4eSJulien Thierry return -EINVAL;
2365f154c4eSJulien Thierry
2375f154c4eSJulien Thierry return stop_machine_cpuslocked(aarch64_insn_patch_text_cb, &patch,
2385f154c4eSJulien Thierry cpu_online_mask);
2395f154c4eSJulien Thierry }
240