From c29dd5f80e7c32836dfe6838bb582191dd154a19 Mon Sep 17 00:00:00 2001 From: Jurgis Date: Mon, 26 Oct 2020 22:41:31 +0200 Subject: [PATCH] Fix multithreaded wasm crash (solves #164) (#165) Signed-off-by: Joe Richey --- .travis.yml | 2 +- Cargo.toml | 3 ++- src/wasm-bindgen.rs | 31 +++++++++++++++++++------------ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index c931e47..3765508 100644 --- a/.travis.yml +++ b/.travis.yml @@ -102,7 +102,7 @@ jobs: rust: nightly install: - rustup target add wasm32-unknown-unknown - - cargo --list | egrep "^\s*deadlinks$" -q || cargo install cargo-deadlinks + - cargo install cargo-deadlinks - cargo deadlinks -V script: # Check that setting various features does not break the build diff --git a/Cargo.toml b/Cargo.toml index 5bb3f0d..8f43e16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ wasi = "0.10" stdweb = { version = "0.4.18", default-features = false, optional = true } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown", not(cargo_web)))'.dependencies] wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } +js-sys = { version = "0.3", optional = true } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown", not(cargo_web)))'.dev-dependencies] wasm-bindgen-test = "0.3.18" @@ -40,7 +41,7 @@ std = [] # Feature to enable fallback RDRAND-based implementation on x86/x86_64 rdrand = [] # Feature to enable JavaScript bindings on wasm32-unknown-unknown -js = ["stdweb", "wasm-bindgen"] +js = ["stdweb", "wasm-bindgen", "js-sys"] # Feature to enable custom RNG implementations custom = [] # Unstable feature to support being a libstd dependency diff --git a/src/wasm-bindgen.rs b/src/wasm-bindgen.rs index 4e78485..d48c7d3 100644 --- a/src/wasm-bindgen.rs +++ b/src/wasm-bindgen.rs @@ -10,11 +10,15 @@ use crate::Error; extern crate std; use std::thread_local; +use js_sys::Uint8Array; use wasm_bindgen::prelude::*; +// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +const BROWSER_CRYPTO_BUFFER_SIZE: usize = 256; + enum RngSource { Node(NodeCrypto), - Browser(BrowserCrypto), + Browser(BrowserCrypto, Uint8Array), } // JsValues are always per-thread, so we initialize RngSource for each thread. @@ -33,17 +37,18 @@ pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { return Err(Error::NODE_RANDOM_FILL_SYNC); } } - RngSource::Browser(n) => { - // see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues - // - // where it says: - // - // > A QuotaExceededError DOMException is thrown if the - // > requested length is greater than 65536 bytes. - for chunk in dest.chunks_mut(65536) { - if n.get_random_values(chunk).is_err() { + RngSource::Browser(crypto, buf) => { + // getRandomValues does not work with all types of WASM memory, + // so we initially write to browser memory to avoid exceptions. + for chunk in dest.chunks_mut(BROWSER_CRYPTO_BUFFER_SIZE) { + // The chunk can be smaller than buf's length, so we call to + // JS to create a smaller view of buf without allocation. + let sub_buf = buf.subarray(0, chunk.len() as u32); + + if crypto.get_random_values(&sub_buf).is_err() { return Err(Error::WEB_GET_RANDOM_VALUES); } + sub_buf.copy_to(chunk); } } }; @@ -63,7 +68,9 @@ fn getrandom_init() -> Result { (_, crypto) if !crypto.is_undefined() => crypto, _ => return Err(Error::WEB_CRYPTO), }; - return Ok(RngSource::Browser(crypto)); + + let buf = Uint8Array::new_with_length(BROWSER_CRYPTO_BUFFER_SIZE as u32); + return Ok(RngSource::Browser(crypto, buf)); } let crypto = MODULE.require("crypto").map_err(|_| Error::NODE_CRYPTO)?; @@ -84,7 +91,7 @@ extern "C" { type BrowserCrypto; #[wasm_bindgen(method, js_name = getRandomValues, catch)] - fn get_random_values(me: &BrowserCrypto, buf: &mut [u8]) -> Result<(), JsValue>; + fn get_random_values(me: &BrowserCrypto, buf: &Uint8Array) -> Result<(), JsValue>; #[wasm_bindgen(js_name = module)] static MODULE: NodeModule;