Live data from Hacker News

Exploiting the Math.expm1 typing bug in V8

abiondo.me

11–20 of 47 posts

Re: Exploiting the Math.expm1 typing bug in V8

#11
post #9
post #8

Earlier quoted context omitted.

Hi, TLM of the WebAssembly runtime in V8 here. TLDR: it's asm.js's fault. And yes, complexity. The reason that WebAssembly JIT code memory is still RMW (for now) is actually really unfortunate. As you might know, V8's JIT code memory for JS is only writable when the application is quiesced (i.e. JS is not running) and the JIT is either finishing a function or the garbage collector is moving JITted code. It's read-exe…

> TLDR: it's asm.js's fault. > In order to meet our performance goals for fast asm.js validation and startup, the WebAssembly engine does not do up-front compilation of Wasm code coming from asm.js. It sounds like the fault is how you reached your performance goals rather than asm.js itself.

It's very obvious from context the reference is to the asm.js implementation in V8, I'm not sure how you could read it otherwise ("strongest plausible interpretation" and all that).

Re: Exploiting the Math.expm1 typing bug in V8

#12
post #3

The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0. But if x is -0, it can. That, in turn, means it believes Object.is(Math.expm(x), -0) must always be false. But if x is -0, it's true, not false. That, in turn, means that the JIT believes array[Object.is(Math.expm(x), -0) * INDEX] must be array[0], no matter what INDEX is. But if x is -0, it'll…

> The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0.

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?

Re: Exploiting the Math.expm1 typing bug in V8

#13
post #9
post #8

Earlier quoted context omitted.

Hi, TLM of the WebAssembly runtime in V8 here. TLDR: it's asm.js's fault. And yes, complexity. The reason that WebAssembly JIT code memory is still RMW (for now) is actually really unfortunate. As you might know, V8's JIT code memory for JS is only writable when the application is quiesced (i.e. JS is not running) and the JIT is either finishing a function or the garbage collector is moving JITted code. It's read-exe…

> TLDR: it's asm.js's fault. > In order to meet our performance goals for fast asm.js validation and startup, the WebAssembly engine does not do up-front compilation of Wasm code coming from asm.js. It sounds like the fault is how you reached your performance goals rather than asm.js itself.

> It sounds like the fault is how you reached your performance goals rather than asm.js itself.

The properties of asm.js make it inherently less efficient than Wasm.

Re: Exploiting the Math.expm1 typing bug in V8

#14
post #3

The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0. But if x is -0, it can. That, in turn, means it believes Object.is(Math.expm(x), -0) must always be false. But if x is -0, it's true, not false. That, in turn, means that the JIT believes array[Object.is(Math.expm(x), -0) * INDEX] must be array[0], no matter what INDEX is. But if x is -0, it'll…

> The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0. 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 che…

I don't know, to me, this sounds like one of the more subtle examples of the kinds of mistakes that lead to security failures. Like, it might be an almost archetypical example of the "all bugs are security vulnerabilities" hypothesis. They got code execution from expm1!

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.

Re: Exploiting the Math.expm1 typing bug in V8

#15
post #3

The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0. But if x is -0, it can. That, in turn, means it believes Object.is(Math.expm(x), -0) must always be false. But if x is -0, it's true, not false. That, in turn, means that the JIT believes array[Object.is(Math.expm(x), -0) * INDEX] must be array[0], no matter what INDEX is. But if x is -0, it'll…

> The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0. 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 che…

serious question - does the V8 team do property checking?

Re: Exploiting the Math.expm1 typing bug in V8

#16
post #8
post #2

Interesting how at the end, after acquiring out-of-bounds write access, that it was easiest to leverage the WebAssembly infrastructure to execute code than to build a ROP chain. Apparently WebAssembly heap memory storing generated code is not write protected at all . I guess whatever architecture they have for managing typed memory chunks doesn't make it sufficiently easy to manipulate protection bits dynamically, an…

