Commit 1adce1b9 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

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
parents d9de5ce8 923510c8
Loading
Loading
Loading
Loading
+76 −56
Original line number Diff line number Diff line
@@ -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,16 +234,17 @@ 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) \
#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(feat1, 1)						\
	ALTINSTR_ENTRY(feat2, 2)						\
	ALTINSTR_ENTRY(feat3, 3)						\
	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)				\
@@ -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__ */

+31 −0
Original line number Diff line number Diff line
@@ -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 */
+53 −23
Original line number Diff line number Diff line
@@ -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 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
	}
}

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;

+8 −30
Original line number Diff line number Diff line
@@ -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;
	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);

+47 −3
Original line number Diff line number Diff line
@@ -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 ||
Loading