Repository: tetratelabs/wazero
Pull request #736 (2022-08-06 08:17:49)

Switches default random source to deterministic
This changes the default random source to provide deterministic values
similar to how nanotime and walltime do. This also prevents any worries
about if wasm can deplete the host's underlying source of entropy.


Patch:
diff --git a/config.go b/config.go
index 99bef862..cee38053 100644
--- a/config.go
+++ b/config.go
@@ -565,12 +565,15 @@ type ModuleConfig interface {
 	// See WithNanosleep
 	WithSysNanosleep() ModuleConfig
 
-	// WithRandSource configures a source of random bytes. Defaults to crypto/rand.Reader.
+	// WithRandSource configures a source of random bytes. Defaults to return a
+	// deterministic source. You might override this with crypto/rand.Reader
 	//
-	// This reader is most commonly used by the functions like "random_get" in "wasi_snapshot_preview1" or "seed" in
-	// AssemblyScript standard "env" although it could be used by functions imported from other modules.
+	// This reader is most commonly used by the functions like "random_get" in
+	// "wasi_snapshot_preview1", "seed" in AssemblyScript standard "env", and
+	// "getRandomData" when runtime.GOOS is "js".
 	//
-	// Note: The caller is responsible to close any io.Reader they supply: It is not closed on api.Module Close.
+	// Note: The caller is responsible to close any io.Reader they supply: It
+	// is not closed on api.Module Close.
 	WithRandSource(io.Reader) ModuleConfig
 }
 
