diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index ff1076521589f..aea7e2e5d2af6 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -217,7 +217,6 @@ - diff --git a/src/mono/sample/wasm/browser-advanced/Program.cs b/src/mono/sample/wasm/browser-advanced/Program.cs index bec9cb3256b4c..53cbdbb2e3e0a 100644 --- a/src/mono/sample/wasm/browser-advanced/Program.cs +++ b/src/mono/sample/wasm/browser-advanced/Program.cs @@ -14,6 +14,9 @@ public static int Main(string[] args) { Console.WriteLine ("Hello, World!"); + var rand = new Random(); + Console.WriteLine ("Today's lucky number is " + rand.Next(100) + " and " + Guid.NewGuid()); + var start = DateTime.UtcNow; var timezonesCount = TimeZoneInfo.GetSystemTimeZones().Count; var end = DateTime.UtcNow; diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt index 6d939088d7431..e0b19bb4a9b69 100644 --- a/src/mono/wasm/runtime/CMakeLists.txt +++ b/src/mono/wasm/runtime/CMakeLists.txt @@ -34,8 +34,8 @@ target_link_libraries(dotnet.native ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a) set_target_properties(dotnet.native PROPERTIES - LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.pre.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.extpost.js;" - LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-link.rsp ${CONFIGURATION_LINK_FLAGS} --pre-js ${NATIVE_BIN_DIR}/src/es6/dotnet.es6.pre.js --js-library ${NATIVE_BIN_DIR}/src/es6/dotnet.es6.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_random.lib.js --extern-post-js ${NATIVE_BIN_DIR}/src/es6/dotnet.es6.extpost.js " + LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.pre.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.lib.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.extpost.js;" + LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-link.rsp ${CONFIGURATION_LINK_FLAGS} --pre-js ${NATIVE_BIN_DIR}/src/es6/dotnet.es6.pre.js --js-library ${NATIVE_BIN_DIR}/src/es6/dotnet.es6.lib.js --extern-post-js ${NATIVE_BIN_DIR}/src/es6/dotnet.es6.extpost.js " RUNTIME_OUTPUT_DIRECTORY "${NATIVE_BIN_DIR}") set(ignoreMeWasmOptFlags "${CONFIGURATION_WASM_OPT_FLAGS}") diff --git a/src/mono/wasm/runtime/crypto.ts b/src/mono/wasm/runtime/crypto.ts new file mode 100644 index 0000000000000..f396b5d833b14 --- /dev/null +++ b/src/mono/wasm/runtime/crypto.ts @@ -0,0 +1,33 @@ +import { isSharedArrayBuffer, localHeapViewU8 } from "./memory"; + +// batchedQuotaMax is the max number of bytes as specified by the api spec. +// If the byteLength of array is greater than 65536, throw a QuotaExceededError and terminate the algorithm. +// https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues +const batchedQuotaMax = 65536; + +export function mono_wasm_browser_entropy(bufferPtr: number, bufferLength: number): number { + if (!globalThis.crypto || !globalThis.crypto.getRandomValues) { + return -1; + } + + const memoryView = localHeapViewU8(); + const targetView = memoryView.subarray(bufferPtr, bufferPtr + bufferLength); + + // When threading is enabled, Chrome doesn't want SharedArrayBuffer to be passed to crypto APIs + const needsCopy = isSharedArrayBuffer(memoryView.buffer); + const targetBuffer = needsCopy + ? new Uint8Array(bufferLength) + : targetView; + + // fill the targetBuffer in batches of batchedQuotaMax + for (let i = 0; i < bufferLength; i += batchedQuotaMax) { + const targetBatch = targetBuffer.subarray(i, i + Math.min(bufferLength - i, batchedQuotaMax)); + globalThis.crypto.getRandomValues(targetBatch); + } + + if (needsCopy) { + targetView.set(targetBuffer); + } + + return 0; +} diff --git a/src/mono/wasm/runtime/exports-binding.ts b/src/mono/wasm/runtime/exports-binding.ts index 8e856c35fa4c5..d7ef9ad945d14 100644 --- a/src/mono/wasm/runtime/exports-binding.ts +++ b/src/mono/wasm/runtime/exports-binding.ts @@ -35,6 +35,7 @@ import { mono_wasm_typed_array_to_array_ref } from "./net6-legacy/js-to-cs"; import { mono_wasm_typed_array_from_ref } from "./net6-legacy/buffers"; import { mono_wasm_get_culture_info } from "./hybrid-globalization/culture-info"; import { mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year } from "./hybrid-globalization/locales"; +import { mono_wasm_browser_entropy } from "./crypto"; // the JS methods would be visible to EMCC linker and become imports of the WASM module @@ -98,6 +99,9 @@ export const mono_wasm_imports = [ mono_wasm_set_entrypoint_breakpoint, mono_wasm_event_pipe_early_startup_callback, + // src/native/minipal/random.c + mono_wasm_browser_entropy, + // corebindings.c mono_wasm_release_cs_owned_object, mono_wasm_bind_js_function, diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 6de09caa18e9d..4a864cf347f63 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -401,7 +401,10 @@ export function receiveWorkerHeapViews() { const sharedArrayBufferDefined = typeof SharedArrayBuffer !== "undefined"; export function isSharedArrayBuffer(buffer: any): buffer is SharedArrayBuffer { - if (!MonoWasmThreads) return false; // this condition should be eliminated by rollup on non-threading builds + if (!MonoWasmThreads) return false; + // BEWARE: In some cases, `instanceof SharedArrayBuffer` returns false even though buffer is an SAB. + // Patch adapted from https://github.com/emscripten-core/emscripten/pull/16994 + // See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag return sharedArrayBufferDefined && buffer[Symbol.toStringTag] === "SharedArrayBuffer"; } diff --git a/src/mono/wasm/runtime/strings.ts b/src/mono/wasm/runtime/strings.ts index f4cfa44238618..a03d478973a53 100644 --- a/src/mono/wasm/runtime/strings.ts +++ b/src/mono/wasm/runtime/strings.ts @@ -242,9 +242,6 @@ function stringToMonoStringNewRoot(string: string, result: WasmRoot) // When threading is enabled, TextDecoder does not accept a view of a // SharedArrayBuffer, we must make a copy of the array first. // See https://github.com/whatwg/encoding/issues/172 -// BEWARE: In some cases, `instanceof SharedArrayBuffer` returns false even though buffer is an SAB. -// Patch adapted from https://github.com/emscripten-core/emscripten/pull/16994 -// See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag export function viewOrCopy(view: Uint8Array, start: CharPtr, end: CharPtr): Uint8Array { // this condition should be eliminated by rollup on non-threading builds const needsCopy = isSharedArrayBuffer(view.buffer); diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 818641337c2d2..1afe827d1da03 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -424,8 +424,7 @@ diff --git a/src/native/libs/System.Native/pal_random.lib.js b/src/native/libs/System.Native/pal_random.lib.js deleted file mode 100644 index 66cd390cccf26..0000000000000 --- a/src/native/libs/System.Native/pal_random.lib.js +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -const DotNetEntropyLib = { - $DOTNETENTROPY: { - getBatchedRandomValues: function (buffer, bufferLength) { - // batchedQuotaMax is the max number of bytes as specified by the api spec. - // If the byteLength of array is greater than 65536, throw a QuotaExceededError and terminate the algorithm. - // https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues - const batchedQuotaMax = 65536; - - // Chrome doesn't want SharedArrayBuffer to be passed to crypto APIs - const needTempBuf = typeof SharedArrayBuffer !== 'undefined' && Module.HEAPU8.buffer instanceof SharedArrayBuffer; - // if we need a temporary buffer, make one that is big enough and write into it from the beginning - // otherwise, use the wasm instance memory and write at the given 'buffer' pointer offset. - const buf = needTempBuf ? new ArrayBuffer(bufferLength) : Module.HEAPU8.buffer; - const offset = needTempBuf ? 0 : buffer; - // for modern web browsers - // map the work array to the memory buffer passed with the length - for (let i = 0; i < bufferLength; i += batchedQuotaMax) { - const view = new Uint8Array(buf, offset + i, Math.min(bufferLength - i, batchedQuotaMax)); - crypto.getRandomValues(view) - } - if (needTempBuf) { - // copy data out of the temporary buffer into the wasm instance memory - const heapView = new Uint8Array(Module.HEAPU8.buffer, buffer, bufferLength); - heapView.set(new Uint8Array(buf)); - } - } - }, - dotnet_browser_entropy: function (buffer, bufferLength) { - // check that we have crypto available - if (typeof crypto === 'object' && typeof crypto['getRandomValues'] === 'function') { - DOTNETENTROPY.getBatchedRandomValues(buffer, bufferLength) - return 0; - } else { - // we couldn't find a proper implementation, as Math.random() is not suitable - // instead of aborting here we will return and let managed code handle the message - return -1; - } - }, -}; - -autoAddDeps(DotNetEntropyLib, '$DOTNETENTROPY') -mergeInto(LibraryManager.library, DotNetEntropyLib) diff --git a/src/native/minipal/random.c b/src/native/minipal/random.c index 2a0f57d53163a..bde7edc47d117 100644 --- a/src/native/minipal/random.c +++ b/src/native/minipal/random.c @@ -69,11 +69,11 @@ int32_t minipal_get_cryptographically_secure_random_bytes(uint8_t* buffer, int32 assert(buffer != NULL); #ifdef __EMSCRIPTEN__ - extern int32_t dotnet_browser_entropy(uint8_t* buffer, int32_t bufferLength); + extern int32_t mono_wasm_browser_entropy(uint8_t* buffer, int32_t bufferLength); static bool sMissingBrowserCrypto; if (!sMissingBrowserCrypto) { - int32_t bff = dotnet_browser_entropy(buffer, bufferLength); + int32_t bff = mono_wasm_browser_entropy(buffer, bufferLength); if (bff == -1) sMissingBrowserCrypto = true; else