From 7db2e9790442d073d25fec220d88fb2f85e4683f Mon Sep 17 00:00:00 2001 From: Juergen Gross Date: Wed, 7 May 2025 12:47:22 +0200 Subject: [PATCH] x86/alternative: save original code before replacing it In case of ALT_FLAG_DIRECT_CALL being set for an alternative replacement, the patching needs to look at the original instruction to find the target address of the direct call to be patched in. In case of nested ALTERNATIVEs this limits the use of ALT_FLAG_DIRECT_CALL to either the first replacement, or to be mutually exclusive with all previous replacements. Otherwise the original code could have been overwritten already resulting in a BUG(), due to ALT_FLAG_DIRECT_CALL handling not finding the expected indirect call instruction. Avoid this problem by saving the original code before replacing it. As this is the only case where the original code is required to be analyzed, special case the copy to happen only if the original code has the length of an indirect call (6 bytes). This minimizes complexity and stack usage. Signed-off-by: Juergen Gross --- arch/x86/kernel/alternative.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index bf82c6f7d690..8d6d3a4fc4ab 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -387,6 +387,12 @@ EXPORT_SYMBOL(BUG_func); #define CALL_RIP_REL_OPCODE 0xff #define CALL_RIP_REL_MODRM 0x15 +#define CALL_RIP_INSTR_LEN 6 + +static inline u8 * instr_va(struct alt_instr *i) +{ + return (u8 *)&i->instr_offset + i->instr_offset; +} /* * Rewrite the "call BUG_func" replacement to point to the target of the @@ -402,7 +408,7 @@ static int alt_replace_call(u8 *instr, u8 *insn_buff, struct alt_instr *a) BUG(); } - if (a->instrlen != 6 || + if (a->instrlen != CALL_RIP_INSTR_LEN || instr[0] != CALL_RIP_REL_OPCODE || instr[1] != CALL_RIP_REL_MODRM) { pr_err("ALT_FLAG_DIRECT_CALL set for unrecognized indirect call\n"); @@ -414,7 +420,7 @@ static int alt_replace_call(u8 *instr, u8 *insn_buff, struct alt_instr *a) #ifdef CONFIG_X86_64 /* ff 15 00 00 00 00 call *0x0(%rip) */ /* target address is stored at "next instruction + disp". */ - target = *(void **)(instr + a->instrlen + disp); + target = *(void **)(instr_va(a) + a->instrlen + disp); #else /* ff 15 00 00 00 00 call *0x0 */ /* target address is stored at disp. */ @@ -432,11 +438,6 @@ static int alt_replace_call(u8 *instr, u8 *insn_buff, struct alt_instr *a) return 5; } -static inline u8 * instr_va(struct alt_instr *i) -{ - return (u8 *)&i->instr_offset + i->instr_offset; -} - /* * Replace instructions with better alternatives for this CPU type. This runs * before SMP is initialized to avoid SMP problems with self modifying code. @@ -451,7 +452,8 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start, struct alt_instr *end) { u8 insn_buff[MAX_PATCH_LEN]; - u8 *instr, *replacement; + u8 old_insn[CALL_RIP_INSTR_LEN]; + u8 *instr, *replacement, *old_va = NULL; struct alt_instr *a, *b; DPRINTK(ALT, "alt table %px, -> %px", start, end); @@ -513,11 +515,21 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start, instr, instr, a->instrlen, replacement, a->replacementlen, a->flags); + /* + * Remember original code if it could be an indirect call. + * This enables ALT_FLAG_DIRECT_CALL handling with nested + * alternatives even if the original code has been modified + * already. + */ + if (old_va != instr && a->instrlen == CALL_RIP_INSTR_LEN) { + old_va = instr; + memcpy(old_insn, instr, CALL_RIP_INSTR_LEN); + } memcpy(insn_buff, replacement, a->replacementlen); insn_buff_sz = a->replacementlen; if (a->flags & ALT_FLAG_DIRECT_CALL) { - insn_buff_sz = alt_replace_call(instr, insn_buff, a); + insn_buff_sz = alt_replace_call(old_insn, insn_buff, a); if (insn_buff_sz < 0) continue; } -- 2.43.0