Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: convert result of allocate_mappings from signed to unsigned #473

Merged
merged 6 commits into from
Dec 19, 2022

Conversation

nornagon
Copy link
Contributor

I haven't yet tested this because I don't have a good repro locally, but this might fix #412? I noticed that the rust bundle returns a signed integer, and I'm seeing it sometimes come back as negative (i.e. > 2GB). I think this maybe started being an issue with https://v8.dev/blog/4gb-wasm-memory.

@ochameau
Copy link
Contributor

Having a way to reproduce would be super helpful, even if this include setting up some other project.

@nornagon
Copy link
Contributor Author

I think that in order to reproduce this, you need to create enough SourceMapConsumers that the WASM memory exceeds 2GB. I'll see if I can write a test.

@nornagon
Copy link
Contributor Author

@ochameau i've added a somewhat crude test that reproduces the behavior. However, it seems to crash with "unreachable" instead of the RangeError when I put in this fix. I'm not sure exactly why that would be. But at least there's a reliable way to produce the error described in #412 now!

@nornagon
Copy link
Contributor Author

ah, rebuilt mappings.wasm with -g and it looks like the unreachable is an allocation error:

RuntimeError: unreachable
    at alloc::alloc::handle_alloc_error::h4d4fb786473fa992 (<anonymous>:wasm-function[54]:0xb65c)
    at parse_mappings (<anonymous>:wasm-function[23]:0xae24)

@nornagon
Copy link
Contributor Author

figured it out, my test for "are we over 2GB yet" was wrong, and the "unreachable" was happening when the 4GB limit was reached. the test is updated now to check the size of the WASM memory exceeds 3GB. Experimentally, exceeding 2GB wasn't enough because the mappings weren't being allocated past the 2GB byte mark. The test as written now fails without the fix, and passes with the fix.

@ochameau
Copy link
Contributor

Thanks a ton for being able to have a test for this!

Now, I'm wondering about the actual fix.
I'm new to Rust and Wasm so I don't know exactly what is going on.
It looks like once we reach some particular memory size, the address returned by rust's allocate_mapping function gets out of hands. This is typed as *mut u8. This isn't clear what it means from the Javascript point of view...

Your test highlights that the returned value starts being negative from the Javascript codebase around 2GB (i.e. 2 * 1024 * 1024 * 1024).
Why would that suddently happen? Is there really a 2GB limit for Wasm memory? If so why exports.memory.buffer.byteLength is able to go above this limit?
Also Javascript numbers support larger numbers. Why does that start being negative when going over 2GB? Could there be a typedef issue in rust code? Or is there something wrong in the convertion of *mut u8 to JS value?

I'm wondering if using unsigned right shift is actually correct. It sounds like we may just map to some arbitrary address, that is no longer negative, so it no longer throw. But it may not necessarily be the address we meant.

@nornagon
Copy link
Contributor Author

nornagon commented Nov 22, 2022 via email

@nornagon
Copy link
Contributor Author

Ah, I think CI might be hitting "unreachable" because possibly the runner has less RAM, so V8 chooses a smaller heap limit...?

@ochameau
Copy link
Contributor

Thanks a lot for the explanation.

It makes sense. I'm really surprised there isn't any intermediate layer to do that automatically.
It looks like we would benefit from using wasm-bindgen. I tried migrating to it. It isn't obvious, but it doesn't seem to apply the >>> 0 trick to allocate_mappings, whereas it does in many places...
rustwasm/wasm-bindgen#1401
rustwasm/wasm-bindgen#1388

There is still one suspicous behavior. This may be unrelated but may highlight some other leak.
When I log mappingBufPtr while running your test, I see that it overflows only once on 2816143240.
Without your patch, this value was negative and it throws on UInt8Array.
With your patch, the value is the one I mention and code keeps running.
The one thing that puzzles me is that later on, we get a lower value, 1114192 which used to be used earlier for that same pointer.

You can see that with the following patch:

diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js
index 4c9fbd3..ba58832 100644
--- a/lib/source-map-consumer.js
+++ b/lib/source-map-consumer.js
@@ -302,6 +302,11 @@ class BasicSourceMapConsumer extends SourceMapConsumer {
     const size = aStr.length;
 
     const mappingsBufPtr = this._wasm.exports.allocate_mappings(size) >>> 0;
+    if (globalThis.lastPtr != mappingsBufPtr) {
+      console.log(mappingsBufPtr)
+      globalThis.lastPtr = mappingsBufPtr;
+    }
+
     const mappingsBuf = new Uint8Array(
       this._wasm.exports.memory.buffer,
       mappingsBufPtr,
$ yarn test
yarn run v1.21.1
$ node test/run-tests.js
1114128
1114160
1114192
1119512
1114192
1114224
1114192
1114224
1114192
1114224
1114192
1119512
1114192
1114224
1114192
1114224
1114192
1114224
1114192
1118528
4768120
1410455680
2816143240
1114192
1114224
1114192

Why is there such an increase in memory and then we are able to allocate back in lower addresses?
Is that just expected entropy?

Anyway, feel free to ignore. I think your patch is super valuable as-is.
Could you just add a comment to explain the >>>0 ?
Regarding the test, while it was super handy to investigate, I'm not sure it is worth having in-tree.
It makes the overall test suite much slower to run...

@nornagon
Copy link
Contributor Author

@ochameau i think that's because the memory gets freed when the SourceMapConsumer is destroyed. Once the memory is freed, the allocator can use the lower addresses again.

@nornagon
Copy link
Contributor Author

BTW, we don't need to also do this for _mappingsPtr. It's fine for that one to be negative because we only ever use it to pass it back into wasm, which will correctly interpret the value as unsigned. We need it for mappingsBufPtr because we use it to index into a Uint8Array, i.e. the WASM memory object.

Agree that converting to wasm-bindgen would be a good idea!

Copy link
Contributor

@ochameau ochameau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay, I missed the notification about your newest revision...

Thanks a lot for having looked into this!

@ochameau ochameau merged commit 4e304db into mozilla:master Dec 19, 2022
surjikal added a commit to 42technologies/source-map that referenced this pull request Jan 9, 2023
fix: convert result of allocate_mappings from signed to unsigned (mozilla#473)
@alizubair76
Copy link

alizubair76 commented Sep 20, 2023

@nornagon , @ochameau When can we expect the release of this fix?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Memory access out of bounds
3 participants