Throughout November, I plan to release details on vulnerabilities I found in web-browsers which I've not released before. This is the tenth entry in that series. The below information is available in more detail on my blog at http://blog.skylined.nl/20161114001.html. Follow me on http://twitter.com/berendjanwever for daily browser bugs. Microsoft Internet Explorer 11 MSHTML CMapElement::Notify use-after-free ======================================================================== (MS15-009, CVE-2015-0040) Synopsis -------- A specially crafted web-page can cause MSIE 11 to interrupt the handling of one `readystatechange` event with another. This interrupts a call to one of the various `CElement::Notify` functions to make another such call and at least one of these functions is non-reentrant. This can have various repercussions, e.g. when an attacker triggers this vulnerability using a `CMapElement` object, a reference to that object can be stored in a linked list and the object itself can be freed. This pointer can later be re-used to cause a classic use-after-free issue. Known affected versions, attack vectors and mitigations ----------------------- * Microsoft Internet Explorer 11 An attacker would need to get a target user to open a specially crafted web-page. Disabling JavaScript should prevent an attacker from triggering the vulnerable code path. Description ----------- When a `DocumentFragment` containing an applet element is added to the DOM, all elements receive a notification that they are removed from the `CMarkup`. Next, they are added to the DOM and receive notification of being added to another `CMarkup`. When the applet is added, a `CObjectElement` is created and added to the `CMarkup`. This causes a `readystatechange` event to fire, which interrupts the current code. During this `readystatechange` event, the DOM may be modified, which causes further notifications to fire. However, elements in the `DocumentFragment` that come after the applet element have already received a notification that they have been remove from one `CMarkup`, but not that they have been added to the new one. Thus, these elements may receive another notification of removal, followed by two notifications of being added to a `CMarkup`. AFAICT, this event-within-an-event itself is the root cause of the bug and allows memory corruption in various ways. I discovered the issue because the code in `CMapElement::Notify` is non-reentrant and does not handle this sequence of events well. This code maintains a singly linked list of map elements that have been added to the document. An object should never be added to this list twice, as this will cause a loop in the list (a map element pointing to itself as the next in the list). However, the event-within-an-event can be used to first cause two consecutive calls to remove the same element from this list followed by two calls to add the same element to the list. This results in the following sequence of events: * The first call to remove the element will remove it from the list. * The second call to remove the element will do nothing. * The first call to add the element will add it to the list. * The second call to add the element will try to add it to the list again, causing the list to contain a loop. This list is now corrupt. At this point, an attacker can remove the `CMapElement`, causing the code to try to remove it from the list and free it. However, because of the loop in the list, the above code will not actually remove it from the list. After this, the pointer in the list points to freed memory and an attacker can force MSIE to reuse it. Time-line --------- * *September 2014*: This vulnerability was found through fuzzing. * *September 2014*: This vulnerability was submitted to ZDI. * *September 2014*: This vulnerability was acquired by ZDI. * *February 2015*: Microsoft address this issue in MS15-009. * *November 2016*: Details of this issue are released. Cheers, SkyLined Repro.xhtml: AFAICT, this event-within-an-event itself is the root cause of the bug and allows memory corruption in various ways. I discovered the issue because the code in CMapAElement::Notify does not handle this sequence of events well. The below pseudo-code represents that function and shows how this can lead to memory corruption: void MSHTML!CMapAElement::Notify(CNotification* pANotification) { CElement::Notify(pAArg1); if (pANotification->dwACode_00 == 17) { // add CMarkup* pAMarkup = this->CElement::GetAMarkup(); this->pANextAMapAElement_38 = pAMarkup->GetAMapAHead(); pAMarkup->CMarkup::SetAMapAHead(this); } else if (pANotification->dwACode_00 == 18) { // remove CMarkup* pAMarkup = this->CElement::GetAMarkup(); CDoc pADoc = pAMarkup->CMarkup::GetALookasideAPtr(4); CMapAElement** ppAMapAElement = &(pADoc->pAMapAElement_08); while(*ppAMapAElement) { if (*ppAMapAElement == this) { *ppAMapAElement = this->pAMapAElement_38; break; } ppAMapAElement = &(*ppAMapAElement->pAMapAElement_38); } } }