On 08/03/2022 14:37, Jan Beulich wrote:
> On 08.03.2022 15:01, Andrew Cooper wrote:
>> For livepatching, we need to look at a potentially clobbered function and
>> determine whether it used to have an ENDBR64 instruction.
>>
>> Use a non-default 4-byte P6 long nop, not emitted by toolchains, and 
>> introduce
>> the was_endbr64() predicate.
> Did you consider using ENDBR32 for this purpose?

No, and no because that's very short sighted.  Even 4 non-nops would be
better than ENDBR32, because they wouldn't create actually-usable
codepaths in corner cases we care to exclude.

> I'm worried that
> the pattern you picked is still too close to what might be used
> (down the road) by a tool chain.

This is what Linux are doing too, but no - I'm not worried.  The
encoding isn't the only protection; toolchains also have no reason to
put a nop4 at the head of functions; nop5 is the common one to find.

> One neat thing about ENDBR32 would be that you wouldn't even
> need memcpy() - you'd merely flip the low main opcode bit.

Not relevant.  You're taking the SMC pipeline hit for any sized write,
and a single movl is far less cryptic.

>> Bjoern: For the livepatching code, I think you want:
>>
>>   if ( is_endbr64(...) || was_endbr64(...) )
>>       needed += ENDBR64_LEN;
> Looks like I didn't fully understand the problem then from your
> initial description. The adjustment here (and the one needed in
> Björn's patch) is to compensate for the advancing of the
> targets of altcalls past the ENDBR?

No.  Consider this scenario:

callee:
    endbr64
    ...

altcall:
    call *foo(%rip)

During boot, we rewrite altcall to be `call callee+4` and turn endbr64
into nops, so it now looks like:

callee:
    nop4
    ...

altcall:
    call callee+4

Then we want to livepatch callee to callee_new, so we get

callee_new:
    endbr64
    ...

in the livepatch itself.

Now, to actually patch, we need to memcpy(callee+4, "jmp callee_new", 5).

The livepatch logic calling is_endbr(callee) doesn't work because it's
now a nop4, which is why it needs a was_endbr64(callee) too.

>
>> --- a/xen/arch/x86/include/asm/endbr.h
>> +++ b/xen/arch/x86/include/asm/endbr.h
>> @@ -52,4 +52,16 @@ static inline void place_endbr64(void *ptr)
>>      *(uint32_t *)ptr = gen_endbr64();
>>  }
>>  
>> +/*
>> + * After clobbering ENDBR64, we may need to confirm that the site used to
>> + * contain an ENDBR64 instruction.  Use an encoding which isn't the default
>> + * P6_NOP4.
>> + */
>> +#define ENDBR64_POISON "\x66\x0f\x1f\x00" /* osp nopl (%rax) */
> In case this remains as is - did you mean "opsz" instead of "osp"?
> But this really is "nopw (%rax)" anyway.

Oh, osp is the nasm name.  I'll switch to nopw.

~Andrew

Reply via email to