Skip to content

Commit 270a69c

Browse files
Peter Zijlstrabp3tk0v
authored andcommitted
x86/alternative: Support relocations in alternatives
A little while ago someone (Kirill) ran into the whole 'alternatives don't do relocations nonsense' again and I got annoyed enough to actually look at the code. Since the whole alternative machinery already fully decodes the instructions it is simple enough to adjust immediates and displacement when needed. Specifically, the immediates for IP modifying instructions (JMP, CALL, Jcc) and the displacement for RIP-relative instructions. [ bp: Massage comment some more and get rid of third loop in apply_relocation(). ] Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de> Link: https://lore.kernel.org/r/20230208171431.313857925@infradead.org
1 parent 6becb50 commit 270a69c

File tree

2 files changed

+173
-96
lines changed

2 files changed

+173
-96
lines changed

arch/x86/kernel/alternative.c

Lines changed: 172 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -134,71 +134,6 @@ extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
134134
extern s32 __smp_locks[], __smp_locks_end[];
135135
void text_poke_early(void *addr, const void *opcode, size_t len);
136136

137-
/*
138-
* Are we looking at a near JMP with a 1 or 4-byte displacement.
139-
*/
140-
static inline bool is_jmp(const u8 opcode)
141-
{
142-
return opcode == 0xeb || opcode == 0xe9;
143-
}
144-
145-
static void __init_or_module
146-
recompute_jump(struct alt_instr *a, u8 *orig_insn, u8 *repl_insn, u8 *insn_buff)
147-
{
148-
u8 *next_rip, *tgt_rip;
149-
s32 n_dspl, o_dspl;
150-
int repl_len;
151-
152-
if (a->replacementlen != 5)
153-
return;
154-
155-
o_dspl = *(s32 *)(insn_buff + 1);
156-
157-
/* next_rip of the replacement JMP */
158-
next_rip = repl_insn + a->replacementlen;
159-
/* target rip of the replacement JMP */
160-
tgt_rip = next_rip + o_dspl;
161-
n_dspl = tgt_rip - orig_insn;
162-
163-
DPRINTK(ALT, "target RIP: %px, new_displ: 0x%x", tgt_rip, n_dspl);
164-
165-
if (tgt_rip - orig_insn >= 0) {
166-
if (n_dspl - 2 <= 127)
167-
goto two_byte_jmp;
168-
else
169-
goto five_byte_jmp;
170-
/* negative offset */
171-
} else {
172-
if (((n_dspl - 2) & 0xff) == (n_dspl - 2))
173-
goto two_byte_jmp;
174-
else
175-
goto five_byte_jmp;
176-
}
177-
178-
two_byte_jmp:
179-
n_dspl -= 2;
180-
181-
insn_buff[0] = 0xeb;
182-
insn_buff[1] = (s8)n_dspl;
183-
add_nops(insn_buff + 2, 3);
184-
185-
repl_len = 2;
186-
goto done;
187-
188-
five_byte_jmp:
189-
n_dspl -= 5;
190-
191-
insn_buff[0] = 0xe9;
192-
*(s32 *)&insn_buff[1] = n_dspl;
193-
194-
repl_len = 5;
195-
196-
done:
197-
198-
DPRINTK(ALT, "final displ: 0x%08x, JMP 0x%lx",
199-
n_dspl, (unsigned long)orig_insn + n_dspl + repl_len);
200-
}
201-
202137
/*
203138
* optimize_nops_range() - Optimize a sequence of single byte NOPs (0x90)
204139
*
@@ -265,6 +200,139 @@ static void __init_or_module noinline optimize_nops(u8 *instr, size_t len)
265200
}
266201
}
267202

203+
/*
204+
* In this context, "source" is where the instructions are placed in the
205+
* section .altinstr_replacement, for example during kernel build by the
206+
* toolchain.
207+
* "Destination" is where the instructions are being patched in by this
208+
* machinery.
209+
*
210+
* The source offset is:
211+
*
212+
* src_imm = target - src_next_ip (1)
213+
*
214+
* and the target offset is:
215+
*
216+
* dst_imm = target - dst_next_ip (2)
217+
*
218+
* so rework (1) as an expression for target like:
219+
*
220+
* target = src_imm + src_next_ip (1a)
221+
*
222+
* and substitute in (2) to get:
223+
*
224+
* dst_imm = (src_imm + src_next_ip) - dst_next_ip (3)
225+
*
226+
* Now, since the instruction stream is 'identical' at src and dst (it
227+
* is being copied after all) it can be stated that:
228+
*
229+
* src_next_ip = src + ip_offset
230+
* dst_next_ip = dst + ip_offset (4)
231+
*
232+
* Substitute (4) in (3) and observe ip_offset being cancelled out to
233+
* obtain:
234+
*
235+
* dst_imm = src_imm + (src + ip_offset) - (dst + ip_offset)
236+
* = src_imm + src - dst + ip_offset - ip_offset
237+
* = src_imm + src - dst (5)
238+
*
239+
* IOW, only the relative displacement of the code block matters.
240+
*/
241+
242+
#define apply_reloc_n(n_, p_, d_) \
243+
do { \
244+
s32 v = *(s##n_ *)(p_); \
245+
v += (d_); \
246+
BUG_ON((v >> 31) != (v >> (n_-1))); \
247+
*(s##n_ *)(p_) = (s##n_)v; \
248+
} while (0)
249+
250+
251+
static __always_inline
252+
void apply_reloc(int n, void *ptr, uintptr_t diff)
253+
{
254+
switch (n) {
255+
case 1: apply_reloc_n(8, ptr, diff); break;
256+
case 2: apply_reloc_n(16, ptr, diff); break;
257+
case 4: apply_reloc_n(32, ptr, diff); break;
258+
default: BUG();
259+
}
260+
}
261+
262+
static __always_inline
263+
bool need_reloc(unsigned long offset, u8 *src, size_t src_len)
264+
{
265+
u8 *target = src + offset;
266+
/*
267+
* If the target is inside the patched block, it's relative to the
268+
* block itself and does not need relocation.
269+
*/
270+
return (target < src || target > src + src_len);
271+
}
272+
273+
static void __init_or_module noinline
274+
apply_relocation(u8 *buf, size_t len, u8 *dest, u8 *src, size_t src_len)
275+
{
276+
for (int next, i = 0; i < len; i = next) {
277+
struct insn insn;
278+
279+
if (WARN_ON_ONCE(insn_decode_kernel(&insn, &buf[i])))
280+
return;
281+
282+
next = i + insn.length;
283+
284+
switch (insn.opcode.bytes[0]) {
285+
case 0x0f:
286+
if (insn.opcode.bytes[1] < 0x80 ||
287+
insn.opcode.bytes[1] > 0x8f)
288+
break;
289+
290+
fallthrough; /* Jcc.d32 */
291+
case 0x70 ... 0x7f: /* Jcc.d8 */
292+
case JMP8_INSN_OPCODE:
293+
case JMP32_INSN_OPCODE:
294+
case CALL_INSN_OPCODE:
295+
if (need_reloc(next + insn.immediate.value, src, src_len)) {
296+
apply_reloc(insn.immediate.nbytes,
297+
buf + i + insn_offset_immediate(&insn),
298+
src - dest);
299+
}
300+
301+
/*
302+
* Where possible, convert JMP.d32 into JMP.d8.
303+
*/
304+
if (insn.opcode.bytes[0] == JMP32_INSN_OPCODE) {
305+
s32 imm = insn.immediate.value;
306+
imm += src - dest;
307+
imm += JMP32_INSN_SIZE - JMP8_INSN_SIZE;
308+
if ((imm >> 31) == (imm >> 7)) {
309+
buf[i+0] = JMP8_INSN_OPCODE;
310+
buf[i+1] = (s8)imm;
311+
312+
memset(&buf[i+2], INT3_INSN_OPCODE, insn.length - 2);
313+
}
314+
}
315+
break;
316+
}
317+
318+
if (insn_rip_relative(&insn)) {
319+
if (need_reloc(next + insn.displacement.value, src, src_len)) {
320+
apply_reloc(insn.displacement.nbytes,
321+
buf + i + insn_offset_displacement(&insn),
322+
src - dest);
323+
}
324+
}
325+
326+
327+
/*
328+
* See if this and any potentially following NOPs can be
329+
* optimized.
330+
*/
331+
if (insn.length == 1 && insn.opcode.bytes[0] == 0x90)
332+
next = i + optimize_nops_range(buf, len, i);
333+
}
334+
}
335+
268336
/*
269337
* Replace instructions with better alternatives for this CPU type. This runs
270338
* before SMP is initialized to avoid SMP problems with self modifying code.
@@ -306,8 +374,10 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
306374
* - feature not present but ALT_FLAG_NOT is set to mean,
307375
* patch if feature is *NOT* present.
308376
*/
309-
if (!boot_cpu_has(a->cpuid) == !(a->flags & ALT_FLAG_NOT))
310-
goto next;
377+
if (!boot_cpu_has(a->cpuid) == !(a->flags & ALT_FLAG_NOT)) {
378+
optimize_nops(instr, a->instrlen);
379+
continue;
380+
}
311381

312382
DPRINTK(ALT, "feat: %s%d*32+%d, old: (%pS (%px) len: %d), repl: (%px, len: %d)",
313383
(a->flags & ALT_FLAG_NOT) ? "!" : "",
@@ -316,37 +386,19 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
316386
instr, instr, a->instrlen,
317387
replacement, a->replacementlen);
318388

319-
DUMP_BYTES(ALT, instr, a->instrlen, "%px: old_insn: ", instr);
320-
DUMP_BYTES(ALT, replacement, a->replacementlen, "%px: rpl_insn: ", replacement);
321-
322389
memcpy(insn_buff, replacement, a->replacementlen);
323390
insn_buff_sz = a->replacementlen;
324391

325-
/*
326-
* 0xe8 is a relative jump; fix the offset.
327-
*
328-
* Instruction length is checked before the opcode to avoid
329-
* accessing uninitialized bytes for zero-length replacements.
330-
*/
331-
if (a->replacementlen == 5 && *insn_buff == 0xe8) {
332-
*(s32 *)(insn_buff + 1) += replacement - instr;
333-
DPRINTK(ALT, "Fix CALL offset: 0x%x, CALL 0x%lx",
334-
*(s32 *)(insn_buff + 1),
335-
(unsigned long)instr + *(s32 *)(insn_buff + 1) + 5);
336-
}
337-
338-
if (a->replacementlen && is_jmp(replacement[0]))
339-
recompute_jump(a, instr, replacement, insn_buff);
340-
341392
for (; insn_buff_sz < a->instrlen; insn_buff_sz++)
342393
insn_buff[insn_buff_sz] = 0x90;
343394

395+
apply_relocation(insn_buff, a->instrlen, instr, replacement, a->replacementlen);
396+
397+
DUMP_BYTES(ALT, instr, a->instrlen, "%px: old_insn: ", instr);
398+
DUMP_BYTES(ALT, replacement, a->replacementlen, "%px: rpl_insn: ", replacement);
344399
DUMP_BYTES(ALT, insn_buff, insn_buff_sz, "%px: final_insn: ", instr);
345400

346401
text_poke_early(instr, insn_buff, insn_buff_sz);
347-
348-
next:
349-
optimize_nops(instr, a->instrlen);
350402
}
351403
}
352404

