From 59c32bfe6170df78fb7852e6173cf554b9371fd5 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 5 Dec 2024 02:13:50 +0100 Subject: [PATCH] Account for `SharedArrayBuffer` not being available --- src/backends/wasm_js.rs | 95 +++++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 930e58be..8c05aa3c 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -6,10 +6,7 @@ extern crate std; use crate::Error; -use core::{ - mem::MaybeUninit, - sync::atomic::{AtomicU8, Ordering}, -}; +use core::mem::MaybeUninit; #[cfg(feature = "std")] use std::thread_local; @@ -18,42 +15,18 @@ pub use crate::util::{inner_u32, inner_u64}; #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] compile_error!("`wasm_js` backend can be enabled only for OS-less WASM targets!"); -use js_sys::{SharedArrayBuffer, Uint8Array, WebAssembly::Memory}; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; +use js_sys::{JsString, Uint8Array}; +use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; // Size of our temporary Uint8Array buffer used with WebCrypto methods // Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues const CRYPTO_BUFFER_SIZE: u16 = 256; -const MEMORY_KIND_UNINIT: u8 = 0; -const MEMORY_KIND_NOT_SHARED: u8 = 1; -const MEMORY_KIND_SHARED: u8 = 2; - -static MEMORY_KIND: AtomicU8 = AtomicU8::new(0); - pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { CRYPTO.with(|crypto| { let crypto = crypto.as_ref().ok_or(Error::WEB_CRYPTO)?; - let shared = loop { - break match MEMORY_KIND.load(Ordering::Relaxed) { - MEMORY_KIND_NOT_SHARED => false, - MEMORY_KIND_SHARED => true, - MEMORY_KIND_UNINIT => { - let memory: Memory = wasm_bindgen::memory().unchecked_into(); - let val = if memory.buffer().is_instance_of::() { - MEMORY_KIND_SHARED - } else { - MEMORY_KIND_NOT_SHARED - }; - MEMORY_KIND.store(val, Ordering::Relaxed); - continue; - } - _ => unreachable!(), - }; - }; - - if shared { + if is_sab() { // getRandomValues does not work with all types of WASM memory, // so we initially write to browser memory to avoid exceptions. let buf = Uint8Array::new_with_length(CRYPTO_BUFFER_SIZE.into()); @@ -88,7 +61,62 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { }) } +#[cfg(not(target_feature = "atomics"))] +fn is_sab() -> bool { + use core::sync::atomic::{AtomicU8, Ordering}; + + use js_sys::WebAssembly::Memory; + use js_sys::{Object, SharedArrayBuffer}; + use wasm_bindgen::JsCast; + + const MEMORY_KIND_UNINIT: u8 = 0; + const MEMORY_KIND_NOT_SHARED: u8 = 1; + const MEMORY_KIND_SHARED: u8 = 2; + + static MEMORY_KIND: AtomicU8 = AtomicU8::new(0); + + loop { + break match MEMORY_KIND.load(Ordering::Relaxed) { + MEMORY_KIND_NOT_SHARED => false, + MEMORY_KIND_SHARED => true, + MEMORY_KIND_UNINIT => { + let buffer = wasm_bindgen::memory().unchecked_into::().buffer(); + + // `SharedArrayBuffer` is only available with COOP & COEP. But even without its + // possible to create a shared `WebAssembly.Memory`, so we check for that via + // the constructor name. + // + // Keep in mind that `crossOriginIsolated` is not available on Node.js, in + // which case we can still use `instanceof` because `SharedArrayBuffer` is + // always available. + let shared = match CROSS_ORIGIN_ISOLATED.with(Option::clone) { + Some(true) | None => buffer.is_instance_of::(), + Some(false) => { + let constructor_name = Object::from(buffer).constructor().name(); + SHARED_ARRAY_BUFFER_NAME.with(|name| &constructor_name == name) + } + }; + + let val = if shared { + MEMORY_KIND_SHARED + } else { + MEMORY_KIND_NOT_SHARED + }; + MEMORY_KIND.store(val, Ordering::Relaxed); + continue; + } + _ => unreachable!(), + }; + } +} + +#[cfg(target_feature = "atomics")] +fn is_sab() -> bool { + true +} + #[wasm_bindgen] +#[rustfmt::skip] extern "C" { // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) type Crypto; @@ -100,4 +128,9 @@ extern "C" { fn get_random_values(this: &Crypto, buf: &Uint8Array) -> Result<(), JsValue>; #[wasm_bindgen(method, js_name = getRandomValues, catch)] fn get_random_values_ref(this: &Crypto, buf: &mut [u8]) -> Result<(), JsValue>; + // Returns the [`crossOriginIsolated`](https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated) global property. + #[wasm_bindgen(thread_local_v2, js_namespace = globalThis, js_name = crossOriginIsolated)] + static CROSS_ORIGIN_ISOLATED: Option; + #[wasm_bindgen(thread_local_v2, static_string)] + static SHARED_ARRAY_BUFFER_NAME: JsString = "SharedArrayBuffer"; }