From 1ae7e3ce95515758b4cd7215cb4e48539a0f4031 Mon Sep 17 00:00:00 2001
From: Rajat Dua <radua@microsoft.com>
Date: Mon, 8 May 2017 16:57:23 -0700
Subject: [PATCH] [CVE-2017-0236] Bug with array buffer detach

Change the vtable of virtual typed arrays to regular typed arrays upon array buffer detach to prevent writes to detached buffer in the jitted code.
---
 lib/Runtime/Library/ArrayBuffer.cpp | 145 +++++++++++++++++++++++++++-
 lib/Runtime/Library/ArrayBuffer.h   |   2 +-
 lib/Runtime/Library/DataView.cpp    |   8 ++
 lib/Runtime/Library/DataView.h      |   1 +
 lib/Runtime/Library/TypedArray.cpp  |   8 ++
 lib/Runtime/Library/TypedArray.h    |   1 +
 6 files changed, 159 insertions(+), 6 deletions(-)

diff --git a/lib/Runtime/Library/ArrayBuffer.cpp b/lib/Runtime/Library/ArrayBuffer.cpp
index 1dbf195c55f..aed7980559f 100644
--- a/lib/Runtime/Library/ArrayBuffer.cpp
+++ b/lib/Runtime/Library/ArrayBuffer.cpp
@@ -38,7 +38,7 @@ namespace Js
         return toReturn;
     }
 
