aboutsummaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
authorAndi Kleen <ak@suse.de>2007-08-10 16:31:03 -0400
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-08-11 18:58:13 -0400
commitab144f5ec64c42218a555ec1dbde6b60cf2982d6 (patch)
treee3a4532e1db116e87060c9b18f4cfbf6258fdba3 /arch
parentd3f3c9346979bfa074c64eac5fc3ed5bba4f40ed (diff)
i386: Make patching more robust, fix paravirt issue
Commit 19d36ccdc34f5ed444f8a6af0cbfdb6790eb1177 "x86: Fix alternatives and kprobes to remap write-protected kernel text" uses code which is being patched for patching. In particular, paravirt_ops does patching in two stages: first it calls paravirt_ops.patch, then it fills any remaining instructions with nop_out(). nop_out calls text_poke() which calls lookup_address() which calls pgd_val() (aka paravirt_ops.pgd_val): that call site is one of the places we patch. If we always do patching as one single call to text_poke(), we only need make sure we're not patching the memcpy in text_poke itself. This means the prototype to paravirt_ops.patch needs to change, to marshal the new code into a buffer rather than patching in place as it does now. It also means all patching goes through text_poke(), which is known to be safe (apply_alternatives is also changed to make a single patch). AK: fix compilation on x86-64 (bad rusty!) AK: fix boot on x86-64 (sigh) AK: merged with other patches Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: Andi Kleen <ak@suse.de> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'arch')
-rw-r--r--arch/i386/kernel/alternative.c33
-rw-r--r--arch/i386/kernel/paravirt.c52
-rw-r--r--arch/i386/kernel/vmi.c35
-rw-r--r--arch/i386/xen/enlighten.c12
4 files changed, 75 insertions, 57 deletions
diff --git a/arch/i386/kernel/alternative.c b/arch/i386/kernel/alternative.c
index c85598acb8fd..27a6b0c9a7cc 100644
--- a/arch/i386/kernel/alternative.c
+++ b/arch/i386/kernel/alternative.c
@@ -11,6 +11,8 @@
11#include <asm/mce.h> 11#include <asm/mce.h>
12#include <asm/nmi.h> 12#include <asm/nmi.h>
13 13
14#define MAX_PATCH_LEN (255-1)
15
14#ifdef CONFIG_HOTPLUG_CPU 16#ifdef CONFIG_HOTPLUG_CPU
15static int smp_alt_once; 17static int smp_alt_once;
16 18
@@ -148,7 +150,8 @@ static unsigned char** find_nop_table(void)
148 150
149#endif /* CONFIG_X86_64 */ 151#endif /* CONFIG_X86_64 */
150 152
151static void nop_out(void *insns, unsigned int len) 153/* Use this to add nops to a buffer, then text_poke the whole buffer. */
154static void add_nops(void *insns, unsigned int len)
152{ 155{
153 unsigned char **noptable = find_nop_table(); 156 unsigned char **noptable = find_nop_table();
154 157
@@ -156,7 +159,7 @@ static void nop_out(void *insns, unsigned int len)
156 unsigned int noplen = len; 159 unsigned int noplen = len;
157 if (noplen > ASM_NOP_MAX) 160 if (noplen > ASM_NOP_MAX)
158 noplen = ASM_NOP_MAX; 161 noplen = ASM_NOP_MAX;
159 text_poke(insns, noptable[noplen], noplen); 162 memcpy(insns, noptable[noplen], noplen);
160 insns += noplen; 163 insns += noplen;
161 len -= noplen; 164 len -= noplen;
162 } 165 }
@@ -174,15 +177,15 @@ extern u8 *__smp_locks[], *__smp_locks_end[];
174void apply_alternatives(struct alt_instr *start, struct alt_instr *end) 177void apply_alternatives(struct alt_instr *start, struct alt_instr *end)
175{ 178{
176 struct alt_instr *a; 179 struct alt_instr *a;
177 u8 *instr; 180 char insnbuf[MAX_PATCH_LEN];
178 int diff;
179 181
180 DPRINTK("%s: alt table %p -> %p\n", __FUNCTION__, start, end); 182 DPRINTK("%s: alt table %p -> %p\n", __FUNCTION__, start, end);
181 for (a = start; a < end; a++) { 183 for (a = start; a < end; a++) {
184 u8 *instr = a->instr;
182 BUG_ON(a->replacementlen > a->instrlen); 185 BUG_ON(a->replacementlen > a->instrlen);
186 BUG_ON(a->instrlen > sizeof(insnbuf));
183 if (!boot_cpu_has(a->cpuid)) 187 if (!boot_cpu_has(a->cpuid))
184 continue; 188 continue;
185 instr = a->instr;
186#ifdef CONFIG_X86_64 189#ifdef CONFIG_X86_64
187 /* vsyscall code is not mapped yet. resolve it manually. */ 190 /* vsyscall code is not mapped yet. resolve it manually. */
188 if (instr >= (u8 *)VSYSCALL_START && instr < (u8*)VSYSCALL_END) { 191 if (instr >= (u8 *)VSYSCALL_START && instr < (u8*)VSYSCALL_END) {
@@ -191,9 +194,10 @@ void apply_alternatives(struct alt_instr *start, struct alt_instr *end)
191 __FUNCTION__, a->instr, instr); 194 __FUNCTION__, a->instr, instr);
192 } 195 }
193#endif 196#endif
194 memcpy(instr, a->replacement, a->replacementlen); 197 memcpy(insnbuf, a->replacement, a->replacementlen);
195 diff = a->instrlen - a->replacementlen; 198 add_nops(insnbuf + a->replacementlen,
196 nop_out(instr + a->replacementlen, diff); 199 a->instrlen - a->replacementlen);
200 text_poke(instr, insnbuf, a->instrlen);
197 } 201 }
198} 202}
199 203
@@ -215,16 +219,18 @@ static void alternatives_smp_lock(u8 **start, u8 **end, u8 *text, u8 *text_end)
215static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8 *text_end) 219static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8 *text_end)
216{ 220{
217 u8 **ptr; 221 u8 **ptr;
222 char insn[1];
218 223
219 if (noreplace_smp) 224 if (noreplace_smp)
220 return; 225 return;
221 226
227 add_nops(insn, 1);
222 for (ptr = start; ptr < end; ptr++) { 228 for (ptr = start; ptr < end; ptr++) {
223 if (*ptr < text) 229 if (*ptr < text)
224 continue; 230 continue;
225 if (*ptr > text_end) 231 if (*ptr > text_end)
226 continue; 232 continue;
227 nop_out(*ptr, 1); 233 text_poke(*ptr, insn, 1);
228 }; 234 };
229} 235}
230 236
@@ -351,6 +357,7 @@ void apply_paravirt(struct paravirt_patch_site *start,
351 struct paravirt_patch_site *end) 357 struct paravirt_patch_site *end)
352{ 358{
353 struct paravirt_patch_site *p; 359 struct paravirt_patch_site *p;
360 char insnbuf[MAX_PATCH_LEN];
354 361
355 if (noreplace_paravirt) 362 if (noreplace_paravirt)
356 return; 363 return;
@@ -358,13 +365,15 @@ void apply_paravirt(struct paravirt_patch_site *start,
358 for (p = start; p < end; p++) { 365 for (p = start; p < end; p++) {
359 unsigned int used; 366 unsigned int used;
360 367
361 used = paravirt_ops.patch(p->instrtype, p->clobbers, p->instr, 368 BUG_ON(p->len > MAX_PATCH_LEN);
362 p->len); 369 used = paravirt_ops.patch(p->instrtype, p->clobbers, insnbuf,
370 (unsigned long)p->instr, p->len);
363 371
364 BUG_ON(used > p->len); 372 BUG_ON(used > p->len);
365 373
366 /* Pad the rest with nops */ 374 /* Pad the rest with nops */
367 nop_out(p->instr + used, p->len - used); 375 add_nops(insnbuf + used, p->len - used);
376 text_poke(p->instr, insnbuf, p->len);
368 } 377 }
369} 378}
370extern struct paravirt_patch_site __start_parainstructions[], 379extern struct paravirt_patch_site __start_parainstructions[],
diff --git a/arch/i386/kernel/paravirt.c b/arch/i386/kernel/paravirt.c
index ea962c0667d5..739cfb207dd7 100644
--- a/arch/i386/kernel/paravirt.c
+++ b/arch/i386/kernel/paravirt.c
@@ -69,7 +69,8 @@ DEF_NATIVE(read_tsc, "rdtsc");
69 69
70DEF_NATIVE(ud2a, "ud2a"); 70DEF_NATIVE(ud2a, "ud2a");
71 71
72static unsigned native_patch(u8 type, u16 clobbers, void *insns, unsigned len) 72static unsigned native_patch(u8 type, u16 clobbers, void *ibuf,
73 unsigned long addr, unsigned len)
73{ 74{
74 const unsigned char *start, *end; 75 const unsigned char *start, *end;
75 unsigned ret; 76 unsigned ret;
@@ -90,7 +91,7 @@ static unsigned native_patch(u8 type, u16 clobbers, void *insns, unsigned len)
90#undef SITE 91#undef SITE
91 92
92 patch_site: 93 patch_site:
93 ret = paravirt_patch_insns(insns, len, start, end); 94 ret = paravirt_patch_insns(ibuf, len, start, end);
94 break; 95 break;
95 96
96 case PARAVIRT_PATCH(make_pgd): 97 case PARAVIRT_PATCH(make_pgd):
@@ -107,7 +108,7 @@ static unsigned native_patch(u8 type, u16 clobbers, void *insns, unsigned len)
107 break; 108 break;
108 109
109 default: 110 default:
110 ret = paravirt_patch_default(type, clobbers, insns, len); 111 ret = paravirt_patch_default(type, clobbers, ibuf, addr, len);
111 break; 112 break;
112 } 113 }
113 114
@@ -129,68 +130,67 @@ struct branch {
129 u32 delta; 130 u32 delta;
130} __attribute__((packed)); 131} __attribute__((packed));
131 132
132unsigned paravirt_patch_call(void *target, u16 tgt_clobbers, 133unsigned paravirt_patch_call(void *insnbuf,
133 void *site, u16 site_clobbers, 134 const void *target, u16 tgt_clobbers,
135 unsigned long addr, u16 site_clobbers,
134 unsigned len) 136 unsigned len)
135{ 137{
136 unsigned char *call = site; 138 struct branch *b = insnbuf;
137 unsigned long delta = (unsigned long)target - (unsigned long)(call+5); 139 unsigned long delta = (unsigned long)target - (addr+5);
138 struct branch b;
139 140
140 if (tgt_clobbers & ~site_clobbers) 141 if (tgt_clobbers & ~site_clobbers)
141 return len; /* target would clobber too much for this site */ 142 return len; /* target would clobber too much for this site */
142 if (len < 5) 143 if (len < 5)
143 return len; /* call too long for patch site */ 144 return len; /* call too long for patch site */
144 145
145 b.opcode = 0xe8; /* call */ 146 b->opcode = 0xe8; /* call */
146 b.delta = delta; 147 b->delta = delta;
147 BUILD_BUG_ON(sizeof(b) != 5); 148 BUILD_BUG_ON(sizeof(*b) != 5);
148 text_poke(call, (unsigned char *)&b, 5);
149 149
150 return 5; 150 return 5;
151} 151}
152 152
153unsigned paravirt_patch_jmp(void *target, void *site, unsigned len) 153unsigned paravirt_patch_jmp(const void *target, void *insnbuf,
154 unsigned long addr, unsigned len)
154{ 155{
155 unsigned char *jmp = site; 156 struct branch *b = insnbuf;
156 unsigned long delta = (unsigned long)target - (unsigned long)(jmp+5); 157 unsigned long delta = (unsigned long)target - (addr+5);
157 struct branch b;
158 158
159 if (len < 5) 159 if (len < 5)
160 return len; /* call too long for patch site */ 160 return len; /* call too long for patch site */
161 161
162 b.opcode = 0xe9; /* jmp */ 162 b->opcode = 0xe9; /* jmp */
163 b.delta = delta; 163 b->delta = delta;
164 text_poke(jmp, (unsigned char *)&b, 5);
165 164
166 return 5; 165 return 5;
167} 166}
168 167
169unsigned paravirt_patch_default(u8 type, u16 clobbers, void *site, unsigned len) 168unsigned paravirt_patch_default(u8 type, u16 clobbers, void *insnbuf,
169 unsigned long addr, unsigned len)
170{ 170{
171 void *opfunc = *((void **)&paravirt_ops + type); 171 void *opfunc = *((void **)&paravirt_ops + type);
172 unsigned ret; 172 unsigned ret;
173 173
174 if (opfunc == NULL) 174 if (opfunc == NULL)
175 /* If there's no function, patch it with a ud2a (BUG) */ 175 /* If there's no function, patch it with a ud2a (BUG) */
176 ret = paravirt_patch_insns(site, len, start_ud2a, end_ud2a); 176 ret = paravirt_patch_insns(insnbuf, len, start_ud2a, end_ud2a);
177 else if (opfunc == paravirt_nop) 177 else if (opfunc == paravirt_nop)
178 /* If the operation is a nop, then nop the callsite */ 178 /* If the operation is a nop, then nop the callsite */
179 ret = paravirt_patch_nop(); 179 ret = paravirt_patch_nop();
180 else if (type == PARAVIRT_PATCH(iret) || 180 else if (type == PARAVIRT_PATCH(iret) ||
181 type == PARAVIRT_PATCH(irq_enable_sysexit)) 181 type == PARAVIRT_PATCH(irq_enable_sysexit))
182 /* If operation requires a jmp, then jmp */ 182 /* If operation requires a jmp, then jmp */
183 ret = paravirt_patch_jmp(opfunc, site, len); 183 ret = paravirt_patch_jmp(opfunc, insnbuf, addr, len);
184 else 184 else
185 /* Otherwise call the function; assume target could 185 /* Otherwise call the function; assume target could
186 clobber any caller-save reg */ 186 clobber any caller-save reg */
187 ret = paravirt_patch_call(opfunc, CLBR_ANY, 187 ret = paravirt_patch_call(insnbuf, opfunc, CLBR_ANY,
188 site, clobbers, len); 188 addr, clobbers, len);
189 189
190 return ret; 190 return ret;
191} 191}
192 192
193unsigned paravirt_patch_insns(void *site, unsigned len, 193unsigned paravirt_patch_insns(void *insnbuf, unsigned len,
194 const char *start, const char *end) 194 const char *start, const char *end)
195{ 195{
196 unsigned insn_len = end - start; 196 unsigned insn_len = end - start;
@@ -198,7 +198,7 @@ unsigned paravirt_patch_insns(void *site, unsigned len,
198 if (insn_len > len || start == NULL) 198 if (insn_len > len || start == NULL)
199 insn_len = len; 199 insn_len = len;
200 else 200 else
201 memcpy(site, start, insn_len); 201 memcpy(insnbuf, start, insn_len);
202 202
203 return insn_len; 203 return insn_len;
204} 204}
diff --git a/arch/i386/kernel/vmi.c b/arch/i386/kernel/vmi.c
index 72042bb7ec94..18673e0f193b 100644
--- a/arch/i386/kernel/vmi.c
+++ b/arch/i386/kernel/vmi.c
@@ -87,12 +87,14 @@ struct vmi_timer_ops vmi_timer_ops;
87#define IRQ_PATCH_INT_MASK 0 87#define IRQ_PATCH_INT_MASK 0
88#define IRQ_PATCH_DISABLE 5 88#define IRQ_PATCH_DISABLE 5
89 89
90static inline void patch_offset(unsigned char *eip, unsigned char *dest) 90static inline void patch_offset(void *insnbuf,
91 unsigned long eip, unsigned long dest)
91{ 92{
92 *(unsigned long *)(eip+1) = dest-eip-5; 93 *(unsigned long *)(insnbuf+1) = dest-eip-5;
93} 94}
94 95
95static unsigned patch_internal(int call, unsigned len, void *insns) 96static unsigned patch_internal(int call, unsigned len, void *insnbuf,
97 unsigned long eip)
96{ 98{
97 u64 reloc; 99 u64 reloc;
98 struct vmi_relocation_info *const rel = (struct vmi_relocation_info *)&reloc; 100 struct vmi_relocation_info *const rel = (struct vmi_relocation_info *)&reloc;
@@ -100,14 +102,14 @@ static unsigned patch_internal(int call, unsigned len, void *insns)
100 switch(rel->type) { 102 switch(rel->type) {
101 case VMI_RELOCATION_CALL_REL: 103 case VMI_RELOCATION_CALL_REL:
102 BUG_ON(len < 5); 104 BUG_ON(len < 5);
103 *(char *)insns = MNEM_CALL; 105 *(char *)insnbuf = MNEM_CALL;
104 patch_offset(insns, rel->eip); 106 patch_offset(insnbuf, eip, (unsigned long)rel->eip);
105 return 5; 107 return 5;
106 108
107 case VMI_RELOCATION_JUMP_REL: 109 case VMI_RELOCATION_JUMP_REL:
108 BUG_ON(len < 5); 110 BUG_ON(len < 5);
109 *(char *)insns = MNEM_JMP; 111 *(char *)insnbuf = MNEM_JMP;
110 patch_offset(insns, rel->eip); 112 patch_offset(insnbuf, eip, (unsigned long)rel->eip);
111 return 5; 113 return 5;
112 114
113 case VMI_RELOCATION_NOP: 115 case VMI_RELOCATION_NOP:
@@ -128,21 +130,26 @@ static unsigned patch_internal(int call, unsigned len, void *insns)
128 * Apply patch if appropriate, return length of new instruction 130 * Apply patch if appropriate, return length of new instruction
129 * sequence. The callee does nop padding for us. 131 * sequence. The callee does nop padding for us.
130 */ 132 */
131static unsigned vmi_patch(u8 type, u16 clobbers, void *insns, unsigned len) 133static unsigned vmi_patch(u8 type, u16 clobbers, void *insns,
134 unsigned long eip, unsigned len)
132{ 135{
133 switch (type) { 136 switch (type) {
134 case PARAVIRT_PATCH(irq_disable): 137 case PARAVIRT_PATCH(irq_disable):
135 return patch_internal(VMI_CALL_DisableInterrupts, len, insns); 138 return patch_internal(VMI_CALL_DisableInterrupts, len,
139 insns, eip);
136 case PARAVIRT_PATCH(irq_enable): 140 case PARAVIRT_PATCH(irq_enable):
137 return patch_internal(VMI_CALL_EnableInterrupts, len, insns); 141 return patch_internal(VMI_CALL_EnableInterrupts, len,
142 insns, eip);
138 case PARAVIRT_PATCH(restore_fl): 143 case PARAVIRT_PATCH(restore_fl):
139 return patch_internal(VMI_CALL_SetInterruptMask, len, insns); 144 return patch_internal(VMI_CALL_SetInterruptMask, len,
145 insns, eip);
140 case PARAVIRT_PATCH(save_fl): 146 case PARAVIRT_PATCH(save_fl):
141 return patch_internal(VMI_CALL_GetInterruptMask, len, insns); 147 return patch_internal(VMI_CALL_GetInterruptMask, len,
148 insns, eip);
142 case PARAVIRT_PATCH(iret): 149 case PARAVIRT_PATCH(iret):
143 return patch_internal(VMI_CALL_IRET, len, insns); 150 return patch_internal(VMI_CALL_IRET, len, insns, eip);
144 case PARAVIRT_PATCH(irq_enable_sysexit): 151 case PARAVIRT_PATCH(irq_enable_sysexit):
145 return patch_internal(VMI_CALL_SYSEXIT, len, insns); 152 return patch_internal(VMI_CALL_SYSEXIT, len, insns, eip);
146 default: 153 default:
147 break; 154 break;
148 } 155 }
diff --git a/arch/i386/xen/enlighten.c b/arch/i386/xen/enlighten.c
index 9a8c1181c001..f0c37511d8da 100644
--- a/arch/i386/xen/enlighten.c
+++ b/arch/i386/xen/enlighten.c
@@ -842,7 +842,8 @@ void __init xen_setup_vcpu_info_placement(void)
842 } 842 }
843} 843}
844 844
845static unsigned xen_patch(u8 type, u16 clobbers, void *insns, unsigned len) 845static unsigned xen_patch(u8 type, u16 clobbers, void *insnbuf,
846 unsigned long addr, unsigned len)
846{ 847{
847 char *start, *end, *reloc; 848 char *start, *end, *reloc;
848 unsigned ret; 849 unsigned ret;
@@ -869,7 +870,7 @@ static unsigned xen_patch(u8 type, u16 clobbers, void *insns, unsigned len)
869 if (start == NULL || (end-start) > len) 870 if (start == NULL || (end-start) > len)
870 goto default_patch; 871 goto default_patch;
871 872
872 ret = paravirt_patch_insns(insns, len, start, end); 873 ret = paravirt_patch_insns(insnbuf, len, start, end);
873 874
874 /* Note: because reloc is assigned from something that 875 /* Note: because reloc is assigned from something that
875 appears to be an array, gcc assumes it's non-null, 876 appears to be an array, gcc assumes it's non-null,
@@ -877,8 +878,8 @@ static unsigned xen_patch(u8 type, u16 clobbers, void *insns, unsigned len)
877 end. */ 878 end. */
878 if (reloc > start && reloc < end) { 879 if (reloc > start && reloc < end) {
879 int reloc_off = reloc - start; 880 int reloc_off = reloc - start;
880 long *relocp = (long *)(insns + reloc_off); 881 long *relocp = (long *)(insnbuf + reloc_off);
881 long delta = start - (char *)insns; 882 long delta = start - (char *)addr;
882 883
883 *relocp += delta; 884 *relocp += delta;
884 } 885 }
@@ -886,7 +887,8 @@ static unsigned xen_patch(u8 type, u16 clobbers, void *insns, unsigned len)
886 887
887 default_patch: 888 default_patch:
888 default: 889 default:
889 ret = paravirt_patch_default(type, clobbers, insns, len); 890 ret = paravirt_patch_default(type, clobbers, insnbuf,
891 addr, len);
890 break; 892 break;
891 } 893 }
892 894