Hi, TLM of the WebAssembly runtime in V8 here. TLDR: it's asm.js's fault. And yes, complexity. The reason that WebAssembly JIT code memory is still RMW (for now) is actually really unfortunate. As you might know, V8's JIT code memory for JS is only writable when the application is quiesced (i.e. JS is not running) and the JIT is either finishing a function or the garbage collector is moving JITted code. It's read-exe…

I'm not sure how asm.js is relevant here. The issue is that function-level lazy compilation makes compilation too hot to allow for an mprotect call, no? Wouldn't a function-level lazy Web Assembly implementation have the same problem?

It seems to me that the solution is the same for both wasm and asm.js: make the unit of compilation larger than the function, so as to amortize the cost of mprotect.

Re: Exploiting the Math.expm1 typing bug in V8

#17
post #3

The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0. But if x is -0, it can. That, in turn, means it believes Object.is(Math.expm(x), -0) must always be false. But if x is -0, it's true, not false. That, in turn, means that the JIT believes array[Object.is(Math.expm(x), -0) * INDEX] must be array[0], no matter what INDEX is. But if x is -0, it'll…

> The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0. 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 che…

I wouldn't be surprised if NegativeZero got introduced to fix some other bug along the same lines involving a different function, and when fixing that bug they neglected to notice that it applies to expm1 as well. Math.expm1 is... a bit obscure. I could certainly see myself making the same mistake.

Re: Exploiting the Math.expm1 typing bug in V8

#18
post #8

Earlier quoted context omitted.

Hi, TLM of the WebAssembly runtime in V8 here. TLDR: it's asm.js's fault. And yes, complexity. The reason that WebAssembly JIT code memory is still RMW (for now) is actually really unfortunate. As you might know, V8's JIT code memory for JS is only writable when the application is quiesced (i.e. JS is not running) and the JIT is either finishing a function or the garbage collector is moving JITted code. It's read-exe…

I'm not sure how asm.js is relevant here. The issue is that function-level lazy compilation makes compilation too hot to allow for an mprotect call, no? Wouldn't a function-level lazy Web Assembly implementation have the same problem? It seems to me that the solution is the same for both wasm and asm.js: make the unit of compilation larger than the function, so as to amortize the cost of mprotect.

You and I both know that the comments section of HN lacks sufficient space to discuss all the things we thought of / could think of :)

Re: Exploiting the Math.expm1 typing bug in V8

#19
post #8

Earlier quoted context omitted.

Hi, TLM of the WebAssembly runtime in V8 here. TLDR: it's asm.js's fault. And yes, complexity. The reason that WebAssembly JIT code memory is still RMW (for now) is actually really unfortunate. As you might know, V8's JIT code memory for JS is only writable when the application is quiesced (i.e. JS is not running) and the JIT is either finishing a function or the garbage collector is moving JITted code. It's read-exe…

I'm not sure how asm.js is relevant here. The issue is that function-level lazy compilation makes compilation too hot to allow for an mprotect call, no? Wouldn't a function-level lazy Web Assembly implementation have the same problem? It seems to me that the solution is the same for both wasm and asm.js: make the unit of compilation larger than the function, so as to amortize the cost of mprotect.

Also, I should have mentioned, but concurrent compilation of Wasm (for incremental tierup) essentially puts the final nail in the WX exclusion coffin. (but we deployed that long after the first reason, lazy compilation for asm.js). The only solution in the long run afaict is out-of-process compilation, which we will explore this year.

Re: Exploiting the Math.expm1 typing bug in V8

#20
post #3

The fundamental bug here is really slick. The static analyzer in the JIT incorrectly believes Math.expm1(x) can't return -0. But if x is -0, it can. That, in turn, means it believes Object.is(Math.expm(x), -0) must always be false. But if x is -0, it's true, not false. That, in turn, means that the JIT believes array[Object.is(Math.expm(x), -0) * INDEX] must be array[0], no matter what INDEX is. But if x is -0, it'll…

Here's what I don't get. Why isn't Math.expm1 just implemented in JavaScript?

    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?
Post reply on HN