Preventative Patching Produces Pwn2Own Participant’s Panic

October 31, 2018 | Jasiel Spelman

One of the things that adds anxiety to every entry for Pwn2Own is the duplicate checking process. Even if a contestant successfully demonstrates an exploit, it doesn’t count as a win if the vulnerability is already known by us or the vendor. This can be incredibly stressful for researchers as there’s no way to know if someone else has already submitted the vulnerability. As we begin preparing for Pwn2Own Tokyo, I thought I’d share a bug collision that affected me earlier this year.

The vulnerability was in the JIT engine within WebKit’s JavaScript implementation, JavaScriptCore. Specifically, the vulnerability was in the Data Flow Graph (DFG) tier.

Before I go into the vulnerability and the collisions, let’s make sure we’re all up to speed on JavaScriptCore. Some functions supported by JavaScriptCore have an optimized variant, known as an Intrinsic, that has its own opcode dedicated to the operation. As examples, new Uint32Array can end up being handled by the NewTypedArray opcode while Math.abs(x) can end up being handled by the ArithAbs opcode. These opcodes need any side effects to be described in the same way as any other opcode, which in JavaScriptCore's JIT engine will occur primarily within two files: DFGAbstractInterpreterInlines.h and DFGClobberize.h.

Just to be explicit about it, side effects are considered to be anything that may occur outside of the operation itself. As an example, the ArithAbs opcode is expected to take an argument and return the absolute value of that argument, just as Math.abs does. As such, it is not expected to be an effectful operation, meaning there should be no allocations of Arrays as a result of executing an ArithAbs opcode. Effectful operations are denoted in a couple of different ways. In DFGAbstractInterpreterInlines.h, they are denoted with a call to clobberWorld, whereas in DFGClobberize.h, they are denoted with calls to read(World) and write(Heap). We can gloss over what happens during those calls and focus on the fact that they clobber known states such that the JIT engine knows not to make any assumptions after the opcode has executed.

While I was en route to OPCDE Dubai in April of this year, I was reading through the DFG byte code parser and happened across this rather large comment within the handleTypedArrayConstructor method:

The case where blah is a non-buffer object piqued my curiosity, so I quickly looked at DFGAbstractInterpreterInlines.h and DFGClobberize.h to see if the NewTypedArray opcode was properly treated as being effectful.

Here's what I saw in DFGAbstractInterpreterInlines.h:

This is standard and pretty much what I expected. If the argument to the TypedArray constructor is UntypedUse, then a call to clobberWorld occurs.

However, things were far more interesting when I looked at DFGClobberize.h:

As a comparison, here's how Math.abs, handled by the ArithAbs opcode, is handled within DFGClobberize.h:

See the difference? With the ArithAbs operation, if the argument to Math.abs is not an integer or a double, then clobberize will mark the operation as effectful. The NewTypedArray operation was assumed to not be marked as effectful by DFGClobberize but was marked as effectful by the abstract interpreter. Since the operation isn’t properly modeled, we could get the JIT engine to confuse the type of an Array such that it lets us read out pointer values as floating point values or write floating point values that can later get interpreted as pointers. In the end, this type confusion could lead to code execution if crafted fake objects break the assumptions made by the JIT engine.

An observant reader will notice that I haven't provided any CVE or ZDI identifier for this vulnerability, which is due to the collisions I briefly mentioned at the beginning of this post. Six days after I found this bug, git commit 36dd2d2b40c5640412f39efcb6fd081a56016a5d was introduced in an attempt to catch issues with clobberize and the abstract interpreter disagreeing with each other. As part of this commit, the following was added to DFGClobberize.h:

Oddly enough, two days after this commit, one of our researchers found and submitted the same vulnerability.

Bug collisions are a part of research, especially when many are looking in similar areas. With Pwn2Own Tokyo right around the corner, here’s hoping that everyone participating has a chain that survives any patches issued before the contest.

You can find me on Twitter at @WanderingGlitch, and follow the team for the latest in exploit techniques and security patches.