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

Chrome requires unsafe-eval to run webassembly code #196

Closed
jennatuckerdeveloper opened this issue May 1, 2019 · 32 comments
Closed

Chrome requires unsafe-eval to run webassembly code #196

jennatuckerdeveloper opened this issue May 1, 2019 · 32 comments

Comments

@jennatuckerdeveloper
Copy link

Hello.

We have a Docker container that uses Nginx to serve static files of a React app that uses libsodium-wrappers. In Chrome, versions up to .5 work without error, and versions over .7 throw an error as soon as the page loads on libsodium.js. We have sandboxed this issue, and versions under .7 work properly, as well as all versions when we build and serve them without using Docker. The error does not occur in Firefox.

Screen Shot 2019-04-29 at 11 14 19 AM

Screen Shot 2019-04-29 at 11 14 11 AM

Any help resolving this issue would be greatly appreciated!

@jennatuckerdeveloper
Copy link
Author

We were able to get around this error by loosening our Content Security Policy to include:

script-src 'self' 'unsafe-eval'
connect-src data:
object-src: 'self'

Could you address the need for these headers in the .7 and up versions of libsodium.js? Specifically, script-src 'unsafe-eval' raises considerable security concerns on the frontend.

@jedisct1
Copy link
Owner

jedisct1 commented May 1, 2019

@jedisct1 jedisct1 closed this as completed May 1, 2019
@jennatuckerdeveloper
Copy link
Author

jennatuckerdeveloper commented May 2, 2019

I think this issue was closed, because the linked discussion indicates that libsodium.js errors, falls back to asm.js, and then continues to load and work properly. That is not the case in current versions of Chrome. The libsodium.js library will only load if the CSP includes script-src unsafe-eval. This is a significant security issue. Trying to get crypto means breaking your CSP. Is there currently a known way to get libsodium.js to load in Chrome without using this faulty CSP header?

@jedisct1
Copy link
Owner

jedisct1 commented May 2, 2019

@jedisct1 jedisct1 reopened this May 2, 2019
@jedisct1 jedisct1 changed the title Docker container with libsodium.js cannot load in Chrome Chrome requires unsafe-eval to run webassembly code May 2, 2019
@jennatuckerdeveloper
Copy link
Author

Someone commented in Feb 2018 that the libsodium.js load could fallback from wasm.js to asm.js and load despite the error:

emscripten-core/emscripten#5911 (comment)

A request to get rid of the error message was placed from that thread. I think that state of that bug has changed. The error now stops libsodium.js from loading. Is it possible that handling this error better could allow this fallback from wasm.js to asm.js to take place and libsodium.js to load?

@jedisct1
Copy link
Owner

jedisct1 commented May 3, 2019

It's supposed to be the case: https://github.com/jedisct1/libsodium/blob/8a1ac8e11fda8b77af0d9073f57475f9f66cb509/dist-build/emscripten.sh#L99-L123

If the module can't be compiled or doesn't work as expected (as originally seen on a specific iOS version), the fallback JS code is supposed to be run. Any exception should trigger the fallback code.

