Invariantly Exploitable Input: An Apple Safari Bug Worth RevisitingDecember 20, 2017 | Jasiel Spelman
This is the third in our series of Top 5 interesting cases from 2017. Each of these bugs has some element that sets them apart from the approximately 1,000 advisories released by the program this year. Today’s blog looks at a type confusion bug in Apple Safari that allowed remote code execution and the patch that addresses it.
Back in 2016, Neymar of Tencent's Xuanwu LAB submitted a vulnerability in Apple Safari to the Zero Day Initiative (ZDI) program. This vulnerability was patched by Apple in January and is identified by ZDI-17-054/CVE-2017-2354. The Safari web browser has received additional scrutiny from researchers as its market share increases, and the value of these bugs has increased alongside the popularity. This particular bug is a type confusion bug in the handling of SearchInputType objects. Being a fan of type confusion vulnerabilities, I found this to be an especially interesting one. A certain particularity in this bug added to my fascination with it, but I'll explain that particularity later on.
Whenever I'm analyzing a vulnerability submission, I like to look at the proof of concept (PoC) first, so let's start with that code:
I removed a portion of the PoC that exerted control over the vulnerability, because at this point we're more concerned with the bug itself. Thanks to the minimal nature of the PoC, we can reason that the bug is related to the input tag and more specifically modifying the type of the input tag.
To find out more, let's look at the crash:
Although the access violation in
WebCore::TimerBase::heapPop is where we see the result of the bug, it doesn't look like it is the cause of the issue. Why? The crash actually occurs as a result of reading a pointer that comes from the
'this' object. Based on that, it would seem that something is wrong with the Timer object passed into the
If we look at the call stack, we have two more methods within
WebCore::TimerBase that are called:
setNextFireTime. If the issue is with our
'this' pointer, then looking at those functions won't yield anything useful. Additionally, when we looked at the PoC, we surmised that the bug was related to input objects and changing the type within an event handler. As such, let's skip ahead and look at
Now things are a little clearer. By the time
HTMLInputElement::onSearch is being called,
m_inputType is no longer a SearchInputType object due to the type of the Input element being changed. There is a check, but it only occurs within debug builds. An invariant for
HTMLInputElement::onSearch is that
m_inputType is a SearchInputType object. However, event handlers can break that assumption since the type check is not actually enforced.
Putting this all together, we end up with a type confusion vulnerability where an object is confused as a SearchInputType object prior to an instance method.
So how did this bug get patched? Let's look at HTMLInputSearch::onSearch as it looks now:
The sanity check is now explicit and no longer within an assert.
In the beginning of the blog post, I mentioned that I loved this bug in part because of a particularity about the vulnerability, but I never specified what that particularity was. I'm always intrigued whenever a bug could have been prevented had a debug check been used instead of a runtime check. In fact, WebKit has support for this type of assertion already through a RELEASE_ASSERT macro, which would have turned this exploitable bug into a simple denial-of-service by immediately and safely crashing the browser. Another notable instance of this is a debug versus runtime mismatch that occurred at Pwn2Own 2013. Joshua Drake took advantage of a vulnerability in Java that was a runtime assert in Java 6 but only a debug assert in Java 7 to win himself $20,000.