diff mbox

[10/13] x86/alternative: Support indirect call replacement

Message ID efabcfb022d29fb0a9ccb39380623573555c2bcb.1507128293.git.jpoimboe@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Josh Poimboeuf Oct. 4, 2017, 3:58 p.m. UTC
Add alternative patching support for replacing an instruction with an
indirect call.  This will be needed for the paravirt alternatives.

Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
---
 arch/x86/kernel/alternative.c | 22 +++++++++++++++-------
 1 file changed, 15 insertions(+), 7 deletions(-)

Comments

Jürgen Groß Oct. 25, 2017, 11:25 a.m. UTC | #1
On 04/10/17 17:58, Josh Poimboeuf wrote:
> Add alternative patching support for replacing an instruction with an
> indirect call.  This will be needed for the paravirt alternatives.
> 
> Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
> ---
>  arch/x86/kernel/alternative.c | 22 +++++++++++++++-------
>  1 file changed, 15 insertions(+), 7 deletions(-)
> 
> diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
> index 3344d3382e91..81c577c7deba 100644
> --- a/arch/x86/kernel/alternative.c
> +++ b/arch/x86/kernel/alternative.c
> @@ -410,20 +410,28 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
>  		insnbuf_sz = a->replacementlen;
>  
>  		/*
> -		 * 0xe8 is a relative jump; fix the offset.
> -		 *
> -		 * Instruction length is checked before the opcode to avoid
> -		 * accessing uninitialized bytes for zero-length replacements.
> +		 * Fix the address offsets for call and jump instructions which
> +		 * use PC-relative addressing.
>  		 */
>  		if (a->replacementlen == 5 && *insnbuf == 0xe8) {
> +			/* direct call */
>  			*(s32 *)(insnbuf + 1) += replacement - instr;
> -			DPRINTK("Fix CALL offset: 0x%x, CALL 0x%lx",
> +			DPRINTK("Fix direct CALL offset: 0x%x, CALL 0x%lx",
>  				*(s32 *)(insnbuf + 1),
>  				(unsigned long)instr + *(s32 *)(insnbuf + 1) + 5);
> -		}
>  
> -		if (a->replacementlen && is_jmp(replacement[0]))
> +		} else if (a->replacementlen == 6 && *insnbuf == 0xff &&
> +			   *(insnbuf+1) == 0x15) {
> +			/* indirect call */
> +			*(s32 *)(insnbuf + 2) += replacement - instr;
> +			DPRINTK("Fix indirect CALL offset: 0x%x, CALL *0x%lx",
> +				*(s32 *)(insnbuf + 2),
> +				(unsigned long)instr + *(s32 *)(insnbuf + 2) + 6);
> +
> +		} else if (a->replacementlen && is_jmp(replacement[0])) {

Is this correct? Without your patch this was:

if (*insnbuf == 0xe8) ...
if (is_jmp(replacement[0])) ...

Now you have:

if (*insnbuf == 0xe8) ...
else if (*insnbuf == 0xff15) ...
else if (is_jmp(replacement[0])) ...

So only one or none of the three variants will be executed. In the past
it could be none, one or both.


Juergen
Josh Poimboeuf Nov. 16, 2017, 9:19 p.m. UTC | #2
On Wed, Oct 25, 2017 at 01:25:02PM +0200, Juergen Gross wrote:
> On 04/10/17 17:58, Josh Poimboeuf wrote:
> > Add alternative patching support for replacing an instruction with an
> > indirect call.  This will be needed for the paravirt alternatives.
> > 
> > Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
> > ---
> >  arch/x86/kernel/alternative.c | 22 +++++++++++++++-------
> >  1 file changed, 15 insertions(+), 7 deletions(-)
> > 
> > diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
> > index 3344d3382e91..81c577c7deba 100644
> > --- a/arch/x86/kernel/alternative.c
> > +++ b/arch/x86/kernel/alternative.c
> > @@ -410,20 +410,28 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
> >  		insnbuf_sz = a->replacementlen;
> >  
> >  		/*
> > -		 * 0xe8 is a relative jump; fix the offset.
> > -		 *
> > -		 * Instruction length is checked before the opcode to avoid
> > -		 * accessing uninitialized bytes for zero-length replacements.
> > +		 * Fix the address offsets for call and jump instructions which
> > +		 * use PC-relative addressing.
> >  		 */
> >  		if (a->replacementlen == 5 && *insnbuf == 0xe8) {
> > +			/* direct call */
> >  			*(s32 *)(insnbuf + 1) += replacement - instr;
> > -			DPRINTK("Fix CALL offset: 0x%x, CALL 0x%lx",
> > +			DPRINTK("Fix direct CALL offset: 0x%x, CALL 0x%lx",
> >  				*(s32 *)(insnbuf + 1),
> >  				(unsigned long)instr + *(s32 *)(insnbuf + 1) + 5);
> > -		}
> >  
> > -		if (a->replacementlen && is_jmp(replacement[0]))
> > +		} else if (a->replacementlen == 6 && *insnbuf == 0xff &&
> > +			   *(insnbuf+1) == 0x15) {
> > +			/* indirect call */
> > +			*(s32 *)(insnbuf + 2) += replacement - instr;
> > +			DPRINTK("Fix indirect CALL offset: 0x%x, CALL *0x%lx",
> > +				*(s32 *)(insnbuf + 2),
> > +				(unsigned long)instr + *(s32 *)(insnbuf + 2) + 6);
> > +
> > +		} else if (a->replacementlen && is_jmp(replacement[0])) {
> 
> Is this correct? Without your patch this was:
> 
> if (*insnbuf == 0xe8) ...
> if (is_jmp(replacement[0])) ...
> 
> Now you have:
> 
> if (*insnbuf == 0xe8) ...
> else if (*insnbuf == 0xff15) ...
> else if (is_jmp(replacement[0])) ...
> 
> So only one or none of the three variants will be executed. In the past
> it could be none, one or both.

It can't be a call *and* a jump.  It's either one or the other.

Maybe it's a little confusing that the jump check uses replacement[0]
while the other checks use *insnbuf?  They have the same value, so the
same variable should probably be used everywhere for consistency.  I can
make them more consistent.
Jürgen Groß Nov. 17, 2017, 5:46 a.m. UTC | #3
On 16/11/17 22:19, Josh Poimboeuf wrote:
> On Wed, Oct 25, 2017 at 01:25:02PM +0200, Juergen Gross wrote:
>> On 04/10/17 17:58, Josh Poimboeuf wrote:
>>> Add alternative patching support for replacing an instruction with an
>>> indirect call.  This will be needed for the paravirt alternatives.
>>>
>>> Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
>>> ---
>>>  arch/x86/kernel/alternative.c | 22 +++++++++++++++-------
>>>  1 file changed, 15 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
>>> index 3344d3382e91..81c577c7deba 100644
>>> --- a/arch/x86/kernel/alternative.c
>>> +++ b/arch/x86/kernel/alternative.c
>>> @@ -410,20 +410,28 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
>>>  		insnbuf_sz = a->replacementlen;
>>>  
>>>  		/*
>>> -		 * 0xe8 is a relative jump; fix the offset.
>>> -		 *
>>> -		 * Instruction length is checked before the opcode to avoid
>>> -		 * accessing uninitialized bytes for zero-length replacements.
>>> +		 * Fix the address offsets for call and jump instructions which
>>> +		 * use PC-relative addressing.
>>>  		 */
>>>  		if (a->replacementlen == 5 && *insnbuf == 0xe8) {
>>> +			/* direct call */
>>>  			*(s32 *)(insnbuf + 1) += replacement - instr;
>>> -			DPRINTK("Fix CALL offset: 0x%x, CALL 0x%lx",
>>> +			DPRINTK("Fix direct CALL offset: 0x%x, CALL 0x%lx",
>>>  				*(s32 *)(insnbuf + 1),
>>>  				(unsigned long)instr + *(s32 *)(insnbuf + 1) + 5);
>>> -		}
>>>  
>>> -		if (a->replacementlen && is_jmp(replacement[0]))
>>> +		} else if (a->replacementlen == 6 && *insnbuf == 0xff &&
>>> +			   *(insnbuf+1) == 0x15) {
>>> +			/* indirect call */
>>> +			*(s32 *)(insnbuf + 2) += replacement - instr;
>>> +			DPRINTK("Fix indirect CALL offset: 0x%x, CALL *0x%lx",
>>> +				*(s32 *)(insnbuf + 2),
>>> +				(unsigned long)instr + *(s32 *)(insnbuf + 2) + 6);
>>> +
>>> +		} else if (a->replacementlen && is_jmp(replacement[0])) {
>>
>> Is this correct? Without your patch this was:
>>
>> if (*insnbuf == 0xe8) ...
>> if (is_jmp(replacement[0])) ...
>>
>> Now you have:
>>
>> if (*insnbuf == 0xe8) ...
>> else if (*insnbuf == 0xff15) ...
>> else if (is_jmp(replacement[0])) ...
>>
>> So only one or none of the three variants will be executed. In the past
>> it could be none, one or both.
> 
> It can't be a call *and* a jump.  It's either one or the other.
> 
> Maybe it's a little confusing that the jump check uses replacement[0]
> while the other checks use *insnbuf?  They have the same value, so the

Right, I was fooled by that.

> same variable should probably be used everywhere for consistency.  I can
> make them more consistent.
> 

I'd appreciate that. :-)


Juergen
H. Peter Anvin Nov. 17, 2017, 7:52 p.m. UTC | #4
On 10/04/17 08:58, Josh Poimboeuf wrote:
> Add alternative patching support for replacing an instruction with an
> indirect call.  This will be needed for the paravirt alternatives.

I have a patchset that generalizes the alternatives in what I think is a
more robust way.  I really, really want to get rid of these hacks.  Let
me clean it up an post it...

	-hpa
diff mbox

Patch

diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
index 3344d3382e91..81c577c7deba 100644
--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -410,20 +410,28 @@  void __init_or_module noinline apply_alternatives(struct alt_instr *start,
 		insnbuf_sz = a->replacementlen;
 
 		/*
-		 * 0xe8 is a relative jump; fix the offset.
-		 *
-		 * Instruction length is checked before the opcode to avoid
-		 * accessing uninitialized bytes for zero-length replacements.
+		 * Fix the address offsets for call and jump instructions which
+		 * use PC-relative addressing.
 		 */
 		if (a->replacementlen == 5 && *insnbuf == 0xe8) {
+			/* direct call */
 			*(s32 *)(insnbuf + 1) += replacement - instr;
-			DPRINTK("Fix CALL offset: 0x%x, CALL 0x%lx",
+			DPRINTK("Fix direct CALL offset: 0x%x, CALL 0x%lx",
 				*(s32 *)(insnbuf + 1),
 				(unsigned long)instr + *(s32 *)(insnbuf + 1) + 5);
-		}
 
-		if (a->replacementlen && is_jmp(replacement[0]))
+		} else if (a->replacementlen == 6 && *insnbuf == 0xff &&
+			   *(insnbuf+1) == 0x15) {
+			/* indirect call */
+			*(s32 *)(insnbuf + 2) += replacement - instr;
+			DPRINTK("Fix indirect CALL offset: 0x%x, CALL *0x%lx",
+				*(s32 *)(insnbuf + 2),
+				(unsigned long)instr + *(s32 *)(insnbuf + 2) + 6);
+
+		} else if (a->replacementlen && is_jmp(replacement[0])) {
+			/* direct jump */
 			recompute_jump(a, instr, replacement, insnbuf);
+		}
 
 		if (a->instrlen > a->replacementlen) {
 			add_nops(insnbuf + a->replacementlen,