But apparently not :( Can you help investigate why?

That being said, I'm not sure that it's still worth keeping the Javascript version around.

Besides IE, all the web browsers support it now.

@jennatuckerdeveloper
Copy link
Author

jennatuckerdeveloper commented May 3, 2019

I sandboxed this to try and get the same result the above user mentioned of an error AND a load. I cannot get that result. I can even put this minimal code sample in an index.html file in VS Code, open it in Chrome, and open the inspector. With this 'unsafe-eval', it will console.log the hash. Without 'unsafe-eval', it will log the error and not load.

<!DOCTYPE html>
<html lang="en">
  <head>
        <meta 
        http-equiv="Content-Security-Policy" 
         content="
         script-src 'self' 'unsafe-inline' 'unsafe-eval'
         "> 
       </meta>
  </head>
  <body>
  </script>
  <script>
    window.sodium = {
        onload: function (sodium) {
            let h = sodium.crypto_generichash(64, sodium.from_string('test'));
            console.log(sodium.to_hex(h));
        }
    };
    </script>
    <script src="sodium.js" async></script>
  </body>
  </html>

I can't think of anything that could be different between my code and that of the user who got the result of the error and the load. Anything other than the passing of a year+ and changes to Chrome and perhaps the libraries. I think getting the fallback to load will solve this issue... but why is it not loading... I am not sure. I haven't been able to come up with ways to investigate this part or try any hacks to find out. We don't have anyone particularly experienced with Web Assembly on our team. Right now, we can't ship, because we cannot load libsodium.js in Chrome without a broken, vulnerable CSP. So we are certainly open to being pointed in any direction we can take to figure this out and load libsodium.js without eval().

That being said, I'm not sure that it's still worth keeping the Javascript version around. If it will let us load libsodium.js in Chrome without using usafe-eval, then it's worth holding onto as a fallback for weird cases like this one. It's not that it does't work, it's that bringing in libsodium.js only works if you permit a serious security vulnerability. That's paradoxical for those using libsodium.

@jedisct1
Copy link
Owner

jedisct1 commented May 3, 2019

Can you wait for the Promise?

I'm not sure that the onload thing is still supposed to work.

@jennatuckerdeveloper
Copy link
Author

In our actual application, we were using async / await sodium.ready. I can try to make a minimal code sample for that case to show that it's the same result. I tried this to double-check and strip out everything and get a minimal sandbox, and because the user who got libsodium.js to load specifically said the check was done using onload. Trying to pull away anything that could be the issue. From what I can tell, it's down to libsodium.js versions of .7 and up, Chrome, and the CSP script-src.

@jennatuckerdeveloper
Copy link
Author

Thanks for taking the time to look into this! We're really looking forward to getting past this block.

@jennatuckerdeveloper
Copy link
Author

Also, to be clear, the onload does work. If you add unsafe-eval. So the above code sample should run, and then error if you remove 'unsafe-eval`.

@jennatuckerdeveloper
Copy link
Author

jennatuckerdeveloper commented May 3, 2019

This code uses promises and should show the exact same pattern of error. I opened the index.html from VS Code in the Chrome browser and opened the inspector to see the logs and errors. If you remove the script-src unsafe-eval, the error will be thrown and the console.logs in the .then() will not log. If you add that CSP header, the then() will run and the values will log.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
        <meta 
        http-equiv="Content-Security-Policy" 
         content="
         script-src 'self' 'unsafe-inline' 'unsafe-eval'
         "> 
       </meta>
  </head>
  <body>
    <script src="sodium.js"></script>
    <script src="someCode.js"></script>
  </body>
  </html>

someCode.js

sodium.ready.then(() => {
    console.log('loaded')
    console.log(sodium)
}
)

I got the sodium.js script from the dist/browsers/sodium.js path on the repo.

@jennatuckerdeveloper
Copy link
Author

I am wondering if fixing the way this error gets handled might be the solution. In this request, the assumption is that the fallback works. I have not been able to get the fallback to work:

emscripten-core/emscripten#6255

@bas-d
Copy link
Contributor

bas-d commented May 4, 2019

I think you're right. I've compiled it with the uglifier disabled, then you can see the error is caused by an uncaught error in abort.

Screenshot 2019-05-04 at 16 29 26

Adding the .catch(function() {}); in wrap-template.js after this promise:

var ready = libsodiumModule.ready.then(function () {
libsodium = libsodiumModule;
function libsodiumInit() {
if (libsodium._sodium_init() !== 0) {
throw new Error("libsodium was not correctly initialized.");
}
/*{{exports_here}}*/
}
/* Test to make sure everything works. If not, switch to asm.js fallback. */
try {
libsodiumInit();
var message = new Uint8Array([98, 97, 108, 108, 115]);
var nonce = exports.randombytes_buf(exports.crypto_secretbox_NONCEBYTES);
var key = exports.randombytes_buf(exports.crypto_secretbox_KEYBYTES);
var encrypted = exports.crypto_secretbox_easy(message, nonce, key);
var decrypted = exports.crypto_secretbox_open_easy(encrypted, nonce, key);
if (exports.memcmp(message, decrypted)) {
return;
}
}
catch (_) {}
libsodium.useBackupModule();
libsodiumInit();
});
doesn't remove the error, but it does cause the asm.js to load. I tried that because thought it would maybe catch the abort:

Screenshot 2019-05-04 at 16 25 37

So far so good, but if I re-enable the uglifier, it doesn't work anymore. My guess is that at some point an error is thrown by abort, which isn't caught anywhere so the program just exists. Depending on the moment it is thrown, useBackupMode is either fully loaded or not. If not, you see the TypeError: r.useBackupModule is not a function or C is not a function if the uglifier is enabled.

The root cause seems to be this: https://github.com/emscripten-core/emscripten/blob/59c14a2e5411ae8516a9dbe8222841d78ccc4998/src/postamble.js#L422.

@jennatuckerdeveloper
Copy link
Author

Thanks for looking into this! Any hope of us getting a fix in the near future?

@bas-d
Copy link
Contributor

bas-d commented May 7, 2019

I've submitted a PR to emscripten, emscripten-core/emscripten#8558 that catches the unhandled promise rejection in abort. #198 fixes some other errors. It still shows the error, but does work. Setting drop_console=false in

UGLIFY = npx uglifyjs --mangle --compress drop_console=true,passes=3 --
makes the error disappear and shows a more accurate warning:
Screenshot 2019-05-07 at 17 34 35. @jedisct1 I don't know if changing drop_console causes other issues?

@jedisct1
Copy link
Owner

jedisct1 commented May 7, 2019

drop_console=false may increase the file size, but it's probably completely negligible.

@jennatuckerdeveloper
Copy link
Author

I noticed some failing CI tests on this one emscripten-core/emscripten#8558 . Were those failures predicted with the changes that had to be made to get asm.js to load?

@bas-d
Copy link
Contributor

bas-d commented May 7, 2019

Yeah I noticed it too. Not in my predictions, but too be honest I didn't know anything about emscripten before this weekend, so they're probably not very accurate ;). I'll try to run the tests locally to figure out if the changes caused them.

