I can't get past this part.
When a Googler "believes" Union(PlainNumber, NaN) represents the set of possible return values for a math function and commits that in the code, how is there not an automated set of tests that use one out of every other IEEE754-associated type as input to then check whether it generates output which falls outside the assumed set of types?
I mean in this case you've even got NegativeZero as its own type. What's even the point of having a type with a single value if you don't hurl it at every manually-entered optimization that it could conceivably invalidate?
But if you believe that this is an example of wanton abuse at Google, you can trade on that belief, and in a sense put your money where your mouth is, because this is a whole class of potential bugs, not just one bug; the same pattern will recur for other places where the v8 typer is wrong about the possible results of functions. Go do a sweep! If you find one, the value of the resulting bug might be pretty decent.
This article will be my new go-to example when someone handwaves a bug away with a complacent “it’ll never happen” and “it’s not that big of a deal”. Yes it will, and yes it is.
https://www.blackhat.com/us-18/briefings/schedule/index.html...
Yet the exploit in WebSQL a few weeks ago gave RCE from a component directly reachable by page scripts.
It's slightly worse than that. Math.expm1(-0) = -0 is explicitly called out as part of the standard[1]. You don't need to throw every IEEE754 at your functions to make sure they're all correct, but validating the explicitly defined corner cases is table stakes.
1. https://www.ecma-international.org/ecma-262/6.0/#sec-math.ex...
Then, take your preferred JS fuzzer...
Math.expm1 = (x) => Math.pow(Math.E, (x)) - 1;
With this monkey patched version, Math.expm1(-0) now returns 0. I've been writing JavaScript for a long time and I didn't even know Math.expm1 was a thing. Was it really necessary to implement this inside V8?[0]: https://www.johndcook.com/blog/2010/06/07/math-library-funct...
[1]: https://en.wikipedia.org/wiki/Multiply%E2%80%93accumulate_op...
The exact implementation depends on your math library, but in glibc it appears to reduce the argument modulo log 2, and then uses an approximation found in part by the Remez algorithm for the exponential function on the range [0, 0.5 log2]. My assumption here is that the Remez algorithm will often be preferred over Padé, but I am not an expert on these things. The Padé approximant gives an approximation “near” a point but the Remez algorithm finds an approximation over an entire interval. In either case, the Remez algorithm is only used to construct one part of the function, and there is also some effort to propagate error from the argument reduction. The resulting algorithm claims <1 ULP error.
For matrix exponentiation you would need a library.
But it's not quite that simple, because another consequence of of the static analyzer's misperception is that
is the constant 0, no matter what value x takes and no matter what INDEX is, and the JIT will simply constant-fold 0 in for the whole expression, so the bounds check really won't matter.But constant folding is an optimization, not a security boundary, and easily "defeated": replace the -0 constant in that expression with an object reference:
You can read that code and see that it's the constant -0, but the static analyzer needs to do escape analysis to make sure the object reference really does just evaluate to a constant, and escape analysis happens after the typing analysis, so the constant folding doesn't happen.The end result of the bug is a sort of "black magic zero/one chimera" that, mixed into an expression, tricks the JIT into stripping bounds checks. It's an extremely cool exploit primitive. It's doubly cool because if you don't have Andrea's writeup or a really nuanced understanding of v8 internals, you could stare at that exploit for eternity and never understand why it works.