Skip to content

Commit

Permalink
Switches default random source to deterministic (#736)
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
  • Loading branch information
codefromthecrypt committed Aug 6, 2022
1 parent fe56fab commit 9414b0b
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 28 deletions.
10 changes: 5 additions & 5 deletions assemblyscript/assemblyscript_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
_ "embed"
"encoding/hex"
"errors"
"io"
"strings"
Expand Down Expand Up @@ -123,20 +124,19 @@ 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)
require.NoError(t, err)
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) {
Expand Down
11 changes: 7 additions & 4 deletions config.go
Expand Up @@ -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
}

Expand Down
18 changes: 18 additions & 0 deletions config_test.go
Expand Up @@ -2,6 +2,7 @@ package wazero

import (
"context"
"crypto/rand"
"io"
"io/fs"
"math"
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions 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))
}
11 changes: 6 additions & 5 deletions internal/sys/sys.go
Expand Up @@ -2,7 +2,6 @@ package sys

import (
"context"
"crypto/rand"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -187,7 +188,7 @@ func NewContext(
}

if randSource == nil {
sysCtx.randSource = rand.Reader
sysCtx.randSource = platform.NewFakeRandSource()
} else {
sysCtx.randSource = randSource
}
Expand Down
3 changes: 1 addition & 2 deletions internal/sys/sys_test.go
Expand Up @@ -3,7 +3,6 @@ package sys
import (
"bytes"
"context"
"crypto/rand"
"io"
"testing"
"time"
Expand Down Expand Up @@ -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))
}

Expand Down
6 changes: 2 additions & 4 deletions wasi_snapshot_preview1/random_test.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 0 additions & 8 deletions wasi_snapshot_preview1/wasi_test.go
Expand Up @@ -4,8 +4,6 @@ import (
"bytes"
"context"
_ "embed"
"io"
"math/rand"
"testing"

"github.com/tetratelabs/wazero"
Expand All @@ -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")

Expand Down

0 comments on commit 9414b0b

Please sign in to comment.