mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-12 04:14:26 +00:00
Scripts: Math.random gets a new seed on every web page load
Each realm seeded Math.random from std's RandomState. On wasm32-unknown-unknown its keys come from stack and heap addresses, which are the same on every page load, so scripts in the web build drew the same sequence each time the page opened. The seed now comes from getrandom (crypto.getRandomValues in the browser, as crates/pdf/src/encrypt.rs already does), with RandomState kept as the fallback when it fails.
This commit is contained in:
Generated
+1
@@ -6589,6 +6589,7 @@ name = "vectorcraft-js"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"getrandom 0.4.3",
|
||||
"proptest",
|
||||
"unicode-general-category",
|
||||
]
|
||||
|
||||
@@ -12,6 +12,12 @@ workspace = true
|
||||
[dependencies]
|
||||
chrono = { workspace = true }
|
||||
unicode-general-category = "1"
|
||||
# The seed of Math.random (interp.rs).
|
||||
getrandom = "0.4"
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
# The browser's crypto.getRandomValues.
|
||||
getrandom = { version = "0.4", features = ["wasm_js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = { workspace = true }
|
||||
|
||||
@@ -605,6 +605,17 @@ fn math_functions_follow_the_special_cases() {
|
||||
truth("var n = 0; Math.max({valueOf: function () { n++; return NaN; }}, {valueOf: function () { n++; return 1; }}); n === 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_random_is_seeded_from_the_os_random_source() {
|
||||
// On wasm32-unknown-unknown `RandomState`'s keys come from memory addresses, which are the
|
||||
// same on every page load: the seed must come from the OS (the browser's crypto) instead.
|
||||
let src = include_str!("interp.rs");
|
||||
let Some(start) = src.find("fn random_seed()") else { panic!("interp.rs has no random_seed") };
|
||||
let body = &src[start..];
|
||||
let body = &body[..body.find("\n}").unwrap_or(body.len())];
|
||||
assert!(body.contains("getrandom::u64()"), "Math.random isn't seeded from the OS random source");
|
||||
}
|
||||
|
||||
// ---- JSON ----
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -305,13 +305,16 @@ fn index_keys_bytes(len: usize) -> usize {
|
||||
total
|
||||
}
|
||||
|
||||
/// A seed for `Math.random` that differs from realm to realm, from the standard library's
|
||||
/// randomly keyed hasher; never zero.
|
||||
/// A seed for `Math.random` that differs from realm to realm and from run to run, from the OS
|
||||
/// random source (the browser's crypto on the web), or the standard library's randomly keyed
|
||||
/// hasher when that fails; never zero.
|
||||
fn random_seed() -> u64 {
|
||||
use std::hash::{BuildHasher, Hasher};
|
||||
let mut h = std::collections::hash_map::RandomState::new().build_hasher();
|
||||
h.write_u64(0x9e37_79b9_7f4a_7c15);
|
||||
h.finish() | 1
|
||||
getrandom::u64().unwrap_or_else(|_| {
|
||||
let mut h = std::collections::hash_map::RandomState::new().build_hasher();
|
||||
h.write_u64(0x9e37_79b9_7f4a_7c15);
|
||||
h.finish()
|
||||
}) | 1
|
||||
}
|
||||
|
||||
/// The estimated bytes of a binding or property named by `len` code units.
|
||||
|
||||
Reference in New Issue
Block a user