-    void ArrayBuffer::ClearParentsLength(ArrayBufferParent* parent)
+    void ArrayBuffer::DetachBufferFromParent(ArrayBufferParent* parent)
     {
         if (parent == nullptr)
         {
@@ -48,23 +48,158 @@ namespace Js
         switch (JavascriptOperators::GetTypeId(parent))
         {
             case TypeIds_Int8Array:
+                if (Int8VirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Int8VirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Int8Array>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Int8VirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Int8Array>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Uint8Array:
+                if (Uint8VirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Uint8VirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Uint8Array>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Uint8VirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Uint8Array>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Uint8ClampedArray:
+                if (Uint8ClampedVirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Uint8ClampedVirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Uint8ClampedArray>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Uint8ClampedVirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Uint8ClampedArray>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Int16Array:
+                if (Int16VirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Int16VirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Int16Array>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Int16VirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Int16Array>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Uint16Array:
+                if (Uint16VirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Uint16VirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Uint16Array>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Uint16VirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Uint16Array>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Int32Array:
+                if (Int32VirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Int32VirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Int32Array>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Int32VirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Int32Array>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Uint32Array:
+                if (Uint32VirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Uint32VirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Uint32Array>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Uint32VirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Uint32Array>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Float32Array:
+                if (Float32VirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Float32VirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Float32Array>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Float32VirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Float32Array>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Float64Array:
+                if (Float64VirtualArray::Is(parent))
+                {
+                    if (VirtualTableInfo<Float64VirtualArray>::HasVirtualTable(parent))
+                    {
+                        VirtualTableInfo<Float64Array>::SetVirtualTable(parent);
+                    }
+                    else
+                    {
+                        Assert(VirtualTableInfo<CrossSiteObject<Float64VirtualArray>>::HasVirtualTable(parent));
+                        VirtualTableInfo<CrossSiteObject<Float64Array>>::SetVirtualTable(parent);
+                    }
+                }
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
+                break;
+
             case TypeIds_Int64Array:
             case TypeIds_Uint64Array:
             case TypeIds_CharArray:
             case TypeIds_BoolArray:
-                TypedArrayBase::FromVar(parent)->length = 0;
+                TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
                 break;
 
             case TypeIds_DataView:
-                DataView::FromVar(parent)->length = 0;
+                DataView::FromVar(parent)->ClearLengthAndBufferOnDetach();
                 break;
 
             default:
@@ -90,14 +225,14 @@ namespace Js
 
         if (this->primaryParent != nullptr)
         {
-            this->ClearParentsLength(this->primaryParent->Get());
+            this->DetachBufferFromParent(this->primaryParent->Get());
         }
 
         if (this->otherParents != nullptr)
         {
             this->otherParents->Map([&](RecyclerWeakReference<ArrayBufferParent>* item)
             {
-                this->ClearParentsLength(item->Get());
+                this->DetachBufferFromParent(item->Get());
             });
         }
 
diff --git a/lib/Runtime/Library/ArrayBuffer.h b/lib/Runtime/Library/ArrayBuffer.h
index 04d03e6384d..70bf4845b50 100644
--- a/lib/Runtime/Library/ArrayBuffer.h
+++ b/lib/Runtime/Library/ArrayBuffer.h
@@ -50,7 +50,7 @@ namespace Js
         DEFINE_VTABLE_CTOR_ABSTRACT(ArrayBuffer, ArrayBufferBase);
 #define MAX_ASMJS_ARRAYBUFFER_LENGTH 0x100000000 //4GB
     private:
-        void ClearParentsLength(ArrayBufferParent* parent);
+        void DetachBufferFromParent(ArrayBufferParent* parent);
     public:
         template <typename FreeFN>
         class ArrayBufferDetachedState : public ArrayBufferDetachedStateBase
diff --git a/lib/Runtime/Library/DataView.cpp b/lib/Runtime/Library/DataView.cpp
index e16286e8c7e..ba2c0b7a088 100644
--- a/lib/Runtime/Library/DataView.cpp
+++ b/lib/Runtime/Library/DataView.cpp
@@ -666,6 +666,14 @@ namespace Js
         return FALSE;
     }
 
+    void DataView::ClearLengthAndBufferOnDetach()
+    {
+        AssertMsg(this->GetArrayBuffer()->IsDetached(), "Array buffer should be detached if we're calling this method");
+
+        this->length = 0;
+        this->buffer = nullptr;
+    }
+
 #ifdef _M_ARM
     // Provide template specialization (only) for memory access at unaligned float/double address which causes data alignment exception otherwise.
     template<>
diff --git a/lib/Runtime/Library/DataView.h b/lib/Runtime/Library/DataView.h
index c7b6fab50e2..711cafd3253 100644
--- a/lib/Runtime/Library/DataView.h
+++ b/lib/Runtime/Library/DataView.h
@@ -50,6 +50,7 @@ namespace Js
         }
 
         uint32 GetByteOffset() const { return byteOffset; }
+        void ClearLengthAndBufferOnDetach();
 
         static Var NewInstance(RecyclableObject* function, CallInfo callInfo, ...);
         static Var EntryGetInt8(RecyclableObject* function, CallInfo callInfo, ...);
diff --git a/lib/Runtime/Library/TypedArray.cpp b/lib/Runtime/Library/TypedArray.cpp
index 6e7ec0efbd9..951df220cf7 100644
--- a/lib/Runtime/Library/TypedArray.cpp
+++ b/lib/Runtime/Library/TypedArray.cpp
@@ -1117,6 +1117,14 @@ namespace Js
 
     }
 
+    void TypedArrayBase::ClearLengthAndBufferOnDetach()
+    {
+        AssertMsg(IsDetachedBuffer(), "Array buffer should be detached if we're calling this method");
+
+        this->length = 0;
+        this->buffer = nullptr;
+    }
+
     Var TypedArrayBase::EntryGetterBuffer(RecyclableObject* function, CallInfo callInfo, ...)
     {
         PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
diff --git a/lib/Runtime/Library/TypedArray.h b/lib/Runtime/Library/TypedArray.h
index ac767cd48cf..966c9e0cbe6 100644
--- a/lib/Runtime/Library/TypedArray.h
+++ b/lib/Runtime/Library/TypedArray.h
@@ -157,6 +157,7 @@ namespace Js
         uint32 GetBytesPerElement() const { return BYTES_PER_ELEMENT; }
         byte*  GetByteBuffer() const { return buffer; };
         bool IsDetachedBuffer() const { return this->GetArrayBuffer()->IsDetached(); }
+        void ClearLengthAndBufferOnDetach();
         static Var CommonSet(Arguments& args);
         static Var CommonSubarray(Arguments& args);