From 01ea49adb82fcaf94bc507c8b5c09cffba316dda Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 13 Oct 2023 14:17:40 +0200 Subject: [PATCH 1/3] refactor browser entropy --- .../Directory.Build.props | 1 - .../sample/wasm/browser-advanced/Program.cs | 3 ++ src/mono/wasm/runtime/CMakeLists.txt | 4 +- src/mono/wasm/runtime/crypto.ts | 27 +++++++++++ src/mono/wasm/runtime/exports-binding.ts | 4 ++ src/mono/wasm/runtime/memory.ts | 5 ++- src/mono/wasm/runtime/strings.ts | 3 -- src/mono/wasm/wasm.proj | 3 +- .../libs/System.Native/pal_random.lib.js | 45 ------------------- src/native/minipal/random.c | 4 +- 10 files changed, 43 insertions(+), 56 deletions(-) create mode 100644 src/mono/wasm/runtime/crypto.ts delete mode 100644 src/native/libs/System.Native/pal_random.lib.js 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 ff1076521589f3..aea7e2e5d2af62 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 bec9cb3256b4c5..513074ee59228c 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)); + 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 6d939088d74314..e0b19bb4a9b691 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 00000000000000..a54757f3b8a231 --- /dev/null +++ b/src/mono/wasm/runtime/crypto.ts @@ -0,0 +1,27 @@ +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) { + 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 = new Uint8Array(targetBuffer, i, Math.min(bufferLength - i, batchedQuotaMax)); + crypto.getRandomValues(targetBatch); + } + + if (needsCopy) { + targetView.set(targetBuffer); + } +} diff --git a/src/mono/wasm/runtime/exports-binding.ts b/src/mono/wasm/runtime/exports-binding.ts index 8e856c35fa4c53..d7ef9ad945d14c 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 6de09caa18e9db..4a864cf347f630 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 f4cfa442386183..a03d478973a53f 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 818641337c2d27..1afe827d1da038 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 66cd390cccf268..00000000000000 --- 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 2a0f57d53163a5..bde7edc47d1174 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 From 29cef46868f65314214281c8e7c1088664ad819f Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 13 Oct 2023 14:45:49 +0200 Subject: [PATCH 2/3] feedback --- src/mono/wasm/runtime/crypto.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/crypto.ts b/src/mono/wasm/runtime/crypto.ts index a54757f3b8a231..4826c78890ec4a 100644 --- a/src/mono/wasm/runtime/crypto.ts +++ b/src/mono/wasm/runtime/crypto.ts @@ -5,7 +5,11 @@ import { isSharedArrayBuffer, localHeapViewU8 } from "./memory"; // https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues const batchedQuotaMax = 65536; -export function mono_wasm_browser_entropy(bufferPtr: number, bufferLength: number) { +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); @@ -18,10 +22,12 @@ export function mono_wasm_browser_entropy(bufferPtr: number, bufferLength: numbe // fill the targetBuffer in batches of batchedQuotaMax for (let i = 0; i < bufferLength; i += batchedQuotaMax) { const targetBatch = new Uint8Array(targetBuffer, i, Math.min(bufferLength - i, batchedQuotaMax)); - crypto.getRandomValues(targetBatch); + globalThis.crypto.getRandomValues(targetBatch); } if (needsCopy) { targetView.set(targetBuffer); } + + return 0; } From cbd288384bd3d8cbf76341d9902dcf91875569d5 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Fri, 13 Oct 2023 20:50:06 +0200 Subject: [PATCH 3/3] fix view --- src/mono/sample/wasm/browser-advanced/Program.cs | 2 +- src/mono/wasm/runtime/crypto.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/sample/wasm/browser-advanced/Program.cs b/src/mono/sample/wasm/browser-advanced/Program.cs index 513074ee59228c..53cbdbb2e3e0a2 100644 --- a/src/mono/sample/wasm/browser-advanced/Program.cs +++ b/src/mono/sample/wasm/browser-advanced/Program.cs @@ -15,7 +15,7 @@ public static int Main(string[] args) Console.WriteLine ("Hello, World!"); var rand = new Random(); - Console.WriteLine ("Today's lucky number is " + rand.Next(100)); + Console.WriteLine ("Today's lucky number is " + rand.Next(100) + " and " + Guid.NewGuid()); var start = DateTime.UtcNow; var timezonesCount = TimeZoneInfo.GetSystemTimeZones().Count; diff --git a/src/mono/wasm/runtime/crypto.ts b/src/mono/wasm/runtime/crypto.ts index 4826c78890ec4a..f396b5d833b147 100644 --- a/src/mono/wasm/runtime/crypto.ts +++ b/src/mono/wasm/runtime/crypto.ts @@ -21,7 +21,7 @@ export function mono_wasm_browser_entropy(bufferPtr: number, bufferLength: numbe // fill the targetBuffer in batches of batchedQuotaMax for (let i = 0; i < bufferLength; i += batchedQuotaMax) { - const targetBatch = new Uint8Array(targetBuffer, i, Math.min(bufferLength - i, batchedQuotaMax)); + const targetBatch = targetBuffer.subarray(i, i + Math.min(bufferLength - i, batchedQuotaMax)); globalThis.crypto.getRandomValues(targetBatch); }