Live data from Hacker News

Exploiting the Math.expm1 typing bug in V8

abiondo.me

31–40 of 47 posts

Re: Exploiting the Math.expm1 typing bug in V8

#31
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?

The value -0 is actually correct, in the sense that e^x - 1 < 0 for all x < 0.

Re: Exploiting the Math.expm1 typing bug in V8

#32
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?

I don't know what the rules for JavaScript semantics are - this version will break if someone redefines `Math.pow`. Is that ok or does that break JavaScript semantics?

Re: Exploiting the Math.expm1 typing bug in V8

#33
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…

Author here - thanks for this! I was wondering why Wasm was RWX.

My 2c: I think that there will be risk of code injection as long as write_protect_code_memory in Heap is writable. Changing that flag will usually mess things up and crash writing to RX memory (CodeSpaceMemoryModificationScope won't switch to RW), but a well-crafted exploit might be able to get a fresh executable MemoryChunk (which will now be RWX). It's likely complex to exploit, but the incentive is that code injection is more reliable than ROP when targeting multiple builds (unless you build the chain dynamically, which is slow and often painful).

Re: Exploiting the Math.expm1 typing bug in V8

#34
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…

> 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?

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...

Re: Exploiting the Math.expm1 typing bug in V8

#35

Earlier quoted context omitted.

certainly V8 tries to be correct and this bug will be fixed. Chromium's position is "security in depth". They know it's impossible to have zero bugs therefore the entire architure assumes there will be bugs and tries to prevent them from causing any harm. This is also why there are roughly 10x less code execution bugs in chrome vs other browsers. same number of bugs overall but most lead nowhere

> Chromium's position is "security in depth". Yet the exploit in WebSQL a few weeks ago gave RCE from a component directly reachable by page scripts.

That must have been one of the bugs that lead somewhere.

Re: Exploiting the Math.expm1 typing bug in V8

#36
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?

Because expm is matrix exponentiation?

Re: Exploiting the Math.expm1 typing bug in V8

#37
post #14

Earlier quoted context omitted.

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…

> it might be an almost archetypical example of the "all bugs are security vulnerabilities" hypothesis 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.

Right, and it is right up there with https://www.blackhat.com/us-18/briefings/schedule/index.html, whose security value was unappreciated by some very smart folks.

Re: Exploiting the Math.expm1 typing bug in V8

#38

Earlier quoted context omitted.

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?

Because expm is matrix exponentiation?

expm1 is not expm, there is no expm in JavaScript (maybe you are thinking of MATLAB)

Re: Exploiting the Math.expm1 typing bug in V8

#39

Earlier quoted context omitted.

Because expm is matrix exponentiation?

expm1 is not expm, there is no expm in JavaScript (maybe you are thinking of MATLAB)

Do you have to write the Padé approximant by hand or has this already been sorted out in a popular library?

Re: Exploiting the Math.expm1 typing bug in V8

#40
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?

That's the wrong answer and your algorithm is bad. The point of expm1 is to be accurate for small arguments using the Taylor series or Chebeyshev polynomial representation.
Post reply on HN