CVE-2018-0993
Description
A remote code execution vulnerability in ChakraCore/Microsoft Edge due to improper copy of inline slot data during object cloning, exploited via a crafted webpage.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A remote code execution vulnerability in ChakraCore/Microsoft Edge due to improper copy of inline slot data during object cloning, exploited via a crafted webpage.
Vulnerability
A remote code execution vulnerability exists in the Chakra scripting engine used by Microsoft Edge and ChakraCore versions prior to the fix included in commit 4bdd0ef [2]. The bug is in the DynamicObject copy constructor: when copying properties not stored in inline slots, the code would share the auxSlots array between the source and the copy [2]. The root cause is the lack of a deepCopy flag that would force separate allocation for the auxiliary slot array, leading to a stack-to-heap copy bug [2]. The vulnerability is triggered when the engine handles objects in memory [1].
Exploitation
An attacker can host a crafted website that, when visited with Microsoft Edge or when the code is executed by an application embedding the vulnerable ChakraCore, causes the scripting engine to process specially crafted JavaScript that triggers the memory corruption [1]. The attacker does not require any special authentication or user interaction beyond visiting the malicious page. The vulnerability is classified as remote, with the attack vector over the network [3]. The exploitation sequence involves the engine incorrectly copying auxiliary slot pointers, allowing an attacker to control memory [2].
Impact
Successful exploitation allows an attacker to execute arbitrary code in the context of the current user [1]. This can lead to full compromise of the affected system, including installation of programs, viewing, changing, or deleting data, and creating new accounts [1]. The vulnerability affects Microsoft Edge on Windows 10 and ChakraCore [3]. The impact is remote code execution (RCE) with the privileges of the user running the browser.
Mitigation
Microsoft released security updates on April 10, 2018 to address this vulnerability. Users should apply updates through Windows Update. The fix is also incorporated into the ChakraCore repository as commit 4bdd0ef [2]. ChakraCore 1.11 was supported with security updates until March 9, 2021 [4]; users on later builds should update to a patched version. No workarounds are documented apart from installing the update.
- NVD - CVE-2018-0993
- [CVE-2018-0993] Edge - Stack-to-heap copy bug in Edge,expose internal… · chakra-core/ChakraCore@4bdd0ef
- Microsoft ChakraCore Scripting Engine CVE-2018-0993 Remote Memory Corruption Vulnerability
- GitHub - chakra-core/ChakraCore: ChakraCore is an open source Javascript engine with a C API.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
Microsoft.ChakraCoreNuGet | < 1.8.3 | 1.8.3 |
Affected products
3- Range: ChakraCore
Patches
14bdd0efb1c30[CVE-2018-0993] Edge - Stack-to-heap copy bug in Edge,expose internal property - Qihoo 360
9 files changed · +148 −31
lib/Runtime/Language/JavascriptOperators.cpp+2 −2 modified@@ -9499,9 +9499,9 @@ namespace Js case Js::TypeIds_Integer: return instance; case Js::TypeIds_RegEx: - return JavascriptRegExp::BoxStackInstance(JavascriptRegExp::FromVar(instance)); + return JavascriptRegExp::BoxStackInstance(JavascriptRegExp::FromVar(instance), deepCopy); case Js::TypeIds_Object: - return DynamicObject::BoxStackInstance(DynamicObject::FromVar(instance)); + return DynamicObject::BoxStackInstance(DynamicObject::FromVar(instance), deepCopy); case Js::TypeIds_Array: return JavascriptArray::BoxStackInstance(JavascriptArray::UnsafeFromVar(instance), deepCopy); case Js::TypeIds_NativeIntArray:
lib/Runtime/Library/JavascriptArray.cpp+49 −8 modified@@ -11668,7 +11668,7 @@ namespace Js } JavascriptArray::JavascriptArray(JavascriptArray * instance, bool boxHead, bool deepCopy) - : ArrayObject(instance) + : ArrayObject(instance, deepCopy) { if (boxHead) { @@ -11683,6 +11683,40 @@ namespace Js } } + // Allocate a new Array with its own segments and copy the data in instance + // into the new Array + template <typename T> + T * JavascriptArray::DeepCopyInstance(T * instance) + { + return RecyclerNewPlusZ(instance->GetRecycler(), + instance->GetTypeHandler()->GetInlineSlotsSize() + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement), + T, instance, true /*boxHead*/, true /*deepCopy*/); + } + + ArrayObject* JavascriptArray::DeepCopyInstance(ArrayObject* arrayObject) + { + ArrayObject* arrayCopy; + TypeId typeId = JavascriptOperators::GetTypeId(arrayObject); + switch (typeId) + { + case Js::TypeIds_Array: + arrayCopy = JavascriptArray::DeepCopyInstance<JavascriptArray>(JavascriptArray::UnsafeFromVar(arrayObject)); + break; + case Js::TypeIds_NativeIntArray: + arrayCopy = JavascriptArray::DeepCopyInstance<JavascriptNativeIntArray>(JavascriptNativeIntArray::UnsafeFromVar(arrayObject)); + break; + case Js::TypeIds_NativeFloatArray: + arrayCopy = JavascriptArray::DeepCopyInstance<JavascriptNativeFloatArray>(JavascriptNativeFloatArray::UnsafeFromVar(arrayObject)); + break; + + default: + AssertAndFailFast(!"Unexpected objectArray type while boxing stack instance"); + arrayCopy = nullptr; + }; + + return arrayCopy; + } + template <typename T> T * JavascriptArray::BoxStackInstance(T * instance, bool deepCopy) { @@ -11713,9 +11747,16 @@ namespace Js // Reallocate both the object as well as the head segment when the head is on the stack or // when a deep copy is needed. This is to prevent a scenario where box may leave either one // on the stack when both must be on the heap. - boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(), - inlineSlotsSize + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement), - T, instance, true, deepCopy); + if (deepCopy) + { + boxedInstance = DeepCopyInstance(instance); + } + else + { + boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(), + inlineSlotsSize + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement), + T, instance, true /*boxHead*/, false /*deepCopy*/); + } } else if(inlineSlotsSize) { @@ -11803,14 +11844,14 @@ namespace Js } #endif - JavascriptNativeArray::JavascriptNativeArray(JavascriptNativeArray * instance) : - JavascriptArray(instance, false, false), + JavascriptNativeArray::JavascriptNativeArray(JavascriptNativeArray * instance, bool deepCopy) : + JavascriptArray(instance, false, deepCopy), weakRefToFuncBody(instance->weakRefToFuncBody) { } JavascriptNativeIntArray::JavascriptNativeIntArray(JavascriptNativeIntArray * instance, bool boxHead, bool deepCopy) : - JavascriptNativeArray(instance) + JavascriptNativeArray(instance, deepCopy) { if (boxHead) { @@ -11856,7 +11897,7 @@ namespace Js #endif JavascriptNativeFloatArray::JavascriptNativeFloatArray(JavascriptNativeFloatArray * instance, bool boxHead, bool deepCopy) : - JavascriptNativeArray(instance) + JavascriptNativeArray(instance, deepCopy) { if (boxHead) {
lib/Runtime/Library/JavascriptArray.h+3 −1 modified@@ -899,10 +899,12 @@ namespace Js static uint32 GetSpreadArgLen(Var spreadArg, ScriptContext *scriptContext); static JavascriptArray * BoxStackInstance(JavascriptArray * instance, bool deepCopy); + static ArrayObject * DeepCopyInstance(ArrayObject * instance); protected: template <typename T> void InitBoxedInlineSegments(SparseArraySegment<T> * dst, SparseArraySegment<T> * src, bool deepCopy); template <typename T> static T * BoxStackInstance(T * instance, bool deepCopy); + template <typename T> static T * DeepCopyInstance(T * instance); public: template<class T, uint InlinePropertySlots> static size_t DetermineAllocationSize(const uint inlineElementSlots, size_t *const allocationPlusSizeRef = nullptr, uint *const alignedInlineElementSlotsRef = nullptr); @@ -960,7 +962,7 @@ namespace Js JavascriptArray(length, type), weakRefToFuncBody(nullptr) {} // For BoxStackInstance - JavascriptNativeArray(JavascriptNativeArray * instance); + JavascriptNativeArray(JavascriptNativeArray * instance, bool deepCopy); Field(RecyclerWeakReference<FunctionBody> *) weakRefToFuncBody;
lib/Runtime/Library/JavascriptRegularExpression.cpp+9 −4 modified@@ -54,15 +54,20 @@ namespace Js #endif } - JavascriptRegExp::JavascriptRegExp(JavascriptRegExp * instance) : - DynamicObject(instance), + JavascriptRegExp::JavascriptRegExp(JavascriptRegExp * instance, bool deepCopy) : + DynamicObject(instance, deepCopy), pattern(instance->GetPattern()), splitPattern(instance->GetSplitPattern()), lastIndexVar(instance->lastIndexVar), lastIndexOrFlag(instance->lastIndexOrFlag) { // For boxing stack instance Assert(ThreadContext::IsOnStack(instance)); + + // These members should never be on the stack and thus never need to be deep copied + Assert(!ThreadContext::IsOnStack(instance->GetPattern())); + Assert(!ThreadContext::IsOnStack(instance->GetSplitPattern())); + Assert(!ThreadContext::IsOnStack(instance->lastIndexVar)); } bool JavascriptRegExp::Is(Var aValue) @@ -1057,7 +1062,7 @@ namespace Js DEFINE_FLAG_GETTER(EntryGetterSticky, sticky, IsSticky) DEFINE_FLAG_GETTER(EntryGetterUnicode, unicode, IsUnicode) - JavascriptRegExp * JavascriptRegExp::BoxStackInstance(JavascriptRegExp * instance) + JavascriptRegExp * JavascriptRegExp::BoxStackInstance(JavascriptRegExp * instance, bool deepCopy) { Assert(ThreadContext::IsOnStack(instance)); // On the stack, the we reserved a pointer before the object as to store the boxed value @@ -1068,7 +1073,7 @@ namespace Js return boxedInstance; } Assert(instance->GetTypeHandler()->GetInlineSlotsSize() == 0); - boxedInstance = RecyclerNew(instance->GetRecycler(), JavascriptRegExp, instance); + boxedInstance = RecyclerNew(instance->GetRecycler(), JavascriptRegExp, instance, deepCopy); *boxedInstanceRef = boxedInstance; return boxedInstance; }
lib/Runtime/Library/JavascriptRegularExpression.h+2 −2 modified@@ -82,7 +82,7 @@ namespace Js UnifiedRegex::RegexFlags SetRegexFlag(PropertyId propertyId, UnifiedRegex::RegexFlags flags, UnifiedRegex::RegexFlags flag, ScriptContext* scriptContext); // For boxing stack instance - JavascriptRegExp(JavascriptRegExp * instance); + JavascriptRegExp(JavascriptRegExp * instance, bool deepCopy); DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptRegExp); protected: @@ -201,7 +201,7 @@ namespace Js virtual uint GetSpecialPropertyCount() const override; virtual PropertyId const * GetSpecialPropertyIds() const override; - static Js::JavascriptRegExp * BoxStackInstance(Js::JavascriptRegExp * instance); + static Js::JavascriptRegExp * BoxStackInstance(Js::JavascriptRegExp * instance, bool deepCopy); #if ENABLE_TTD public:
lib/Runtime/Types/ArrayObject.cpp+2 −2 modified@@ -10,8 +10,8 @@ namespace Js { - ArrayObject::ArrayObject(ArrayObject * instance) - : DynamicObject(instance), + ArrayObject::ArrayObject(ArrayObject * instance, bool deepCopy) + : DynamicObject(instance, deepCopy), length(instance->length) { }
lib/Runtime/Types/ArrayObject.h+1 −1 modified@@ -34,7 +34,7 @@ namespace Js } // For boxing stack instance - ArrayObject(ArrayObject * instance); + ArrayObject(ArrayObject * instance, bool deepCopy); void __declspec(noreturn) ThrowItemNotConfigurableError(PropertyId propId = Constants::NoProperty); void VerifySetItemAttributes(PropertyId propId, PropertyAttributes attributes);
lib/Runtime/Types/DynamicObject.cpp+55 −5 modified@@ -46,7 +46,7 @@ namespace Js #endif } - DynamicObject::DynamicObject(DynamicObject * instance) : + DynamicObject::DynamicObject(DynamicObject * instance, bool deepCopy) : RecyclableObject(instance->type), auxSlots(instance->auxSlots), objectArray(instance->objectArray) // copying the array should copy the array flags and array call site index as well @@ -63,6 +63,8 @@ namespace Js #if !FLOATVAR ScriptContext * scriptContext = this->GetScriptContext(); #endif + // Copy the inline slot data from the source instance. Deep copy is implicit because + // the inline slot allocation is already accounted for with the allocation of the object. for (int i = 0; i < inlineSlotCount; i++) { #if !FLOATVAR @@ -71,11 +73,27 @@ namespace Js #else dstSlots[i] = srcSlots[i]; #endif - + Assert(!ThreadContext::IsOnStack(dstSlots[i])); } if (propertyCount > inlineSlotCapacity) { + // Properties that are not inlined are stored in the auxSlots, which must be copied + // from the source instance. + + // Assert that this block of code will not overwrite inline slot data + Assert(!typeHandler->IsObjectHeaderInlinedTypeHandler()); + + if (deepCopy) + { + // When a deepCopy is needed, ensure that auxSlots is not shared with the source instance + // so that both objects can have their own, separate lifetimes. + InitSlots(this); + + // This auxSlots should now be a separate allocation. + Assert(auxSlots != instance->auxSlots); + } + uint auxSlotCount = propertyCount - inlineSlotCapacity; for (uint i = 0; i < auxSlotCount; i++) @@ -84,11 +102,43 @@ namespace Js // Currently we only support temp numbers assigned to stack objects auxSlots[i] = JavascriptNumber::BoxStackNumber(instance->auxSlots[i], scriptContext); #else + // Copy the slot values from that instance to this + Assert(!ThreadContext::IsOnStack(instance->auxSlots[i])); auxSlots[i] = instance->auxSlots[i]; #endif + Assert(!ThreadContext::IsOnStack(auxSlots[i])); } } + if (deepCopy && instance->HasObjectArray()) + { + // Assert that this block of code will not overwrite inline slot data + Assert(!typeHandler->IsObjectHeaderInlinedTypeHandler()); + + // While the objectArray can be any array type, a DynamicObject that is created on the + // stack will only have one of these three types (as these are also the only array types + // that can be allocated on the stack). + Assert(Js::JavascriptArray::Is(instance->GetObjectArrayOrFlagsAsArray()) + || Js::JavascriptNativeIntArray::Is(instance->GetObjectArrayOrFlagsAsArray()) + || Js::JavascriptNativeFloatArray::Is(instance->GetObjectArrayOrFlagsAsArray()) + ); + + // Since a deep copy was requested for this DynamicObject, deep copy the object array as well + SetObjectArray(JavascriptArray::DeepCopyInstance(instance->GetObjectArrayOrFlagsAsArray())); + } + else + { + // Otherwise, assert that there is either + // - no object array to deep copy + // - an object array, but no deep copy needed + // - data in the objectArray member, but it is inline slot data + // - data in the objectArray member, but it is array flags + Assert( + (instance->GetObjectArrayOrFlagsAsArray() == nullptr) || + (!deepCopy || typeHandler->IsObjectHeaderInlinedTypeHandler() || instance->UsesObjectArrayOrFlagsAsFlags()) + ); + } + #if ENABLE_OBJECT_SOURCE_TRACKING TTD::InitializeDiagnosticOriginInformation(this->TTDDiagOriginInfo); #endif @@ -805,7 +855,7 @@ namespace Js } DynamicObject * - DynamicObject::BoxStackInstance(DynamicObject * instance) + DynamicObject::BoxStackInstance(DynamicObject * instance, bool deepCopy) { Assert(ThreadContext::IsOnStack(instance)); // On the stack, the we reserved a pointer before the object as to store the boxed value @@ -819,11 +869,11 @@ namespace Js size_t inlineSlotsSize = instance->GetTypeHandler()->GetInlineSlotsSize(); if (inlineSlotsSize) { - boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(), inlineSlotsSize, DynamicObject, instance); + boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(), inlineSlotsSize, DynamicObject, instance, deepCopy); } else { - boxedInstance = RecyclerNew(instance->GetRecycler(), DynamicObject, instance); + boxedInstance = RecyclerNew(instance->GetRecycler(), DynamicObject, instance, deepCopy); } *boxedInstanceRef = boxedInstance;
lib/Runtime/Types/DynamicObject.h+25 −6 modified@@ -84,21 +84,40 @@ namespace Js #endif private: + // Memory layout of DynamicObject can be one of the following: + // (#1) (#2) (#3) + // +--------------+ +--------------+ +--------------+ + // | vtable, etc. | | vtable, etc. | | vtable, etc. | + // |--------------| |--------------| |--------------| + // | auxSlots | | auxSlots | | inline slots | + // | union | | union | | | + // +--------------+ |--------------| | | + // | inline slots | | | + // +--------------+ +--------------+ + // The allocation size of inline slots is variable and dependent on profile data for the + // object. The offset of the inline slots is managed by DynamicTypeHandler. + // More details for the layout scenarios below. + Field(Field(Var)*) auxSlots; - // The objectArrayOrFlags field can store one of two things: - // a) a pointer to the object array holding numeric properties of this object, or - // b) a bitfield of flags. + + // The objectArrayOrFlags field can store one of three things: + // a) a pointer to the object array holding numeric properties of this object (#1, #2), or + // b) a bitfield of flags (#1, #2), or + // c) inline slot data (#3) // Because object arrays are not commonly used, the storage space can be reused to carry information that // can improve performance for typical objects. To indicate the bitfield usage we set the least significant bit to 1. // Object array pointer always trumps the flags, such that when the first numeric property is added to an // object, its flags will be wiped out. Hence flags can only be used as a form of cache to improve performance. // For functional correctness, some other fallback mechanism must exist to convey the information contained in flags. // This fields always starts off initialized to null. Currently, only JavascriptArray overrides it to store flags, the // bits it uses are DynamicObjectFlags::AllArrayFlags. + // Regarding c) above, inline slots can be stored within the allocation of sizeof(DynamicObject) (#3) or after + // sizeof(DynamicObject) (#2). This is indicated by GetTypeHandler()->IsObjectHeaderInlinedTypeHandler(); when true, the + // inline slots are within the object, and thus the union members *and* auxSlots actually contain inline slot data. union { - Field(ArrayObject *) objectArray; // Only if !IsAnyArray + Field(ArrayObject *) objectArray; // Only if !IsAnyArray struct // Only if IsAnyArray { Field(DynamicObjectFlags) arrayFlags; @@ -122,7 +141,7 @@ namespace Js DynamicObject(DynamicType * type, ScriptContext * scriptContext); // For boxing stack instance - DynamicObject(DynamicObject * instance); + DynamicObject(DynamicObject * instance, bool deepCopy); uint16 GetOffsetOfInlineSlots() const; @@ -317,7 +336,7 @@ namespace Js ProfileId GetArrayCallSiteIndex() const; void SetArrayCallSiteIndex(ProfileId profileId); - static DynamicObject * BoxStackInstance(DynamicObject * instance); + static DynamicObject * BoxStackInstance(DynamicObject * instance, bool deepCopy); private: ArrayObject* EnsureObjectArray();
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-7c7v-g484-j4cfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-0993ghsaADVISORY
- www.securityfocus.com/bid/103627mitrevdb-entryx_refsource_BID
- www.securitytracker.com/id/1040650mitrevdb-entryx_refsource_SECTRACK
- github.com/chakra-core/ChakraCore/commit/4bdd0efb1c309a0307cdfbd2856ca8cd88b154bbghsaWEB
- portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-0993ghsax_refsource_CONFIRMWEB
- web.archive.org/web/20210125210148/http://www.securityfocus.com/bid/103627ghsaWEB
- web.archive.org/web/20211207123630/http://www.securitytracker.com/id/1040650ghsaWEB
News mentions
0No linked articles in our index yet.