xref: /openbsd-src/sys/arch/amd64/amd64/codepatch.c (revision ea15225f4d0992ce43f25039df7a56779c06ea1b)
1 /*      $OpenBSD: codepatch.c,v 1.11 2024/02/04 20:18:48 guenther Exp $    */
2 /*
3  * Copyright (c) 2014-2015 Stefan Fritsch <sf@sfritsch.de>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <machine/codepatch.h>
21 #include <uvm/uvm_extern.h> /* round_page */
22 
23 #ifdef CODEPATCH_DEBUG
24 #define DBGPRINT(fmt, args...)	printf("%s: " fmt "\n", __func__, ## args)
25 #else
26 #define DBGPRINT(fmt, args...)	do {} while (0)
27 #endif
28 
29 struct codepatch {
30 	vaddr_t addr;
31 	uint16_t len;
32 	uint16_t tag;
33 	uint32_t _padding;
34 };
35 CTASSERT(sizeof(struct codepatch) % 8 == 0);
36 
37 extern struct codepatch codepatch_begin;
38 extern struct codepatch codepatch_end;
39 extern char __cptext_start[];
40 extern char __cptext_end[];
41 
42 enum op_type { OP_CALL, OP_JMP };
43 __cptext void	codepatch_control_flow(uint16_t _tag, void *_func,
44 			enum op_type _type);
45 
46 void
codepatch_fill_nop(void * caddr,uint16_t len)47 codepatch_fill_nop(void *caddr, uint16_t len)
48 {
49 	unsigned char *addr = caddr;
50 	uint16_t nop_len;
51 
52 	while (len > 0) {
53 		nop_len = len < 127 ? len : 127;
54 		switch (nop_len) {
55 		case 1:
56 			addr[0] = 0x90;
57 			break;
58 		case 2:
59 			addr[0] = 0x66;
60 			addr[1] = 0x90;
61 			break;
62 		default:
63 			addr[0] = 0xEB;
64 			addr[1] = nop_len - 2;
65 			memset(addr + 2, 0xCC, nop_len - 2);
66 			break;
67 		}
68 		addr += nop_len;
69 		len -= nop_len;
70 	}
71 }
72 
73 /*
74  * Create writeable aliases of memory we need
75  * to write to as kernel is mapped read-only
76  */
77 void *
codepatch_maprw(vaddr_t * nva,vaddr_t dest)78 codepatch_maprw(vaddr_t *nva, vaddr_t dest)
79 {
80 	paddr_t kva = trunc_page((paddr_t)dest);
81 	paddr_t po = (paddr_t)dest & PAGE_MASK;
82 	paddr_t pa1, pa2;
83 
84 	if (*nva == 0)
85 		*nva = (vaddr_t)km_alloc(2 * PAGE_SIZE, &kv_any, &kp_none,
86 					&kd_waitok);
87 
88 	pmap_extract(pmap_kernel(), kva, &pa1);
89 	pmap_extract(pmap_kernel(), kva + PAGE_SIZE, &pa2);
90 	pmap_kenter_pa(*nva, pa1, PROT_READ | PROT_WRITE);
91 	pmap_kenter_pa(*nva + PAGE_SIZE, pa2, PROT_READ | PROT_WRITE);
92 	pmap_update(pmap_kernel());
93 
94 	return (void *)(*nva + po);
95 }
96 
97 void
codepatch_unmaprw(vaddr_t nva)98 codepatch_unmaprw(vaddr_t nva)
99 {
100 	if (nva == 0)
101 		return;
102 	pmap_kremove(nva, 2 * PAGE_SIZE);
103 	km_free((void *)nva, 2 * PAGE_SIZE, &kv_any, &kp_none);
104 }
105 
106 /* Patch with NOPs */
107 void
codepatch_nop(uint16_t tag)108 codepatch_nop(uint16_t tag)
109 {
110 	struct codepatch *patch;
111 	unsigned char *rwaddr;
112 	vaddr_t rwmap = 0;
113 	int i = 0;
114 
115 	DBGPRINT("patching tag %u", tag);
116 
117 	for (patch = &codepatch_begin; patch < &codepatch_end; patch++) {
118 		if (patch->tag != tag)
119 			continue;
120 		rwaddr = codepatch_maprw(&rwmap, patch->addr);
121 		codepatch_fill_nop(rwaddr, patch->len);
122 		i++;
123 	}
124 	codepatch_unmaprw(rwmap);
125 	DBGPRINT("patched %d places", i);
126 }
127 
128 /* Patch with alternative code */
129 void
codepatch_replace(uint16_t tag,const void * code,size_t len)130 codepatch_replace(uint16_t tag, const void *code, size_t len)
131 {
132 	struct codepatch *patch;
133 	unsigned char *rwaddr;
134 	vaddr_t rwmap = 0;
135 	int i = 0;
136 
137 	DBGPRINT("patching tag %u with %p", tag, code);
138 
139 	for (patch = &codepatch_begin; patch < &codepatch_end; patch++) {
140 		if (patch->tag != tag)
141 			continue;
142 
143 		if (len > patch->len) {
144 			panic("%s: can't replace len %u with %zu at %#lx",
145 			    __func__, patch->len, len, patch->addr);
146 		}
147 		rwaddr = codepatch_maprw(&rwmap, patch->addr);
148 		memcpy(rwaddr, code, len);
149 		codepatch_fill_nop(rwaddr + len, patch->len - len);
150 		i++;
151 	}
152 	codepatch_unmaprw(rwmap);
153 	DBGPRINT("patched %d places", i);
154 }
155 
156 void
codepatch_call(uint16_t tag,void * func)157 codepatch_call(uint16_t tag, void *func)
158 {
159 	codepatch_control_flow(tag, func, OP_CALL);
160 }
161 
162 void
codepatch_jmp(uint16_t tag,void * func)163 codepatch_jmp(uint16_t tag, void *func)
164 {
165 	codepatch_control_flow(tag, func, OP_JMP);
166 }
167 
168 /* Patch with call or jump to func */
169 void
codepatch_control_flow(uint16_t tag,void * func,enum op_type type)170 codepatch_control_flow(uint16_t tag, void *func, enum op_type type)
171 {
172 	struct codepatch *patch;
173 	unsigned char *rwaddr;
174 	int32_t offset;
175 	int i = 0;
176 	vaddr_t rwmap = 0;
177 	const char *op = type == OP_JMP ? "jmp" : "call";
178 	char opcode = type == OP_JMP ? 0xe9 /* jmp near */
179 	    : 0xe8 /* call near */;
180 
181 	DBGPRINT("patching tag %u with %s %p", tag, op, func);
182 
183 	for (patch = &codepatch_begin; patch < &codepatch_end; patch++) {
184 		if (patch->tag != tag)
185 			continue;
186 		if (patch->len < 5)
187 			panic("%s: can't replace len %u with %s at %#lx",
188 			    __func__, patch->len, op, patch->addr);
189 
190 		offset = (vaddr_t)func - (patch->addr + 5);
191 		rwaddr = codepatch_maprw(&rwmap, patch->addr);
192 		rwaddr[0] = opcode;
193 		memcpy(rwaddr + 1, &offset, sizeof(offset));
194 		if (type == OP_CALL)
195 			codepatch_fill_nop(rwaddr + 5, patch->len - 5);
196 		else /* OP_JMP */
197 			memset(rwaddr + 5, 0xCC /* int3 */, patch->len - 5);
198 		i++;
199 	}
200 	codepatch_unmaprw(rwmap);
201 	DBGPRINT("patched %d places", i);
202 }
203 
204 void
codepatch_disable(void)205 codepatch_disable(void)
206 {
207 	size_t size = round_page(__cptext_end - __cptext_start);
208 	/* If this assert fails, something is wrong with the cptext section */
209 	KASSERT(size > 0);
210 	pmap_kremove((vaddr_t)__cptext_start, size);
211 	pmap_update(pmap_kernel());
212 	DBGPRINT("%s: Unmapped %#zx bytes\n", __func__, size);
213 }
214