[PATCH 1/2] i386: Generate lfence with load/indirect branch/ret [CVE-2020-0551]

Jan Beulich jbeulich@suse.com
Wed Apr 22 08:47:23 GMT 2020


On 22.04.2020 05:33, Hongtao Liu wrote:
> On Tue, Apr 21, 2020 at 2:30 PM Jan Beulich <jbeulich@suse.com> wrote:
>>
>> On 21.04.2020 04:24, Hongtao Liu wrote:
>>> On Mon, Apr 20, 2020 at 3:34 PM Jan Beulich <jbeulich@suse.com> wrote:
>>>>
>>>> On 20.04.2020 09:20, Hongtao Liu wrote:
>>>>> On Thu, Apr 16, 2020 at 4:33 PM Jan Beulich <jbeulich@suse.com> wrote:
>>>>>> On 16.04.2020 07:34, Hongtao Liu wrote:
>>>>>>> @@ -4506,6 +4520,22 @@ insert_lfence_after (void)
>>>>>>> {
>>>>>>>   if (lfence_after_load && load_insn_p ())
>>>>>>>     {
>>>>>>> +      /* Insert lfence after rep cmps/scas only under
>>>>>>> +       -mlfence-after-load=all.  */
>>>>>>> +      if (((i.tm.base_opcode | 0x1) == 0xa7
>>>>>>> +         || (i.tm.base_opcode | 0x1) == 0xaf)
>>>>>>> +        && i.prefix[REP_PREFIX])
>>>>>>
>>>>>> I'm afraid I don't understand why the REP forms need treating
>>>>>> differently from the non-REP ones of the same insns.
>>>>>>
>>>>>
>>>>> Not all REP forms, just REP CMPS/SCAS which would change EFLAGS.
>>>>
>>>> Well, of course just the two. But this doesn't answer my question
>>>> as to why there is such a special case.
>>>>
>>>
>>> There are also two REP string instructions that require special
>>> treatment. Specifically, the compare string (CMPS) and scan string
>>> (SCAS) instructions set EFLAGS in a manner that depends on the data
>>> being compared/scanned. When used with a REP prefix, the number of
>>> iterations may therefore vary depending on this data. If the data is a
>>> program secret chosen by the adversary using an LVI method, then this
>>> data-dependent behavior may leak some aspect of the secret. The
>>> solution is to unfold any REP CMPS and REP SCAS operations into a loop
>>> and insert an LFENCE after the CMPS/SCAS instruction. For example,
>>> REPNZ SCAS can be unfolded to:
>>>
>>> .RepLoop:
>>>   JRCXZ .ExitRepLoop
>>>   DEC rcx  # or ecx if the REPNZ SCAS uses a 32-bit address size
>>>   SCAS
>>>   LFENCE
>>>   JNZ .RepLoop
>>> .ExitRepLoop:
>>>   ...
>>>
>>> The request i get is to add options to handle or not handle REP
>>> CMPS/SCAS also plus issue a warning.
>>
>> But you don't handle them as per what you've written above, afaics.
>> Am I overlooking anything?
> 
> Well, that solution is not meant for gas, i put them here for
> convienence of understanding of why we need to handle REP CMPS/SCAS
> specially.

And how is it better then to issue a warning and leave the code
alone over still at least inserting an LFENCE after the insn? I.e.
I'm not sure I see the value of the separate "general" and "all"
sub-options then.

As to it not being meant for gas - why is that?

> @@ -4568,12 +4609,13 @@ insert_lfence_before (void)
>        return;
>      }
> 
> -  /* Output or/not and lfence before ret.  */
> +  /* Output or/not/shl and lfence before ret/lret/iret.  */
>    if (lfence_before_ret != lfence_before_ret_none
>        && (i.tm.base_opcode == 0xc2
>            || i.tm.base_opcode == 0xc3
>            || i.tm.base_opcode == 0xca
> -          || i.tm.base_opcode == 0xcb))
> +          || i.tm.base_opcode == 0xcb
> +          || i.tm.base_opcode == 0xcf))
>      {
>        if (last_insn.kind != last_insn_other
>            && last_insn.seg == now_seg)
> @@ -4583,33 +4625,59 @@ insert_lfence_before (void)
>                           last_insn.name, i.tm.name);
>            return;
>          }
> -      if (lfence_before_ret == lfence_before_ret_or)
> -        {
> -          /* orl: 0x830c2400.  */
> -          p = frag_more ((flag_code == CODE_64BIT ? 1 : 0) + 4 + 3);
> -          if (flag_code == CODE_64BIT)
> -            *p++ = 0x48;
> -          *p++ = 0x83;
> -          *p++ = 0xc;
> -          *p++ = 0x24;
> -          *p++ = 0x0;
> -        }
> +
> +      bfd_boolean lret = (i.tm.base_opcode | 0x1) == 0xcb;

"(i.tm.base_opcode | 0x5) == 0xcf" or "(i.tm.base_opcode & 8)"
to also cover IRET.

> +      bfd_boolean has_rexw = i.prefix[REX_PREFIX] & REX_W;
> +      char prefix = 0x0;
> +      /* Default operand size for far return is 32 bits,
> +         64 bits for near return.  */
> +      if (has_rexw)
> +        prefix = 0x48;
>        else
> +        prefix = i.prefix[DATA_PREFIX]
> +                 ? 0x66
> +                 : !lret && flag_code == CODE_64BIT ? 0x48 : 0x0;

Aiui the workaround is specifically for Intel CPUs. Intel CPUs
ignore operand size overrides on near RET. (Sorry, I should
have pointed out this fact earlier already.)

> +      if (lfence_before_ret == lfence_before_ret_not)
>          {
> -          p = frag_more ((flag_code == CODE_64BIT ? 2 : 0) + 6 + 3);
> -          /* notl: 0xf71424.  */
> -          if (flag_code == CODE_64BIT)
> -            *p++ = 0x48;
> +          /* not: 0xf71424, may add prefix
> +             for operand size overwrite or 64-bit code.  */

As said before - "override", not "overwrite" (there are several
instances to change).

Jan


More information about the Binutils mailing list