- Teach the static_call patching infrastructure to handle conditional
tall calls properly which can be static calls too - Add proper struct alt_instr.flags which controls different aspects of insn patching behavior -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEzv7L6UO9uDPlPSfHEsHwGGHeVUoFAmPzkAcACgkQEsHwGGHe VUpA3A//bALDnLosUQe/m8CTcj1AU12Y59fGInoLl5xArM3liOhRYWj9yu8+2r5N j+89yjWoiaogu/9B18pV0+VnBrFUbALZmHxec0+4VAWyMqYuTbqN28Nj/2cZiHdP I/9mwGu40I/Ira021D132EcdoZI7O/6bFlh+kEoAqxc7rsqhD5KKRMlrTTdEPVjH aRbWIuzqDWNhbi7IwfgEBIPLiQZQKmIdH5hsFMD6yOMIdMRL6CwKmXVg2M1Zp8ta 5v2Aqgvu2nZYCIteP4GQck2AlUBlGR4ClGQQRII+U1o8c9dM0hfcIDgsbSYKvgrY ANm9MQJaF7MRomk9y4E0EHPZAJEMLKUgiQXMxWpER3O1GOKgZPlyzNSe0gRCiL6O NZWZ2cGtdhQMrko4EapE3GNryM1HoCY/QCuD1fCYwoc/pRBDhCxsSqjWUd8G/6wn s3S/mD0v3nmTrxHg8sWvqhKshsd7B9V0LSkTpHktz3soFIJGXTxbrtty0CIS61pM 4iUMYB9SjunoEmdwC7+gCN3sCiRpRqfmIybqXdsW3d37QI+FM5aSBPw51xULubfY Wsxo8SkH+IMYxXmfbQuUppsGZ+1QHzU08+MrlvNxGHUjS1aMnsrFF/fbfbbCnWvX 7hcyBPT0jxc9RPMNeKDm4ItapMMGxGdv6XiRmM8LiUtVG2fMaW4= =XUqC -----END PGP SIGNATURE----- Merge tag 'x86_alternatives_for_v6.3_rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip Pull x86 asm alternatives updates from Borislav Petkov: - Teach the static_call patching infrastructure to handle conditional tall calls properly which can be static calls too - Add proper struct alt_instr.flags which controls different aspects of insn patching behavior * tag 'x86_alternatives_for_v6.3_rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: x86/static_call: Add support for Jcc tail-calls x86/alternatives: Teach text_poke_bp() to patch Jcc.d32 instructions x86/alternatives: Introduce int3_emulate_jcc() x86/alternatives: Add alt_instr.flags
This commit is contained in:
commit
1adce1b944
|
@ -6,8 +6,10 @@
|
|||
#include <linux/stringify.h>
|
||||
#include <asm/asm.h>
|
||||
|
||||
#define ALTINSTR_FLAG_INV (1 << 15)
|
||||
#define ALT_NOT(feat) ((feat) | ALTINSTR_FLAG_INV)
|
||||
#define ALT_FLAGS_SHIFT 16
|
||||
|
||||
#define ALT_FLAG_NOT BIT(0)
|
||||
#define ALT_NOT(feature) ((ALT_FLAG_NOT << ALT_FLAGS_SHIFT) | (feature))
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
|
||||
|
@ -59,10 +61,27 @@
|
|||
".long 999b - .\n\t" \
|
||||
".popsection\n\t"
|
||||
|
||||
/*
|
||||
* The patching flags are part of the upper bits of the @ft_flags parameter when
|
||||
* specifying them. The split is currently like this:
|
||||
*
|
||||
* [31... flags ...16][15... CPUID feature bit ...0]
|
||||
*
|
||||
* but since this is all hidden in the macros argument being split, those fields can be
|
||||
* extended in the future to fit in a u64 or however the need arises.
|
||||
*/
|
||||
struct alt_instr {
|
||||
s32 instr_offset; /* original instruction */
|
||||
s32 repl_offset; /* offset to replacement instruction */
|
||||
u16 cpuid; /* cpuid bit set for replacement */
|
||||
|
||||
union {
|
||||
struct {
|
||||
u32 cpuid: 16; /* CPUID bit set for replacement */
|
||||
u32 flags: 16; /* patching control flags */
|
||||
};
|
||||
u32 ft_flags;
|
||||
};
|
||||
|
||||
u8 instrlen; /* length of original instruction */
|
||||
u8 replacementlen; /* length of new instruction */
|
||||
} __packed;
|
||||
|
@ -182,10 +201,10 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
" - (" alt_slen ")), 0x90\n" \
|
||||
alt_end_marker ":\n"
|
||||
|
||||
#define ALTINSTR_ENTRY(feature, num) \
|
||||
#define ALTINSTR_ENTRY(ft_flags, num) \
|
||||
" .long 661b - .\n" /* label */ \
|
||||
" .long " b_replacement(num)"f - .\n" /* new instruction */ \
|
||||
" .word " __stringify(feature) "\n" /* feature bit */ \
|
||||
" .4byte " __stringify(ft_flags) "\n" /* feature + flags */ \
|
||||
" .byte " alt_total_slen "\n" /* source len */ \
|
||||
" .byte " alt_rlen(num) "\n" /* replacement len */
|
||||
|
||||
|
@ -194,20 +213,20 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
b_replacement(num)":\n\t" newinstr "\n" e_replacement(num) ":\n"
|
||||
|
||||
/* alternative assembly primitive: */
|
||||
#define ALTERNATIVE(oldinstr, newinstr, feature) \
|
||||
#define ALTERNATIVE(oldinstr, newinstr, ft_flags) \
|
||||
OLDINSTR(oldinstr, 1) \
|
||||
".pushsection .altinstructions,\"a\"\n" \
|
||||
ALTINSTR_ENTRY(feature, 1) \
|
||||
ALTINSTR_ENTRY(ft_flags, 1) \
|
||||
".popsection\n" \
|
||||
".pushsection .altinstr_replacement, \"ax\"\n" \
|
||||
ALTINSTR_REPLACEMENT(newinstr, 1) \
|
||||
".popsection\n"
|
||||
|
||||
#define ALTERNATIVE_2(oldinstr, newinstr1, feature1, newinstr2, feature2)\
|
||||
#define ALTERNATIVE_2(oldinstr, newinstr1, ft_flags1, newinstr2, ft_flags2) \
|
||||
OLDINSTR_2(oldinstr, 1, 2) \
|
||||
".pushsection .altinstructions,\"a\"\n" \
|
||||
ALTINSTR_ENTRY(feature1, 1) \
|
||||
ALTINSTR_ENTRY(feature2, 2) \
|
||||
ALTINSTR_ENTRY(ft_flags1, 1) \
|
||||
ALTINSTR_ENTRY(ft_flags2, 2) \
|
||||
".popsection\n" \
|
||||
".pushsection .altinstr_replacement, \"ax\"\n" \
|
||||
ALTINSTR_REPLACEMENT(newinstr1, 1) \
|
||||
|
@ -215,21 +234,22 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
".popsection\n"
|
||||
|
||||
/* If @feature is set, patch in @newinstr_yes, otherwise @newinstr_no. */
|
||||
#define ALTERNATIVE_TERNARY(oldinstr, feature, newinstr_yes, newinstr_no) \
|
||||
#define ALTERNATIVE_TERNARY(oldinstr, ft_flags, newinstr_yes, newinstr_no) \
|
||||
ALTERNATIVE_2(oldinstr, newinstr_no, X86_FEATURE_ALWAYS, \
|
||||
newinstr_yes, feature)
|
||||
newinstr_yes, ft_flags)
|
||||
|
||||
#define ALTERNATIVE_3(oldinsn, newinsn1, feat1, newinsn2, feat2, newinsn3, feat3) \
|
||||
OLDINSTR_3(oldinsn, 1, 2, 3) \
|
||||
".pushsection .altinstructions,\"a\"\n" \
|
||||
ALTINSTR_ENTRY(feat1, 1) \
|
||||
ALTINSTR_ENTRY(feat2, 2) \
|
||||
ALTINSTR_ENTRY(feat3, 3) \
|
||||
".popsection\n" \
|
||||
".pushsection .altinstr_replacement, \"ax\"\n" \
|
||||
ALTINSTR_REPLACEMENT(newinsn1, 1) \
|
||||
ALTINSTR_REPLACEMENT(newinsn2, 2) \
|
||||
ALTINSTR_REPLACEMENT(newinsn3, 3) \
|
||||
#define ALTERNATIVE_3(oldinsn, newinsn1, ft_flags1, newinsn2, ft_flags2, \
|
||||
newinsn3, ft_flags3) \
|
||||
OLDINSTR_3(oldinsn, 1, 2, 3) \
|
||||
".pushsection .altinstructions,\"a\"\n" \
|
||||
ALTINSTR_ENTRY(ft_flags1, 1) \
|
||||
ALTINSTR_ENTRY(ft_flags2, 2) \
|
||||
ALTINSTR_ENTRY(ft_flags3, 3) \
|
||||
".popsection\n" \
|
||||
".pushsection .altinstr_replacement, \"ax\"\n" \
|
||||
ALTINSTR_REPLACEMENT(newinsn1, 1) \
|
||||
ALTINSTR_REPLACEMENT(newinsn2, 2) \
|
||||
ALTINSTR_REPLACEMENT(newinsn3, 3) \
|
||||
".popsection\n"
|
||||
|
||||
/*
|
||||
|
@ -244,14 +264,14 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
* For non barrier like inlines please define new variants
|
||||
* without volatile and memory clobber.
|
||||
*/
|
||||
#define alternative(oldinstr, newinstr, feature) \
|
||||
asm_inline volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory")
|
||||
#define alternative(oldinstr, newinstr, ft_flags) \
|
||||
asm_inline volatile (ALTERNATIVE(oldinstr, newinstr, ft_flags) : : : "memory")
|
||||
|
||||
#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \
|
||||
asm_inline volatile(ALTERNATIVE_2(oldinstr, newinstr1, feature1, newinstr2, feature2) ::: "memory")
|
||||
#define alternative_2(oldinstr, newinstr1, ft_flags1, newinstr2, ft_flags2) \
|
||||
asm_inline volatile(ALTERNATIVE_2(oldinstr, newinstr1, ft_flags1, newinstr2, ft_flags2) ::: "memory")
|
||||
|
||||
#define alternative_ternary(oldinstr, feature, newinstr_yes, newinstr_no) \
|
||||
asm_inline volatile(ALTERNATIVE_TERNARY(oldinstr, feature, newinstr_yes, newinstr_no) ::: "memory")
|
||||
#define alternative_ternary(oldinstr, ft_flags, newinstr_yes, newinstr_no) \
|
||||
asm_inline volatile(ALTERNATIVE_TERNARY(oldinstr, ft_flags, newinstr_yes, newinstr_no) ::: "memory")
|
||||
|
||||
/*
|
||||
* Alternative inline assembly with input.
|
||||
|
@ -261,8 +281,8 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
* Argument numbers start with 1.
|
||||
* Leaving an unused argument 0 to keep API compatibility.
|
||||
*/
|
||||
#define alternative_input(oldinstr, newinstr, feature, input...) \
|
||||
asm_inline volatile (ALTERNATIVE(oldinstr, newinstr, feature) \
|
||||
#define alternative_input(oldinstr, newinstr, ft_flags, input...) \
|
||||
asm_inline volatile (ALTERNATIVE(oldinstr, newinstr, ft_flags) \
|
||||
: : "i" (0), ## input)
|
||||
|
||||
/*
|
||||
|
@ -273,20 +293,20 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
* Otherwise, if CPU has feature1, newinstr1 is used.
|
||||
* Otherwise, oldinstr is used.
|
||||
*/
|
||||
#define alternative_input_2(oldinstr, newinstr1, feature1, newinstr2, \
|
||||
feature2, input...) \
|
||||
asm_inline volatile(ALTERNATIVE_2(oldinstr, newinstr1, feature1, \
|
||||
newinstr2, feature2) \
|
||||
#define alternative_input_2(oldinstr, newinstr1, ft_flags1, newinstr2, \
|
||||
ft_flags2, input...) \
|
||||
asm_inline volatile(ALTERNATIVE_2(oldinstr, newinstr1, ft_flags1, \
|
||||
newinstr2, ft_flags2) \
|
||||
: : "i" (0), ## input)
|
||||
|
||||
/* Like alternative_input, but with a single output argument */
|
||||
#define alternative_io(oldinstr, newinstr, feature, output, input...) \
|
||||
asm_inline volatile (ALTERNATIVE(oldinstr, newinstr, feature) \
|
||||
#define alternative_io(oldinstr, newinstr, ft_flags, output, input...) \
|
||||
asm_inline volatile (ALTERNATIVE(oldinstr, newinstr, ft_flags) \
|
||||
: output : "i" (0), ## input)
|
||||
|
||||
/* Like alternative_io, but for replacing a direct call with another one. */
|
||||
#define alternative_call(oldfunc, newfunc, feature, output, input...) \
|
||||
asm_inline volatile (ALTERNATIVE("call %P[old]", "call %P[new]", feature) \
|
||||
#define alternative_call(oldfunc, newfunc, ft_flags, output, input...) \
|
||||
asm_inline volatile (ALTERNATIVE("call %P[old]", "call %P[new]", ft_flags) \
|
||||
: output : [old] "i" (oldfunc), [new] "i" (newfunc), ## input)
|
||||
|
||||
/*
|
||||
|
@ -295,10 +315,10 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
* Otherwise, if CPU has feature1, function1 is used.
|
||||
* Otherwise, old function is used.
|
||||
*/
|
||||
#define alternative_call_2(oldfunc, newfunc1, feature1, newfunc2, feature2, \
|
||||
#define alternative_call_2(oldfunc, newfunc1, ft_flags1, newfunc2, ft_flags2, \
|
||||
output, input...) \
|
||||
asm_inline volatile (ALTERNATIVE_2("call %P[old]", "call %P[new1]", feature1,\
|
||||
"call %P[new2]", feature2) \
|
||||
asm_inline volatile (ALTERNATIVE_2("call %P[old]", "call %P[new1]", ft_flags1,\
|
||||
"call %P[new2]", ft_flags2) \
|
||||
: output, ASM_CALL_CONSTRAINT \
|
||||
: [old] "i" (oldfunc), [new1] "i" (newfunc1), \
|
||||
[new2] "i" (newfunc2), ## input)
|
||||
|
@ -347,10 +367,10 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
* enough information for the alternatives patching code to patch an
|
||||
* instruction. See apply_alternatives().
|
||||
*/
|
||||
.macro altinstruction_entry orig alt feature orig_len alt_len
|
||||
.macro altinstr_entry orig alt ft_flags orig_len alt_len
|
||||
.long \orig - .
|
||||
.long \alt - .
|
||||
.word \feature
|
||||
.4byte \ft_flags
|
||||
.byte \orig_len
|
||||
.byte \alt_len
|
||||
.endm
|
||||
|
@ -361,7 +381,7 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
* @newinstr. ".skip" directive takes care of proper instruction padding
|
||||
* in case @newinstr is longer than @oldinstr.
|
||||
*/
|
||||
.macro ALTERNATIVE oldinstr, newinstr, feature
|
||||
.macro ALTERNATIVE oldinstr, newinstr, ft_flags
|
||||
140:
|
||||
\oldinstr
|
||||
141:
|
||||
|
@ -369,7 +389,7 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
142:
|
||||
|
||||
.pushsection .altinstructions,"a"
|
||||
altinstruction_entry 140b,143f,\feature,142b-140b,144f-143f
|
||||
altinstr_entry 140b,143f,\ft_flags,142b-140b,144f-143f
|
||||
.popsection
|
||||
|
||||
.pushsection .altinstr_replacement,"ax"
|
||||
|
@ -399,7 +419,7 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
* has @feature1, it replaces @oldinstr with @newinstr1. If CPU has
|
||||
* @feature2, it replaces @oldinstr with @feature2.
|
||||
*/
|
||||
.macro ALTERNATIVE_2 oldinstr, newinstr1, feature1, newinstr2, feature2
|
||||
.macro ALTERNATIVE_2 oldinstr, newinstr1, ft_flags1, newinstr2, ft_flags2
|
||||
140:
|
||||
\oldinstr
|
||||
141:
|
||||
|
@ -408,8 +428,8 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
142:
|
||||
|
||||
.pushsection .altinstructions,"a"
|
||||
altinstruction_entry 140b,143f,\feature1,142b-140b,144f-143f
|
||||
altinstruction_entry 140b,144f,\feature2,142b-140b,145f-144f
|
||||
altinstr_entry 140b,143f,\ft_flags1,142b-140b,144f-143f
|
||||
altinstr_entry 140b,144f,\ft_flags2,142b-140b,145f-144f
|
||||
.popsection
|
||||
|
||||
.pushsection .altinstr_replacement,"ax"
|
||||
|
@ -421,7 +441,7 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
.popsection
|
||||
.endm
|
||||
|
||||
.macro ALTERNATIVE_3 oldinstr, newinstr1, feature1, newinstr2, feature2, newinstr3, feature3
|
||||
.macro ALTERNATIVE_3 oldinstr, newinstr1, ft_flags1, newinstr2, ft_flags2, newinstr3, ft_flags3
|
||||
140:
|
||||
\oldinstr
|
||||
141:
|
||||
|
@ -430,9 +450,9 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
142:
|
||||
|
||||
.pushsection .altinstructions,"a"
|
||||
altinstruction_entry 140b,143f,\feature1,142b-140b,144f-143f
|
||||
altinstruction_entry 140b,144f,\feature2,142b-140b,145f-144f
|
||||
altinstruction_entry 140b,145f,\feature3,142b-140b,146f-145f
|
||||
altinstr_entry 140b,143f,\ft_flags1,142b-140b,144f-143f
|
||||
altinstr_entry 140b,144f,\ft_flags2,142b-140b,145f-144f
|
||||
altinstr_entry 140b,145f,\ft_flags3,142b-140b,146f-145f
|
||||
.popsection
|
||||
|
||||
.pushsection .altinstr_replacement,"ax"
|
||||
|
@ -447,9 +467,9 @@ static inline int alternatives_text_reserved(void *start, void *end)
|
|||
.endm
|
||||
|
||||
/* If @feature is set, patch in @newinstr_yes, otherwise @newinstr_no. */
|
||||
#define ALTERNATIVE_TERNARY(oldinstr, feature, newinstr_yes, newinstr_no) \
|
||||
#define ALTERNATIVE_TERNARY(oldinstr, ft_flags, newinstr_yes, newinstr_no) \
|
||||
ALTERNATIVE_2 oldinstr, newinstr_no, X86_FEATURE_ALWAYS, \
|
||||
newinstr_yes, feature
|
||||
newinstr_yes, ft_flags
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
|
|
|
@ -184,6 +184,37 @@ void int3_emulate_ret(struct pt_regs *regs)
|
|||
unsigned long ip = int3_emulate_pop(regs);
|
||||
int3_emulate_jmp(regs, ip);
|
||||
}
|
||||
|
||||
static __always_inline
|
||||
void int3_emulate_jcc(struct pt_regs *regs, u8 cc, unsigned long ip, unsigned long disp)
|
||||
{
|
||||
static const unsigned long jcc_mask[6] = {
|
||||
[0] = X86_EFLAGS_OF,
|
||||
[1] = X86_EFLAGS_CF,
|
||||
[2] = X86_EFLAGS_ZF,
|
||||
[3] = X86_EFLAGS_CF | X86_EFLAGS_ZF,
|
||||
[4] = X86_EFLAGS_SF,
|
||||
[5] = X86_EFLAGS_PF,
|
||||
};
|
||||
|
||||
bool invert = cc & 1;
|
||||
bool match;
|
||||
|
||||
if (cc < 0xc) {
|
||||
match = regs->flags & jcc_mask[cc >> 1];
|
||||
} else {
|
||||
match = ((regs->flags & X86_EFLAGS_SF) >> X86_EFLAGS_SF_BIT) ^
|
||||
((regs->flags & X86_EFLAGS_OF) >> X86_EFLAGS_OF_BIT);
|
||||
if (cc >= 0xe)
|
||||
match = match || (regs->flags & X86_EFLAGS_ZF);
|
||||
}
|
||||
|
||||
if ((match && !invert) || (!match && invert))
|
||||
ip += disp;
|
||||
|
||||
int3_emulate_jmp(regs, ip);
|
||||
}
|
||||
|
||||
#endif /* !CONFIG_UML_X86 */
|
||||
|
||||
#endif /* _ASM_X86_TEXT_PATCHING_H */
|
||||
|
|
|
@ -282,27 +282,25 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
|
|||
*/
|
||||
for (a = start; a < end; a++) {
|
||||
int insn_buff_sz = 0;
|
||||
/* Mask away "NOT" flag bit for feature to test. */
|
||||
u16 feature = a->cpuid & ~ALTINSTR_FLAG_INV;
|
||||
|
||||
instr = (u8 *)&a->instr_offset + a->instr_offset;
|
||||
replacement = (u8 *)&a->repl_offset + a->repl_offset;
|
||||
BUG_ON(a->instrlen > sizeof(insn_buff));
|
||||
BUG_ON(feature >= (NCAPINTS + NBUGINTS) * 32);
|
||||
BUG_ON(a->cpuid >= (NCAPINTS + NBUGINTS) * 32);
|
||||
|
||||
/*
|
||||
* Patch if either:
|
||||
* - feature is present
|
||||
* - feature not present but ALTINSTR_FLAG_INV is set to mean,
|
||||
* - feature not present but ALT_FLAG_NOT is set to mean,
|
||||
* patch if feature is *NOT* present.
|
||||
*/
|
||||
if (!boot_cpu_has(feature) == !(a->cpuid & ALTINSTR_FLAG_INV))
|
||||
if (!boot_cpu_has(a->cpuid) == !(a->flags & ALT_FLAG_NOT))
|
||||
goto next;
|
||||
|
||||
DPRINTK("feat: %s%d*32+%d, old: (%pS (%px) len: %d), repl: (%px, len: %d)",
|
||||
(a->cpuid & ALTINSTR_FLAG_INV) ? "!" : "",
|
||||
feature >> 5,
|
||||
feature & 0x1f,
|
||||
(a->flags & ALT_FLAG_NOT) ? "!" : "",
|
||||
a->cpuid >> 5,
|
||||
a->cpuid & 0x1f,
|
||||
instr, instr, a->instrlen,
|
||||
replacement, a->replacementlen);
|
||||
|
||||
|
@ -340,6 +338,12 @@ next:
|
|||
}
|
||||
}
|
||||
|
||||
static inline bool is_jcc32(struct insn *insn)
|
||||
{
|
||||
/* Jcc.d32 second opcode byte is in the range: 0x80-0x8f */
|
||||
return insn->opcode.bytes[0] == 0x0f && (insn->opcode.bytes[1] & 0xf0) == 0x80;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_RETPOLINE) && defined(CONFIG_OBJTOOL)
|
||||
|
||||
/*
|
||||
|
@ -378,12 +382,6 @@ static int emit_indirect(int op, int reg, u8 *bytes)
|
|||
return i;
|
||||
}
|
||||
|
||||
static inline bool is_jcc32(struct insn *insn)
|
||||
{
|
||||
/* Jcc.d32 second opcode byte is in the range: 0x80-0x8f */
|
||||
return insn->opcode.bytes[0] == 0x0f && (insn->opcode.bytes[1] & 0xf0) == 0x80;
|
||||
}
|
||||
|
||||
static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8 *bytes)
|
||||
{
|
||||
u8 op = insn->opcode.bytes[0];
|
||||
|
@ -1772,6 +1770,11 @@ void text_poke_sync(void)
|
|||
on_each_cpu(do_sync_core, NULL, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: crazy scheme to allow patching Jcc.d32 but not increase the size of
|
||||
* this thing. When len == 6 everything is prefixed with 0x0f and we map
|
||||
* opcode to Jcc.d8, using len to distinguish.
|
||||
*/
|
||||
struct text_poke_loc {
|
||||
/* addr := _stext + rel_addr */
|
||||
s32 rel_addr;
|
||||
|
@ -1893,6 +1896,10 @@ noinstr int poke_int3_handler(struct pt_regs *regs)
|
|||
int3_emulate_jmp(regs, (long)ip + tp->disp);
|
||||
break;
|
||||
|
||||
case 0x70 ... 0x7f: /* Jcc */
|
||||
int3_emulate_jcc(regs, tp->opcode & 0xf, (long)ip, tp->disp);
|
||||
break;
|
||||
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
|
@ -1966,16 +1973,26 @@ static void text_poke_bp_batch(struct text_poke_loc *tp, unsigned int nr_entries
|
|||
* Second step: update all but the first byte of the patched range.
|
||||
*/
|
||||
for (do_sync = 0, i = 0; i < nr_entries; i++) {
|
||||
u8 old[POKE_MAX_OPCODE_SIZE] = { tp[i].old, };
|
||||
u8 old[POKE_MAX_OPCODE_SIZE+1] = { tp[i].old, };
|
||||
u8 _new[POKE_MAX_OPCODE_SIZE+1];
|
||||
const u8 *new = tp[i].text;
|
||||
int len = tp[i].len;
|
||||
|
||||
if (len - INT3_INSN_SIZE > 0) {
|
||||
memcpy(old + INT3_INSN_SIZE,
|
||||
text_poke_addr(&tp[i]) + INT3_INSN_SIZE,
|
||||
len - INT3_INSN_SIZE);
|
||||
|
||||
if (len == 6) {
|
||||
_new[0] = 0x0f;
|
||||
memcpy(_new + 1, new, 5);
|
||||
new = _new;
|
||||
}
|
||||
|
||||
text_poke(text_poke_addr(&tp[i]) + INT3_INSN_SIZE,
|
||||
(const char *)tp[i].text + INT3_INSN_SIZE,
|
||||
new + INT3_INSN_SIZE,
|
||||
len - INT3_INSN_SIZE);
|
||||
|
||||
do_sync++;
|
||||
}
|
||||
|
||||
|
@ -2003,8 +2020,7 @@ static void text_poke_bp_batch(struct text_poke_loc *tp, unsigned int nr_entries
|
|||
* The old instruction is recorded so that the event can be
|
||||
* processed forwards or backwards.
|
||||
*/
|
||||
perf_event_text_poke(text_poke_addr(&tp[i]), old, len,
|
||||
tp[i].text, len);
|
||||
perf_event_text_poke(text_poke_addr(&tp[i]), old, len, new, len);
|
||||
}
|
||||
|
||||
if (do_sync) {
|
||||
|
@ -2021,10 +2037,15 @@ static void text_poke_bp_batch(struct text_poke_loc *tp, unsigned int nr_entries
|
|||
* replacing opcode.
|
||||
*/
|
||||
for (do_sync = 0, i = 0; i < nr_entries; i++) {
|
||||
if (tp[i].text[0] == INT3_INSN_OPCODE)
|
||||
u8 byte = tp[i].text[0];
|
||||
|
||||
if (tp[i].len == 6)
|
||||
byte = 0x0f;
|
||||
|
||||
if (byte == INT3_INSN_OPCODE)
|
||||
continue;
|
||||
|
||||
text_poke(text_poke_addr(&tp[i]), tp[i].text, INT3_INSN_SIZE);
|
||||
text_poke(text_poke_addr(&tp[i]), &byte, INT3_INSN_SIZE);
|
||||
do_sync++;
|
||||
}
|
||||
|
||||
|
@ -2042,9 +2063,11 @@ static void text_poke_loc_init(struct text_poke_loc *tp, void *addr,
|
|||
const void *opcode, size_t len, const void *emulate)
|
||||
{
|
||||
struct insn insn;
|
||||
int ret, i;
|
||||
int ret, i = 0;
|
||||
|
||||
memcpy((void *)tp->text, opcode, len);
|
||||
if (len == 6)
|
||||
i = 1;
|
||||
memcpy((void *)tp->text, opcode+i, len-i);
|
||||
if (!emulate)
|
||||
emulate = opcode;
|
||||
|
||||
|
@ -2055,6 +2078,13 @@ static void text_poke_loc_init(struct text_poke_loc *tp, void *addr,
|
|||
tp->len = len;
|
||||
tp->opcode = insn.opcode.bytes[0];
|
||||
|
||||
if (is_jcc32(&insn)) {
|
||||
/*
|
||||
* Map Jcc.d32 onto Jcc.d8 and use len to distinguish.
|
||||
*/
|
||||
tp->opcode = insn.opcode.bytes[1] - 0x10;
|
||||
}
|
||||
|
||||
switch (tp->opcode) {
|
||||
case RET_INSN_OPCODE:
|
||||
case JMP32_INSN_OPCODE:
|
||||
|
@ -2071,7 +2101,6 @@ static void text_poke_loc_init(struct text_poke_loc *tp, void *addr,
|
|||
BUG_ON(len != insn.length);
|
||||
}
|
||||
|
||||
|
||||
switch (tp->opcode) {
|
||||
case INT3_INSN_OPCODE:
|
||||
case RET_INSN_OPCODE:
|
||||
|
@ -2080,6 +2109,7 @@ static void text_poke_loc_init(struct text_poke_loc *tp, void *addr,
|
|||
case CALL_INSN_OPCODE:
|
||||
case JMP32_INSN_OPCODE:
|
||||
case JMP8_INSN_OPCODE:
|
||||
case 0x70 ... 0x7f: /* Jcc */
|
||||
tp->disp = insn.immediate.value;
|
||||
break;
|
||||
|
||||
|
|
|
@ -464,50 +464,26 @@ static void kprobe_emulate_call(struct kprobe *p, struct pt_regs *regs)
|
|||
}
|
||||
NOKPROBE_SYMBOL(kprobe_emulate_call);
|
||||
|
||||
static nokprobe_inline
|
||||
void __kprobe_emulate_jmp(struct kprobe *p, struct pt_regs *regs, bool cond)
|
||||
static void kprobe_emulate_jmp(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long ip = regs->ip - INT3_INSN_SIZE + p->ainsn.size;
|
||||
|
||||
if (cond)
|
||||
ip += p->ainsn.rel32;
|
||||
ip += p->ainsn.rel32;
|
||||
int3_emulate_jmp(regs, ip);
|
||||
}
|
||||
|
||||
static void kprobe_emulate_jmp(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
__kprobe_emulate_jmp(p, regs, true);
|
||||
}
|
||||
NOKPROBE_SYMBOL(kprobe_emulate_jmp);
|
||||
|
||||
static const unsigned long jcc_mask[6] = {
|
||||
[0] = X86_EFLAGS_OF,
|
||||
[1] = X86_EFLAGS_CF,
|
||||
[2] = X86_EFLAGS_ZF,
|
||||
[3] = X86_EFLAGS_CF | X86_EFLAGS_ZF,
|
||||
[4] = X86_EFLAGS_SF,
|
||||
[5] = X86_EFLAGS_PF,
|
||||
};
|
||||
|
||||
static void kprobe_emulate_jcc(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
bool invert = p->ainsn.jcc.type & 1;
|
||||
bool match;
|
||||
unsigned long ip = regs->ip - INT3_INSN_SIZE + p->ainsn.size;
|
||||
|
||||
if (p->ainsn.jcc.type < 0xc) {
|
||||
match = regs->flags & jcc_mask[p->ainsn.jcc.type >> 1];
|
||||
} else {
|
||||
match = ((regs->flags & X86_EFLAGS_SF) >> X86_EFLAGS_SF_BIT) ^
|
||||
((regs->flags & X86_EFLAGS_OF) >> X86_EFLAGS_OF_BIT);
|
||||
if (p->ainsn.jcc.type >= 0xe)
|
||||
match = match || (regs->flags & X86_EFLAGS_ZF);
|
||||
}
|
||||
__kprobe_emulate_jmp(p, regs, (match && !invert) || (!match && invert));
|
||||
int3_emulate_jcc(regs, p->ainsn.jcc.type, ip, p->ainsn.rel32);
|
||||
}
|
||||
NOKPROBE_SYMBOL(kprobe_emulate_jcc);
|
||||
|
||||
static void kprobe_emulate_loop(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long ip = regs->ip - INT3_INSN_SIZE + p->ainsn.size;
|
||||
bool match;
|
||||
|
||||
if (p->ainsn.loop.type != 3) { /* LOOP* */
|
||||
|
@ -535,7 +511,9 @@ static void kprobe_emulate_loop(struct kprobe *p, struct pt_regs *regs)
|
|||
else if (p->ainsn.loop.type == 1) /* LOOPE */
|
||||
match = match && (regs->flags & X86_EFLAGS_ZF);
|
||||
|
||||
__kprobe_emulate_jmp(p, regs, match);
|
||||
if (match)
|
||||
ip += p->ainsn.rel32;
|
||||
int3_emulate_jmp(regs, ip);
|
||||
}
|
||||
NOKPROBE_SYMBOL(kprobe_emulate_loop);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ enum insn_type {
|
|||
NOP = 1, /* site cond-call */
|
||||
JMP = 2, /* tramp / site tail-call */
|
||||
RET = 3, /* tramp / site cond-tail-call */
|
||||
JCC = 4,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -25,12 +26,40 @@ static const u8 xor5rax[] = { 0x2e, 0x2e, 0x2e, 0x31, 0xc0 };
|
|||
|
||||
static const u8 retinsn[] = { RET_INSN_OPCODE, 0xcc, 0xcc, 0xcc, 0xcc };
|
||||
|
||||
static u8 __is_Jcc(u8 *insn) /* Jcc.d32 */
|
||||
{
|
||||
u8 ret = 0;
|
||||
|
||||
if (insn[0] == 0x0f) {
|
||||
u8 tmp = insn[1];
|
||||
if ((tmp & 0xf0) == 0x80)
|
||||
ret = tmp;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern void __static_call_return(void);
|
||||
|
||||
asm (".global __static_call_return\n\t"
|
||||
".type __static_call_return, @function\n\t"
|
||||
ASM_FUNC_ALIGN "\n\t"
|
||||
"__static_call_return:\n\t"
|
||||
ANNOTATE_NOENDBR
|
||||
ANNOTATE_RETPOLINE_SAFE
|
||||
"ret; int3\n\t"
|
||||
".size __static_call_return, . - __static_call_return \n\t");
|
||||
|
||||
static void __ref __static_call_transform(void *insn, enum insn_type type,
|
||||
void *func, bool modinit)
|
||||
{
|
||||
const void *emulate = NULL;
|
||||
int size = CALL_INSN_SIZE;
|
||||
const void *code;
|
||||
u8 op, buf[6];
|
||||
|
||||
if ((type == JMP || type == RET) && (op = __is_Jcc(insn)))
|
||||
type = JCC;
|
||||
|
||||
switch (type) {
|
||||
case CALL:
|
||||
|
@ -57,6 +86,20 @@ static void __ref __static_call_transform(void *insn, enum insn_type type,
|
|||
else
|
||||
code = &retinsn;
|
||||
break;
|
||||
|
||||
case JCC:
|
||||
if (!func) {
|
||||
func = __static_call_return;
|
||||
if (cpu_feature_enabled(X86_FEATURE_RETHUNK))
|
||||
func = x86_return_thunk;
|
||||
}
|
||||
|
||||
buf[0] = 0x0f;
|
||||
__text_gen_insn(buf+1, op, insn+1, func, 5);
|
||||
code = buf;
|
||||
size = 6;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (memcmp(insn, code, size) == 0)
|
||||
|
@ -68,9 +111,9 @@ static void __ref __static_call_transform(void *insn, enum insn_type type,
|
|||
text_poke_bp(insn, code, size, emulate);
|
||||
}
|
||||
|
||||
static void __static_call_validate(void *insn, bool tail, bool tramp)
|
||||
static void __static_call_validate(u8 *insn, bool tail, bool tramp)
|
||||
{
|
||||
u8 opcode = *(u8 *)insn;
|
||||
u8 opcode = insn[0];
|
||||
|
||||
if (tramp && memcmp(insn+5, tramp_ud, 3)) {
|
||||
pr_err("trampoline signature fail");
|
||||
|
@ -79,7 +122,8 @@ static void __static_call_validate(void *insn, bool tail, bool tramp)
|
|||
|
||||
if (tail) {
|
||||
if (opcode == JMP32_INSN_OPCODE ||
|
||||
opcode == RET_INSN_OPCODE)
|
||||
opcode == RET_INSN_OPCODE ||
|
||||
__is_Jcc(insn))
|
||||
return;
|
||||
} else {
|
||||
if (opcode == CALL_INSN_OPCODE ||
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
#define JUMP_NEW_OFFSET 4
|
||||
#define JUMP_KEY_OFFSET 8
|
||||
|
||||
#define ALT_ENTRY_SIZE 12
|
||||
#define ALT_ENTRY_SIZE 14
|
||||
#define ALT_ORIG_OFFSET 0
|
||||
#define ALT_NEW_OFFSET 4
|
||||
#define ALT_FEATURE_OFFSET 8
|
||||
#define ALT_ORIG_LEN_OFFSET 10
|
||||
#define ALT_NEW_LEN_OFFSET 11
|
||||
#define ALT_ORIG_LEN_OFFSET 12
|
||||
#define ALT_NEW_LEN_OFFSET 13
|
||||
|
||||
#endif /* _X86_ARCH_SPECIAL_H */
|
||||
|
|
Loading…
Reference in New Issue