@@ -1344,6 +1396,35 @@ static noinline void __init int3_selftest(void)
13441396
unregister_die_notifier(&int3_exception_nb);
13451397
}
13461398

1399+
static __initdata int __alt_reloc_selftest_addr;
1400+
1401+
__visible noinline void __init __alt_reloc_selftest(void *arg)
1402+
{
1403+
WARN_ON(arg != &__alt_reloc_selftest_addr);
1404+
}
1405+
1406+
static noinline void __init alt_reloc_selftest(void)
1407+
{
1408+
/*
1409+
* Tests apply_relocation().
1410+
*
1411+
* This has a relative immediate (CALL) in a place other than the first
1412+
* instruction and additionally on x86_64 we get a RIP-relative LEA:
1413+
*
1414+
* lea 0x0(%rip),%rdi # 5d0: R_X86_64_PC32 .init.data+0x5566c
1415+
* call +0 # 5d5: R_X86_64_PLT32 __alt_reloc_selftest-0x4
1416+
*
1417+
* Getting this wrong will either crash and burn or tickle the WARN
1418+
* above.
1419+
*/
1420+
asm_inline volatile (
1421+
ALTERNATIVE("", "lea %[mem], %%" _ASM_ARG1 "; call __alt_reloc_selftest;", X86_FEATURE_ALWAYS)
1422+
: /* output */
1423+
: [mem] "m" (__alt_reloc_selftest_addr)
1424+
: _ASM_ARG1
1425+
);
1426+
}
1427+
13471428
void __init alternative_instructions(void)
13481429
{
13491430
int3_selftest();
@@ -1431,6 +1512,8 @@ void __init alternative_instructions(void)
14311512

14321513
restart_nmi();
14331514
alternatives_patched = 1;
1515+
1516+
alt_reloc_selftest();
14341517
}
14351518

14361519
/**

tools/objtool/arch/x86/special.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,7 @@ bool arch_support_alt_relocation(struct special_alt *special_alt,
4242
struct instruction *insn,
4343
struct reloc *reloc)
4444
{
45-
/*
46-
* The x86 alternatives code adjusts the offsets only when it
47-
* encounters a branch instruction at the very beginning of the
48-
* replacement group.
49-
*/
50-
return insn->offset == special_alt->new_off &&
51-
(insn->type == INSN_CALL || is_jump(insn));
45+
return true;
5246
}
5347

5448
/*

0 commit comments

Comments
 (0)