@@ -134,71 +134,6 @@ extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
134134extern s32 __smp_locks [], __smp_locks_end [];
135135void 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+
13471428void __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/**
0 commit comments