@jennatuckerdeveloper
Copy link
Author

Hi! It looks like there has been a lot of great progress on this fix. This is very exciting for us! Thanks for all your hard work! I couldn't quite follow the various PRs attached to this. I wanted to just check, is it thought that the fix is in place already? Or are some of the necessary changes still pending? Thanks again for all the hard work on this! We're really stoked to be able to ship some new file streaming features.

@bas-d
Copy link
Contributor

bas-d commented May 15, 2019

No problem! I'm also developing a browser extension that relies on libsodium, so it was bothering me for a while as well.

I think #198 still needs to be merged and then it should work. You'll might still see an error, but this now shouldn't prevent the asm from loading. emscripten-core/emscripten#8558 should make it easier to properly catch that error in the future. Whenever that one's finished and incorporated into the emscripten master branch, I'll submit a new PR to the libsodium repo to fix. For now you could use a workaround like:

window.addEventListener("unhandledrejection", event => {
    if (event.reason === "abort({}). Build with -s ASSERTIONS=1 for more info.") {
        event.preventDefault()
    }
})

This prevents the error from showing in the console.
In case you do want to use the wasm: wasm-eval instead of unsafe-eval also seems to work.

@jennatuckerdeveloper
Copy link
Author

Thanks! Those workarounds don't seem to work in our sandbox.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
      <script type="text/javascript">
      window.addEventListener("unhandledrejection", event => {
        if (event.reason === "abort({}). Build with -s ASSERTIONS=1 for more info.") {
            event.preventDefault()
        }
      })
        </script>
        <meta 
        http-equiv="Content-Security-Policy" 
         content="
         script-src 'self' 'unsafe-inline' 'wasm-eval'
         "> 
       </meta>
  </head>
  <body>
    <script src="sodium.js"></script>
    <script src="someCode.js"></script>
  </body>
  </html>

