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

Hongtao Liu crazylht@gmail.com
Thu Apr 23 02:53:34 GMT 2020


On Wed, Apr 22, 2020 at 4:47 PM Jan Beulich <jbeulich@suse.com> wrote:
>
> 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.

You're right, i'll revert sub-options for lfence_after_load, and only
issue a warning for REP CMPS/SCAS.

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

And handle REP CMPS/SCAS stuff in another separate patch.

> > @@ -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.
>

Changed.

> > +      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.)

I don't quite understand your point, could you give a testcase to show that?

>
> > +      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).

Changed.

>
> Jan

-- 
BR,
Hongtao


More information about the Binutils mailing list