diff --git a/internal/platform/crypto.go b/internal/platform/crypto.go
new file mode 100644
index 00000000..c141f00f
--- /dev/null
+++ b/internal/platform/crypto.go
@@ -0,0 +1,17 @@
+package platform
+
+import (
+	"io"
+	"math/rand"
+)
+
+// seed is a fixed seed value for NewFakeRandSource.
+//
+// Trivia: While arbitrary, 42 was chosen as it is the "Ultimate Answer" in
+// the Douglas Adams novel "The Hitchhiker's Guide to the Galaxy."
+const seed = int64(42)
+
+// NewFakeRandSource returns a deterministic source of random values.
+func NewFakeRandSource() io.Reader {
+	return rand.New(rand.NewSource(seed))
+}
diff --git a/internal/sys/sys.go b/internal/sys/sys.go
index 663773e0..7b1e405c 100644
--- a/internal/sys/sys.go
+++ b/internal/sys/sys.go
@@ -2,7 +2,6 @@ package sys
 
 import (
 	"context"
-	"crypto/rand"
 	"errors"
 	"fmt"
 	"io"
@@ -118,7 +117,7 @@ func (c *Context) FS(ctx context.Context) *FSContext {
 	return c.fsc
 }
 
-// RandSource is a source of random bytes and defaults to crypto/rand.Reader.
+// RandSource is a source of random bytes and defaults to a deterministic source.
 // see wazero.ModuleConfig WithRandSource
 func (c *Context) RandSource() io.Reader {
 	return c.randSource
@@ -153,8 +152,10 @@ func NewContext(
 	stdin io.Reader,
 	stdout, stderr io.Writer,
 	randSource io.Reader,
-	walltime *sys.Walltime, walltimeResolution sys.ClockResolution,
-	nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution,
+	walltime *sys.Walltime,
+	walltimeResolution sys.ClockResolution,
+	nanotime *sys.Nanotime,
+	nanotimeResolution sys.ClockResolution,
 	nanosleep *sys.Nanosleep,
 	fs fs.FS,
 ) (sysCtx *Context, err error) {
@@ -187,7 +188,7 @@ func NewContext(
 	}
 
 	if randSource == nil {
-		sysCtx.randSource = rand.Reader
+		sysCtx.randSource = platform.NewFakeRandSource()
 	} else {
 		sysCtx.randSource = randSource
 	}


Test patch:
diff --git a/assemblyscript/assemblyscript_test.go b/assemblyscript/assemblyscript_test.go
index a23895fc..7f10dcfe 100644
--- a/assemblyscript/assemblyscript_test.go
+++ b/assemblyscript/assemblyscript_test.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	_ "embed"
+	"encoding/hex"
 	"errors"
 	"io"
 	"strings"
@@ -123,8 +124,7 @@ func TestAbort_Error(t *testing.T) {
 }
 
 func TestSeed(t *testing.T) {
-	b := []byte{0, 1, 2, 3, 4, 5, 6, 7}
-	mod, r, log := requireProxyModule(t, NewFunctionExporter(), wazero.NewModuleConfig().WithRandSource(bytes.NewReader(b)))
+	mod, r, log := requireProxyModule(t, NewFunctionExporter(), wazero.NewModuleConfig())
 	defer r.Close(testCtx)
 
 	ret, err := mod.ExportedFunction(functionSeed).Call(testCtx)
@@ -132,11 +132,11 @@ func TestSeed(t *testing.T) {
 	require.Equal(t, `
 --> proxy.seed()
 	==> env.~lib/builtins/seed()
-	<== (7.949928895127363e-275)
-<-- (7.949928895127363e-275)
+	<== (4.958153677776298e-175)
+<-- (4.958153677776298e-175)
 `, "\n"+log.String())
 
-	require.Equal(t, b, u64.LeBytes(ret[0]))
+	require.Equal(t, "538c7f96b164bf1b", hex.EncodeToString(u64.LeBytes(ret[0])))
 }
 
 func TestSeed_error(t *testing.T) {
diff --git a/config_test.go b/config_test.go
index a17b2711..729450fe 100644
--- a/config_test.go
+++ b/config_test.go
@@ -2,6 +2,7 @@ package wazero
 
 import (
 	"context"
+	"crypto/rand"
 	"io"
 	"io/fs"
 	"math"
@@ -479,6 +480,23 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				testFS2, // fs
 			),
 		},
+		{
+			name:  "WithRandSource",
+			input: base.WithRandSource(rand.Reader),
+			expected: requireSysContext(t,
+				math.MaxUint32, // max
+				nil,            // args
+				nil,            // environ
+				nil,            // stdin
+				nil,            // stdout
+				nil,            // stderr
+				rand.Reader,    // randSource
+				&wt, 1,         // walltime, walltimeResolution
+				&nt, 1, // nanotime, nanotimeResolution
+				nil, // nanosleep
+				nil, // fs
+			),
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go
index b4fc8b5c..1779dd36 100644
--- a/internal/sys/sys_test.go
+++ b/internal/sys/sys_test.go
@@ -3,7 +3,6 @@ package sys
 import (
 	"bytes"
 	"context"
-	"crypto/rand"
 	"io"
 	"testing"
 	"time"
@@ -55,7 +54,7 @@ func TestDefaultSysContext(t *testing.T) {
 	require.Zero(t, sysCtx.Nanotime(testCtx)) // See above on functions.
 	require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution())
 	require.Equal(t, &ns, sysCtx.nanosleep)
-	require.Equal(t, rand.Reader, sysCtx.RandSource())
+	require.Equal(t, platform.NewFakeRandSource(), sysCtx.RandSource())
 	require.Equal(t, NewFSContext(testfs.FS{}), sysCtx.FS(testCtx))
 }
 
diff --git a/wasi_snapshot_preview1/random_test.go b/wasi_snapshot_preview1/random_test.go
index ee778461..53b42147 100644
--- a/wasi_snapshot_preview1/random_test.go
+++ b/wasi_snapshot_preview1/random_test.go
@@ -12,8 +12,7 @@ import (
 )
 
 func Test_randomGet(t *testing.T) {
-	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
-		WithRandSource(deterministicRandomSource()))
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
 	defer r.Close(testCtx)
 
 	expectedMemory := []byte{
@@ -42,8 +41,7 @@ func Test_randomGet(t *testing.T) {
 }
 
 func Test_randomGet_Errors(t *testing.T) {
-	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig().
-		WithRandSource(deterministicRandomSource()))
+	mod, r, log := requireProxyModule(t, wazero.NewModuleConfig())
 	defer r.Close(testCtx)
 
 	memorySize := mod.Memory().Size(testCtx)
diff --git a/wasi_snapshot_preview1/wasi_test.go b/wasi_snapshot_preview1/wasi_test.go
index 7f990edb..02d1843c 100644
--- a/wasi_snapshot_preview1/wasi_test.go
+++ b/wasi_snapshot_preview1/wasi_test.go
@@ -4,8 +4,6 @@ import (
 	"bytes"
 	"context"
 	_ "embed"
-	"io"
-	"math/rand"
 	"testing"
 
 	"github.com/tetratelabs/wazero"
@@ -16,12 +14,6 @@ import (
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
-const seed = int64(42) // fixed seed value
-
-var deterministicRandomSource = func() io.Reader {
-	return rand.New(rand.NewSource(seed))
-}
-
 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")