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);