someCode.js

sodium.ready.then(() => {
    console.log('loaded')
    console.log(sodium)
}
)

I was under the impression from the discussion online that wasm-eval was not added yet.

Perhaps we would need to recompile libsodium for the other workaround.

It seems like a lot of good progress is being made on this either way! So we can probably just sit tight.

@bas-d
Copy link
Contributor

bas-d commented May 15, 2019

Ah sorry, I think wasm-eval only works for browser extensions and had in my head that you were working on one.
Recompiling is necessary, but I've just pushed a recompiled version to the asm branch on my fork if you can't wait: https://github.com/bas-d/libsodium.js/tree/asm/dist

@jennatuckerdeveloper
Copy link
Author

Wow! Thanks! This does work in our sandbox. So we have a way forward regardless!

@jennatuckerdeveloper
Copy link
Author

@bas-d Your PR was merged yesterday! emscripten-core/emscripten#8558 I think you said there was one more PR to go to libsodium.js after that? Then perhaps I can confirm the fix, and we can close this!

@bas-d
Copy link
Contributor

bas-d commented May 28, 2019

Yes to get rid of the error we would have to catch the promise rejection. This could be done by overriding https://emscripten.org/docs/api_reference/module.html#Module.instantiateWasm here: https://github.com/jedisct1/libsodium/blob/af6df5f4a591ea3216d80fb34694a3c292e9b51e/dist-build/emscripten.sh#L99. I think there's two options then:

  1. Check if the instantiateAsync method is available, call it and catch the error. If the error is thrown because wasm couldn't be loaded, load asm. This method was implemented with Unhandled rejection emscripten-core/emscripten#8558, but is not yet available in the latest branch of emsdk. The advantage is that it only requires a few extra lines of code, but the disadvantage is that you rely on an emscripten internal method. If they would change it in the future, things might break, but perhaps this can be sufficiently mitigated by checking if the function exists.
  2. Option two is to copy the needed code from emscripten's preamble.js (instantiateAsync and the other functions it uses), so we don't have to rely on the internal function.

@jedisct1, what approach would you prefer (if any)? I can make a PR for either.

@jedisct1
Copy link
Owner

Rather than introduce hacks that will soon be useless and since your fix was merged into Emscripten, we can directly use the incoming branch of Emscripten until they tag a new release.

@jennatuckerdeveloper
Copy link
Author

Hi! I just tested the sodium.js file from the dist folder, and it looks like the updates to master have very likely resolved this issue. Can you give me a sense of when libsodium.js and libsodium-wrappers.js are likely to see a new release so this fix becomes available through the npm packages? Thanks for the work that's been done on this bug!

@herrbuerger
Copy link

We're still running into this issue with the latest versions of the libsodium.js and libsodium-wrappers.js.

When we add unsafe_eval to our CSP everything is working fine, as soon as we remove it, we end up with the following error:

Uncaught (in promise) abort(CompileError: WebAssembly.instantiate(): Wasm code generation disallowed by embedder). Build with -s ASSERTIONS=1 for more info.

@jmtucker7 did you get this working on your side?

@jedisct1
Copy link
Owner

Looks like unsafe-eval is still needed https://dev.to/aaronpowell/using-webassembly-with-csp-headers-597o

@wisefool769
Copy link

wisefool769 commented Mar 1, 2023

Is there a way to configure the library to fall back to asm.js and stop throwing a CSP error about wasm?
Also @jedisct1, should the issue be re-opened given that unsafe-eval is still needed?

@chickahoona
Copy link

I just switched to libsodium and had nearly a heart attack when that error was reported by sentry. I thought i broke everything but it seems still to work, only an ugly error. But still, any "workaround" that would silence the error without the need for 'unsafe-eval' would be well appreciated. (Sorry for the noise, but I hope hope after 4 months its okay to ask. Btw, I'd be happy to donate for a 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

No branches or pull requests

6 participants