From 4cfd6cd2b7e9ead480e93fe6e2978a17e9cfda4d Mon Sep 17 00:00:00 2001 From: r8d8 Date: Sat, 12 Feb 2022 00:19:01 +0200 Subject: [PATCH 01/10] wasi: Init random_get function Signed-off-by: Constantine Kryvomaz --- wasi/testdata/random.wat | 24 ++++++++++++++++++++++++ wasi/wasi.go | 24 ++++++++++++++++++++++-- wasi/wasi_test.go | 23 ++++++++++++++++++++++- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 wasi/testdata/random.wat diff --git a/wasi/testdata/random.wat b/wasi/testdata/random.wat new file mode 100644 index 0000000000..1da660b264 --- /dev/null +++ b/wasi/testdata/random.wat @@ -0,0 +1,24 @@ +;; This is a wat file to just export clock WASI API to the host environment for testing the APIs. +;; This is currently separated as a wat file and pre-compiled because our text parser doesn't +;; implement 'memory' yet. After it supports 'memory', we can remove this file and embed this +;; wat file in the Go test code. +;; +;; Note: Although this is a raw wat file which should be moved under /tests/wasi in principle, +;; this file is put here for now, because this is a temporary file until the parser supports +;; the enough syntax, and this file will be embedded in unit test codes after that. +(module + (import "wasi_snapshot_preview1" "random_get" + (func $wasi.random_get (param $buf i32) (param $buf_len i32) (result (;errno;) i32))) + (memory 1) ;; just an arbitrary size big enough for tests + (export "memory" (memory 0)) + ;; Define wrapper functions instead of just exporting the imported WASI APIS for now + ;; because wazero's interpreter has a bug that it crashes when an imported-and-exported host function + ;; is called from the host environment, which will be fixed soon. + ;; After it's fixed, these wrapper functions are no longer necessary. + (func $random_get (param i32 i32) (result i32) + local.get 0 + local.get 1 + call $wasi.random_get + ) + (export "random_get" (func $random_get)) + ) \ No newline at end of file diff --git a/wasi/wasi.go b/wasi/wasi.go index bcf4227f17..eab3cfbd47 100644 --- a/wasi/wasi.go +++ b/wasi/wasi.go @@ -143,7 +143,15 @@ type API interface { // TODO: ProcExit // TODO: ProcRaise // TODO: SchedYield - // TODO: RandomGet + + // RandomGet is a WASI function that write random data in buffer. + // + // * buf - buffer to be filled with random values + // * buf_len - buffer size + // + // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno + RandomGet(ctx *wasm.HostFunctionCallContext, buf, buf_len uint32) Errno + // TODO: SockRecv // TODO: SockSend // TODO: SockShutdown @@ -212,7 +220,7 @@ func (a *api) register(store *wasm.Store) (err error) { {FunctionProcExit, proc_exit}, // TODO: FunctionProcRaise // TODO: FunctionSchedYield - // TODO: FunctionRandomGet + {FunctionRandomGet, a.RandomGet}, // TODO: FunctionSockRecv // TODO: FunctionSockSend // TODO: FunctionSockShutdown @@ -503,6 +511,18 @@ func (a *api) fd_close(ctx *wasm.HostFunctionCallContext, fd uint32) (err Errno) return ErrnoSuccess } +func (a *api) RandomGet(ctx *wasm.HostFunctionCallContext, buf uint32, buf_len uint32) (errno Errno) { + random_bytes := make([]byte, buf_len) + _, err := rand.Read(random_bytes) + if err != nil { + return ErrnoInval + } + + copy(ctx.Memory.Buffer[buf:buf_len], random_bytes) + + return ErrnoSuccess +} + func proc_exit(*wasm.HostFunctionCallContext, uint32) { // TODO: implement } diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index 1cbbef32a0..58601faff0 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -312,7 +312,28 @@ func TestAPI_ClockTimeGet_Errors(t *testing.T) { // TODO: TestAPI_ProcExit TestAPI_ProcExit_Errors // TODO: TestAPI_ProcRaise TestAPI_ProcRaise_Errors // TODO: TestAPI_SchedYield TestAPI_SchedYield_Errors -// TODO: TestAPI_RandomGet TestAPI_RandomGet_Errors + +// randomWat is a wasm module to call random_get. +//go:embed testdata/random.wat +var randomWat []byte +func TestAPI_RandomGet(t *testing.T) { + store, wasiAPI := instantiateWasmStore(t, randomWat, "test") + var buf_len uint32 = 5 + var buf uint32 = 0 + + t.Run("API.RandomGet", func(t *testing.T) { + // provide a host context we call directly + hContext := wasm.NewHostFunctionCallContext(context.Background(), store.Memories[0]) + + errno := wasiAPI.RandomGet(hContext, buf, buf_len) + require.Equal(t, ErrnoSuccess, errno) + }) +} + +func TestAPI_RandomGet_Errors(t *testing.T) { + +} + // TODO: TestAPI_SockRecv TestAPI_SockRecv_Errors // TODO: TestAPI_SockSend TestAPI_SockSend_Errors // TODO: TestAPI_SockShutdown TestAPI_SockShutdown_Errors From 19b7cc2ff626322d301b32a9c26b996899d688f6 Mon Sep 17 00:00:00 2001 From: r8d8 Date: Mon, 14 Feb 2022 08:43:31 +0200 Subject: [PATCH 02/10] Update WASI `random_get`. Add tests. Signed-off-by: Constantine Kryvomaz --- wasi/wasi.go | 24 +++++++++++++++++----- wasi/wasi_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++----- wasm/store.go | 12 +++++++++++ 3 files changed, 77 insertions(+), 10 deletions(-) diff --git a/wasi/wasi.go b/wasi/wasi.go index eab3cfbd47..f0e36f48c3 100644 --- a/wasi/wasi.go +++ b/wasi/wasi.go @@ -144,10 +144,19 @@ type API interface { // TODO: ProcRaise // TODO: SchedYield - // RandomGet is a WASI function that write random data in buffer. + // RandomGet is a WASI function that write random data in buffer (rand.Read()). // - // * buf - buffer to be filled with random values - // * buf_len - buffer size + // * buf - is a offset to write random values + // * buf_len - size of random data in bytes + // + // For example, if `HostFunctionCallContext.Randomizer` initialized + // with random seed `rand.NewSource(42)`, we expect `ctx.Memory.Buffer` to contain: + // + // buf_len (5) + // +------------------------+ + // | | + // []byte{?, 0x53, 0x8c, 0x7f, 0x96, 0xb1, ?} + // buf --^ // // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno RandomGet(ctx *wasm.HostFunctionCallContext, buf, buf_len uint32) Errno @@ -512,13 +521,18 @@ func (a *api) fd_close(ctx *wasm.HostFunctionCallContext, fd uint32) (err Errno) } func (a *api) RandomGet(ctx *wasm.HostFunctionCallContext, buf uint32, buf_len uint32) (errno Errno) { + if !ctx.Memory.ValidateAddrRange(buf, uint64(buf_len)) { + return ErrnoInval + } + random_bytes := make([]byte, buf_len) - _, err := rand.Read(random_bytes) + _, err := ctx.Randomizer.Read(random_bytes) if err != nil { return ErrnoInval } - copy(ctx.Memory.Buffer[buf:buf_len], random_bytes) + + copy(ctx.Memory.Buffer[buf:buf+buf_len], random_bytes) return ErrnoSuccess } diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index 58601faff0..360a38c254 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -316,22 +316,63 @@ func TestAPI_ClockTimeGet_Errors(t *testing.T) { // randomWat is a wasm module to call random_get. //go:embed testdata/random.wat var randomWat []byte + func TestAPI_RandomGet(t *testing.T) { store, wasiAPI := instantiateWasmStore(t, randomWat, "test") - var buf_len uint32 = 5 - var buf uint32 = 0 - + maskLength := 7 // number of bytes to write '?' to tell what we've written + expectedMemory := []byte{ + '?', // random bytes in `buf` is after this + 0x53, 0x8c, 0x7f, 0x96, 0xb1, // random data from seed value of 42 + '?', // stopped after encoding + } // tr + + var buf_len = uint32(5) // arbitrary buffer size, + var buf = uint32(1) // offset, + var seed = int64(42) // and seed value + t.Run("API.RandomGet", func(t *testing.T) { - // provide a host context we call directly - hContext := wasm.NewHostFunctionCallContext(context.Background(), store.Memories[0]) + maskMemory(store, maskLength) + // provide a host context with a seed value for random generator + hContext := wasm.NewHostFunctionCallContextWithSeed(context.Background(), store.Memories[0], seed) errno := wasiAPI.RandomGet(hContext, buf, buf_len) require.Equal(t, ErrnoSuccess, errno) + require.Equal(t, expectedMemory, store.Memories[0].Buffer[0:maskLength]) }) } func TestAPI_RandomGet_Errors(t *testing.T) { + store, _ := instantiateWasmStore(t, randomWat, "test") + + memorySize := uint32(len(store.Memories[0].Buffer)) + validAddress := uint32(0) // arbitrary valid address as arguments to args_sizes_get. We chose 0 here. + tests := []struct { + name string + buf uint32 + buf_len uint32 + }{ + { + name: "random buffer out-of-memory", + buf: memorySize, + buf_len: 1, + }, + + { + name: "random buffer size exceeds the maximum valid address by 1", + buf: validAddress, + buf_len: memorySize + 1, + }, + } + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + ret, _, err := store.CallFunction(context.Background(), "test", FunctionRandomGet, uint64(tc.buf), uint64(tc.buf_len)) + require.NoError(t, err) + require.Equal(t, uint64(ErrnoInval), ret[0]) // ret[0] is returned errno + }) + } } // TODO: TestAPI_SockRecv TestAPI_SockRecv_Errors diff --git a/wasm/store.go b/wasm/store.go index 3771303c0a..8166b62e95 100644 --- a/wasm/store.go +++ b/wasm/store.go @@ -7,7 +7,9 @@ import ( "fmt" "io" "math" + "math/rand" "reflect" + "time" "github.com/tetratelabs/wazero/wasm/internal/ieee754" "github.com/tetratelabs/wazero/wasm/internal/leb128" @@ -868,15 +870,25 @@ type HostFunctionCallContext struct { ctx context.Context // Memory is the currently used memory instance at the time when the host function call is made. Memory *MemoryInstance + // Used in `RandomGet` method + Randomizer *rand.Rand // TODO: Add others if necessary. } // NewHostFunctionCallContext creates a new HostFunctionCallContext with a // context and memory instance. func NewHostFunctionCallContext(ctx context.Context, memory *MemoryInstance) *HostFunctionCallContext { + return NewHostFunctionCallContextWithSeed(ctx, memory, time.Now().Unix()) +} + +// NewHostFunctionCallContextWithRandomSeed creates a new HostFunctionCallContext +// with a random generator initialized by a `seed` value. +func NewHostFunctionCallContextWithSeed(ctx context.Context, memory *MemoryInstance, seed int64) *HostFunctionCallContext { + s := rand.NewSource(seed) return &HostFunctionCallContext{ ctx: ctx, Memory: memory, + Randomizer: rand.New(s), } } From 0a0f55b96abb7cd43ebfcc26810912caf0f65e1a Mon Sep 17 00:00:00 2001 From: r8d8 Date: Mon, 14 Feb 2022 08:52:32 +0200 Subject: [PATCH 03/10] Fix source formatting Signed-off-by: r8d8 Signed-off-by: Constantine Kryvomaz --- wasi/wasi.go | 7 +++---- wasm/store.go | 10 +++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/wasi/wasi.go b/wasi/wasi.go index f0e36f48c3..7fd4694b72 100644 --- a/wasi/wasi.go +++ b/wasi/wasi.go @@ -149,8 +149,8 @@ type API interface { // * buf - is a offset to write random values // * buf_len - size of random data in bytes // - // For example, if `HostFunctionCallContext.Randomizer` initialized - // with random seed `rand.NewSource(42)`, we expect `ctx.Memory.Buffer` to contain: + // For example, if `HostFunctionCallContext.Randomizer` initialized + // with random seed `rand.NewSource(42)`, we expect `ctx.Memory.Buffer` to contain: // // buf_len (5) // +------------------------+ @@ -524,13 +524,12 @@ func (a *api) RandomGet(ctx *wasm.HostFunctionCallContext, buf uint32, buf_len u if !ctx.Memory.ValidateAddrRange(buf, uint64(buf_len)) { return ErrnoInval } - + random_bytes := make([]byte, buf_len) _, err := ctx.Randomizer.Read(random_bytes) if err != nil { return ErrnoInval } - copy(ctx.Memory.Buffer[buf:buf+buf_len], random_bytes) diff --git a/wasm/store.go b/wasm/store.go index 8166b62e95..8d5bda3330 100644 --- a/wasm/store.go +++ b/wasm/store.go @@ -870,7 +870,7 @@ type HostFunctionCallContext struct { ctx context.Context // Memory is the currently used memory instance at the time when the host function call is made. Memory *MemoryInstance - // Used in `RandomGet` method + // Used in `RandomGet` method Randomizer *rand.Rand // TODO: Add others if necessary. } @@ -881,13 +881,13 @@ func NewHostFunctionCallContext(ctx context.Context, memory *MemoryInstance) *Ho return NewHostFunctionCallContextWithSeed(ctx, memory, time.Now().Unix()) } -// NewHostFunctionCallContextWithRandomSeed creates a new HostFunctionCallContext -// with a random generator initialized by a `seed` value. +// NewHostFunctionCallContextWithRandomSeed creates a new HostFunctionCallContext +// with a random generator initialized by a `seed` value. func NewHostFunctionCallContextWithSeed(ctx context.Context, memory *MemoryInstance, seed int64) *HostFunctionCallContext { s := rand.NewSource(seed) return &HostFunctionCallContext{ - ctx: ctx, - Memory: memory, + ctx: ctx, + Memory: memory, Randomizer: rand.New(s), } } From 3afe8a519f78fd7487d9c33ddc2cab332705ad77 Mon Sep 17 00:00:00 2001 From: r8d8 Date: Mon, 14 Feb 2022 10:26:36 +0200 Subject: [PATCH 04/10] Fix review comments: * Remove random generator from WASM structure. * Adjust formatting and naming. Signed-off-by: r8d8 Signed-off-by: Constantine Kryvomaz --- wasi/testdata/random.wat | 2 +- wasi/wasi.go | 30 +++++++++++++++++++----------- wasi/wasi_test.go | 32 +++++++++++++++++--------------- wasm/store.go | 16 ++-------------- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/wasi/testdata/random.wat b/wasi/testdata/random.wat index 1da660b264..29c61230ac 100644 --- a/wasi/testdata/random.wat +++ b/wasi/testdata/random.wat @@ -21,4 +21,4 @@ call $wasi.random_get ) (export "random_get" (func $random_get)) - ) \ No newline at end of file + ) diff --git a/wasi/wasi.go b/wasi/wasi.go index 7fd4694b72..09a17f059c 100644 --- a/wasi/wasi.go +++ b/wasi/wasi.go @@ -147,19 +147,19 @@ type API interface { // RandomGet is a WASI function that write random data in buffer (rand.Read()). // // * buf - is a offset to write random values - // * buf_len - size of random data in bytes + // * bufLen - size of random data in bytes // // For example, if `HostFunctionCallContext.Randomizer` initialized // with random seed `rand.NewSource(42)`, we expect `ctx.Memory.Buffer` to contain: // - // buf_len (5) - // +------------------------+ - // | | + // bufLen (5) + // +--------------------------+ + // | | // []byte{?, 0x53, 0x8c, 0x7f, 0x96, 0xb1, ?} // buf --^ // - // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno - RandomGet(ctx *wasm.HostFunctionCallContext, buf, buf_len uint32) Errno + // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno + RandomGet(ctx *wasm.HostFunctionCallContext, buf, bufLen uint32) Errno // TODO: SockRecv // TODO: SockSend @@ -179,6 +179,7 @@ type api struct { opened map[uint32]fileEntry // timeNowUnixNano is mutable for testing timeNowUnixNano func() uint64 + randSource *rand.Rand } func (a *api) register(store *wasm.Store) (err error) { @@ -353,6 +354,7 @@ func registerAPI(store *wasm.Store, opts ...Option) (API, error) { } func newAPI(opts ...Option) *api { + s := rand.NewSource(time.Now().UnixNano()) ret := &api{ args: &nullTerminatedStrings{}, stdin: os.Stdin, @@ -362,6 +364,7 @@ func newAPI(opts ...Option) *api { timeNowUnixNano: func() uint64 { return uint64(time.Now().UnixNano()) }, + randSource: rand.New(s), } // apply functional options @@ -371,6 +374,10 @@ func newAPI(opts ...Option) *api { return ret } +func (a *api) seedRandSource(seed int64) { + a.randSource.Seed(seed) +} + func (a *api) randUnusedFD() uint32 { fd := uint32(rand.Int31()) for { @@ -520,18 +527,19 @@ func (a *api) fd_close(ctx *wasm.HostFunctionCallContext, fd uint32) (err Errno) return ErrnoSuccess } -func (a *api) RandomGet(ctx *wasm.HostFunctionCallContext, buf uint32, buf_len uint32) (errno Errno) { - if !ctx.Memory.ValidateAddrRange(buf, uint64(buf_len)) { +// RandomGet implements API.RandomGet +func (a *api) RandomGet(ctx *wasm.HostFunctionCallContext, buf uint32, bufLen uint32) (errno Errno) { + if !ctx.Memory.ValidateAddrRange(buf, uint64(bufLen)) { return ErrnoInval } - random_bytes := make([]byte, buf_len) - _, err := ctx.Randomizer.Read(random_bytes) + random_bytes := make([]byte, bufLen) + _, err := a.randSource.Read(random_bytes) if err != nil { return ErrnoInval } - copy(ctx.Memory.Buffer[buf:buf+buf_len], random_bytes) + copy(ctx.Memory.Buffer[buf:buf+bufLen], random_bytes) return ErrnoSuccess } diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index 360a38c254..434a5e16a6 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -326,16 +326,18 @@ func TestAPI_RandomGet(t *testing.T) { '?', // stopped after encoding } // tr - var buf_len = uint32(5) // arbitrary buffer size, - var buf = uint32(1) // offset, - var seed = int64(42) // and seed value + var bufLen = uint32(5) // arbitrary buffer size, + var buf = uint32(1) // offset, + var seed = int64(42) // and seed value + + wasiAPI.(*api).seedRandSource(seed) t.Run("API.RandomGet", func(t *testing.T) { maskMemory(store, maskLength) // provide a host context with a seed value for random generator - hContext := wasm.NewHostFunctionCallContextWithSeed(context.Background(), store.Memories[0], seed) + hContext := wasm.NewHostFunctionCallContext(context.Background(), store.Memories[0]) - errno := wasiAPI.RandomGet(hContext, buf, buf_len) + errno := wasiAPI.RandomGet(hContext, buf, bufLen) require.Equal(t, ErrnoSuccess, errno) require.Equal(t, expectedMemory, store.Memories[0].Buffer[0:maskLength]) }) @@ -347,20 +349,20 @@ func TestAPI_RandomGet_Errors(t *testing.T) { memorySize := uint32(len(store.Memories[0].Buffer)) validAddress := uint32(0) // arbitrary valid address as arguments to args_sizes_get. We chose 0 here. tests := []struct { - name string - buf uint32 - buf_len uint32 + name string + buf uint32 + bufLen uint32 }{ { - name: "random buffer out-of-memory", - buf: memorySize, - buf_len: 1, + name: "random buffer out-of-memory", + buf: memorySize, + bufLen: 1, }, { - name: "random buffer size exceeds the maximum valid address by 1", - buf: validAddress, - buf_len: memorySize + 1, + name: "random buffer size exceeds the maximum valid address by 1", + buf: validAddress, + bufLen: memorySize + 1, }, } @@ -368,7 +370,7 @@ func TestAPI_RandomGet_Errors(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - ret, _, err := store.CallFunction(context.Background(), "test", FunctionRandomGet, uint64(tc.buf), uint64(tc.buf_len)) + ret, _, err := store.CallFunction(context.Background(), "test", FunctionRandomGet, uint64(tc.buf), uint64(tc.bufLen)) require.NoError(t, err) require.Equal(t, uint64(ErrnoInval), ret[0]) // ret[0] is returned errno }) diff --git a/wasm/store.go b/wasm/store.go index 8d5bda3330..3771303c0a 100644 --- a/wasm/store.go +++ b/wasm/store.go @@ -7,9 +7,7 @@ import ( "fmt" "io" "math" - "math/rand" "reflect" - "time" "github.com/tetratelabs/wazero/wasm/internal/ieee754" "github.com/tetratelabs/wazero/wasm/internal/leb128" @@ -870,25 +868,15 @@ type HostFunctionCallContext struct { ctx context.Context // Memory is the currently used memory instance at the time when the host function call is made. Memory *MemoryInstance - // Used in `RandomGet` method - Randomizer *rand.Rand // TODO: Add others if necessary. } // NewHostFunctionCallContext creates a new HostFunctionCallContext with a // context and memory instance. func NewHostFunctionCallContext(ctx context.Context, memory *MemoryInstance) *HostFunctionCallContext { - return NewHostFunctionCallContextWithSeed(ctx, memory, time.Now().Unix()) -} - -// NewHostFunctionCallContextWithRandomSeed creates a new HostFunctionCallContext -// with a random generator initialized by a `seed` value. -func NewHostFunctionCallContextWithSeed(ctx context.Context, memory *MemoryInstance, seed int64) *HostFunctionCallContext { - s := rand.NewSource(seed) return &HostFunctionCallContext{ - ctx: ctx, - Memory: memory, - Randomizer: rand.New(s), + ctx: ctx, + Memory: memory, } } From b2c3597290579059614de79a80b94731cfd674c5 Mon Sep 17 00:00:00 2001 From: r8d8 Date: Mon, 14 Feb 2022 13:40:18 +0200 Subject: [PATCH 05/10] Provided interface for random source. Use pseudo-random source in tests. Signed-off-by: r8d8 Signed-off-by: Constantine Kryvomaz --- wasi/wasi.go | 43 ++++++++++++++++++++++++++++++++----------- wasi/wasi_test.go | 25 ++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/wasi/wasi.go b/wasi/wasi.go index 09a17f059c..42d0d36cd2 100644 --- a/wasi/wasi.go +++ b/wasi/wasi.go @@ -1,12 +1,12 @@ package wasi import ( + crand "crypto/rand" "encoding/binary" "errors" "io" "io/fs" "math" - "math/rand" "os" "reflect" "time" @@ -171,6 +171,24 @@ const ( wasiSnapshotPreview1Name = "wasi_snapshot_preview1" ) +type RandomSource interface { + Read([]byte) (int, error) + Int31() (int32, error) +} + +// Non-deterministic random source using crypto/rand +type CryptoRandomSource struct{} + +func (c *CryptoRandomSource) Read(p []byte) (n int, err error) { + return crand.Read(p) +} + +func (c *CryptoRandomSource) Int31() (v int32, err error) { + err = binary.Read(crand.Reader, binary.BigEndian, &v) + + return v, err +} + type api struct { args *nullTerminatedStrings stdin io.Reader @@ -179,7 +197,7 @@ type api struct { opened map[uint32]fileEntry // timeNowUnixNano is mutable for testing timeNowUnixNano func() uint64 - randSource *rand.Rand + randSource RandomSource } func (a *api) register(store *wasm.Store) (err error) { @@ -354,7 +372,6 @@ func registerAPI(store *wasm.Store, opts ...Option) (API, error) { } func newAPI(opts ...Option) *api { - s := rand.NewSource(time.Now().UnixNano()) ret := &api{ args: &nullTerminatedStrings{}, stdin: os.Stdin, @@ -364,7 +381,7 @@ func newAPI(opts ...Option) *api { timeNowUnixNano: func() uint64 { return uint64(time.Now().UnixNano()) }, - randSource: rand.New(s), + randSource: &CryptoRandomSource{}, } // apply functional options @@ -374,15 +391,16 @@ func newAPI(opts ...Option) *api { return ret } -func (a *api) seedRandSource(seed int64) { - a.randSource.Seed(seed) -} +func (a *api) randUnusedFD() (uint32, error) { + v, err := a.randSource.Int31() + if err != nil { + return 0, err + } -func (a *api) randUnusedFD() uint32 { - fd := uint32(rand.Int31()) + fd := uint32(v) for { if _, ok := a.opened[fd]; !ok { - return fd + return fd, nil } fd = (fd + 1) % (1 << 31) } @@ -436,7 +454,10 @@ func (a *api) path_open(ctx *wasm.HostFunctionCallContext, fd, dirFlags, pathPtr } } - newFD := a.randUnusedFD() + newFD, err := a.randUnusedFD() + if err != nil { + return ErrnoInval + } a.opened[newFD] = fileEntry{ file: f, diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index 434a5e16a6..f0b9b9af6c 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -3,6 +3,7 @@ package wasi import ( "context" _ "embed" + mrand "math/rand" "testing" "github.com/stretchr/testify/require" @@ -317,6 +318,28 @@ func TestAPI_ClockTimeGet_Errors(t *testing.T) { //go:embed testdata/random.wat var randomWat []byte +// Non-deterministic random rource using crypto/rand +type DummyRandomSource struct { + rng *mrand.Rand +} + +func (d *DummyRandomSource) Read(p []byte) (n int, err error) { + return d.rng.Read(p) +} + +func (d *DummyRandomSource) Int31() (v int32, err error) { + return d.rng.Int31(), nil + +} + +func NewDummyRandomSource(seed int64) RandomSource { + s := mrand.NewSource(seed) + + return &DummyRandomSource{ + rng: mrand.New(s), + } +} + func TestAPI_RandomGet(t *testing.T) { store, wasiAPI := instantiateWasmStore(t, randomWat, "test") maskLength := 7 // number of bytes to write '?' to tell what we've written @@ -330,7 +353,7 @@ func TestAPI_RandomGet(t *testing.T) { var buf = uint32(1) // offset, var seed = int64(42) // and seed value - wasiAPI.(*api).seedRandSource(seed) + wasiAPI.(*api).randSource = NewDummyRandomSource(seed) t.Run("API.RandomGet", func(t *testing.T) { maskMemory(store, maskLength) From dd8da96a3971a30fcee692cd45aca9ecc4d90bf3 Mon Sep 17 00:00:00 2001 From: Constantine Kryvomaz Date: Mon, 14 Feb 2022 15:24:55 +0200 Subject: [PATCH 06/10] Revert using crypto random for `randUnusedFD` function Signed-off-by: Constantine Kryvomaz --- wasi/wasi.go | 23 +++++------------------ wasi/wasi_test.go | 5 ----- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/wasi/wasi.go b/wasi/wasi.go index 42d0d36cd2..0888456fa0 100644 --- a/wasi/wasi.go +++ b/wasi/wasi.go @@ -7,6 +7,7 @@ import ( "io" "io/fs" "math" + "math/rand" "os" "reflect" "time" @@ -173,7 +174,6 @@ const ( type RandomSource interface { Read([]byte) (int, error) - Int31() (int32, error) } // Non-deterministic random source using crypto/rand @@ -183,12 +183,6 @@ func (c *CryptoRandomSource) Read(p []byte) (n int, err error) { return crand.Read(p) } -func (c *CryptoRandomSource) Int31() (v int32, err error) { - err = binary.Read(crand.Reader, binary.BigEndian, &v) - - return v, err -} - type api struct { args *nullTerminatedStrings stdin io.Reader @@ -391,16 +385,12 @@ func newAPI(opts ...Option) *api { return ret } -func (a *api) randUnusedFD() (uint32, error) { - v, err := a.randSource.Int31() - if err != nil { - return 0, err - } - +func (a *api) randUnusedFD() uint32 { + v := rand.Int31() fd := uint32(v) for { if _, ok := a.opened[fd]; !ok { - return fd, nil + return fd } fd = (fd + 1) % (1 << 31) } @@ -454,10 +444,7 @@ func (a *api) path_open(ctx *wasm.HostFunctionCallContext, fd, dirFlags, pathPtr } } - newFD, err := a.randUnusedFD() - if err != nil { - return ErrnoInval - } + newFD := a.randUnusedFD() a.opened[newFD] = fileEntry{ file: f, diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index f0b9b9af6c..db3ac2bbb3 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -327,11 +327,6 @@ func (d *DummyRandomSource) Read(p []byte) (n int, err error) { return d.rng.Read(p) } -func (d *DummyRandomSource) Int31() (v int32, err error) { - return d.rng.Int31(), nil - -} - func NewDummyRandomSource(seed int64) RandomSource { s := mrand.NewSource(seed) From f83c6597db437ccb588e30b44078ea0fab869a5a Mon Sep 17 00:00:00 2001 From: Constantine Kryvomaz Date: Mon, 14 Feb 2022 16:01:27 +0200 Subject: [PATCH 07/10] Fix review comments. Make random source interfaces private Signed-off-by: Constantine Kryvomaz --- wasi/wasi.go | 16 ++++++++-------- wasi/wasi_test.go | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/wasi/wasi.go b/wasi/wasi.go index 0888456fa0..7643f78a8f 100644 --- a/wasi/wasi.go +++ b/wasi/wasi.go @@ -172,14 +172,14 @@ const ( wasiSnapshotPreview1Name = "wasi_snapshot_preview1" ) -type RandomSource interface { +type randomSource interface { Read([]byte) (int, error) } // Non-deterministic random source using crypto/rand -type CryptoRandomSource struct{} +type cryptoRandomSource struct{} -func (c *CryptoRandomSource) Read(p []byte) (n int, err error) { +func (c *cryptoRandomSource) Read(p []byte) (n int, err error) { return crand.Read(p) } @@ -191,7 +191,7 @@ type api struct { opened map[uint32]fileEntry // timeNowUnixNano is mutable for testing timeNowUnixNano func() uint64 - randSource RandomSource + randSource randomSource } func (a *api) register(store *wasm.Store) (err error) { @@ -375,7 +375,7 @@ func newAPI(opts ...Option) *api { timeNowUnixNano: func() uint64 { return uint64(time.Now().UnixNano()) }, - randSource: &CryptoRandomSource{}, + randSource: &cryptoRandomSource{}, } // apply functional options @@ -541,13 +541,13 @@ func (a *api) RandomGet(ctx *wasm.HostFunctionCallContext, buf uint32, bufLen ui return ErrnoInval } - random_bytes := make([]byte, bufLen) - _, err := a.randSource.Read(random_bytes) + randomBytes := make([]byte, bufLen) + _, err := a.randSource.Read(randomBytes) if err != nil { return ErrnoInval } - copy(ctx.Memory.Buffer[buf:buf+bufLen], random_bytes) + copy(ctx.Memory.Buffer[buf:buf+bufLen], randomBytes) return ErrnoSuccess } diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index db3ac2bbb3..7bf6e72864 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -319,18 +319,18 @@ func TestAPI_ClockTimeGet_Errors(t *testing.T) { var randomWat []byte // Non-deterministic random rource using crypto/rand -type DummyRandomSource struct { +type dummyRandomSource struct { rng *mrand.Rand } -func (d *DummyRandomSource) Read(p []byte) (n int, err error) { +func (d *dummyRandomSource) Read(p []byte) (n int, err error) { return d.rng.Read(p) } -func NewDummyRandomSource(seed int64) RandomSource { +func newDummyRandomSource(seed int64) randomSource { s := mrand.NewSource(seed) - return &DummyRandomSource{ + return &dummyRandomSource{ rng: mrand.New(s), } } @@ -348,7 +348,7 @@ func TestAPI_RandomGet(t *testing.T) { var buf = uint32(1) // offset, var seed = int64(42) // and seed value - wasiAPI.(*api).randSource = NewDummyRandomSource(seed) + wasiAPI.(*api).randSource = newDummyRandomSource(seed) t.Run("API.RandomGet", func(t *testing.T) { maskMemory(store, maskLength) From f8243b501ea3438036a8b0ea2e6ea9a3143ee570 Mon Sep 17 00:00:00 2001 From: Constantine Kryvomaz Date: Tue, 15 Feb 2022 10:06:17 +0200 Subject: [PATCH 08/10] Fix review comments. Remove random source interface, use functor instead Signed-off-by: Constantine Kryvomaz --- wasi/wasi.go | 28 ++++++++++------------------ wasi/wasi_test.go | 27 ++++++++------------------- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/wasi/wasi.go b/wasi/wasi.go index 7643f78a8f..89a6a5690e 100644 --- a/wasi/wasi.go +++ b/wasi/wasi.go @@ -7,7 +7,7 @@ import ( "io" "io/fs" "math" - "math/rand" + mrand "math/rand" "os" "reflect" "time" @@ -172,17 +172,6 @@ const ( wasiSnapshotPreview1Name = "wasi_snapshot_preview1" ) -type randomSource interface { - Read([]byte) (int, error) -} - -// Non-deterministic random source using crypto/rand -type cryptoRandomSource struct{} - -func (c *cryptoRandomSource) Read(p []byte) (n int, err error) { - return crand.Read(p) -} - type api struct { args *nullTerminatedStrings stdin io.Reader @@ -191,7 +180,7 @@ type api struct { opened map[uint32]fileEntry // timeNowUnixNano is mutable for testing timeNowUnixNano func() uint64 - randSource randomSource + randSource func([]byte) error } func (a *api) register(store *wasm.Store) (err error) { @@ -375,7 +364,10 @@ func newAPI(opts ...Option) *api { timeNowUnixNano: func() uint64 { return uint64(time.Now().UnixNano()) }, - randSource: &cryptoRandomSource{}, + randSource: func(p []byte) error { + _, err := crand.Read(p) + return err + }, } // apply functional options @@ -386,8 +378,7 @@ func newAPI(opts ...Option) *api { } func (a *api) randUnusedFD() uint32 { - v := rand.Int31() - fd := uint32(v) + fd := uint32(mrand.Int31()) for { if _, ok := a.opened[fd]; !ok { return fd @@ -542,9 +533,10 @@ func (a *api) RandomGet(ctx *wasm.HostFunctionCallContext, buf uint32, bufLen ui } randomBytes := make([]byte, bufLen) - _, err := a.randSource.Read(randomBytes) + err := a.randSource(randomBytes) if err != nil { - return ErrnoInval + // TODO: handle different errors that syscal to entropy source can return + return ErrnoIo } copy(ctx.Memory.Buffer[buf:buf+bufLen], randomBytes) diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index 7bf6e72864..fdc259fecb 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -3,7 +3,7 @@ package wasi import ( "context" _ "embed" - mrand "math/rand" + "math/rand" "testing" "github.com/stretchr/testify/require" @@ -318,23 +318,6 @@ func TestAPI_ClockTimeGet_Errors(t *testing.T) { //go:embed testdata/random.wat var randomWat []byte -// Non-deterministic random rource using crypto/rand -type dummyRandomSource struct { - rng *mrand.Rand -} - -func (d *dummyRandomSource) Read(p []byte) (n int, err error) { - return d.rng.Read(p) -} - -func newDummyRandomSource(seed int64) randomSource { - s := mrand.NewSource(seed) - - return &dummyRandomSource{ - rng: mrand.New(s), - } -} - func TestAPI_RandomGet(t *testing.T) { store, wasiAPI := instantiateWasmStore(t, randomWat, "test") maskLength := 7 // number of bytes to write '?' to tell what we've written @@ -348,7 +331,13 @@ func TestAPI_RandomGet(t *testing.T) { var buf = uint32(1) // offset, var seed = int64(42) // and seed value - wasiAPI.(*api).randSource = newDummyRandomSource(seed) + wasiAPI.(*api).randSource = func(p []byte) error { + s := rand.NewSource(seed) + rng := rand.New(s) + _, err := rng.Read(p) + + return err + } t.Run("API.RandomGet", func(t *testing.T) { maskMemory(store, maskLength) From 575ec586e7fe9d77ffafb9860feb1e366e5f0f4b Mon Sep 17 00:00:00 2001 From: Constantine Kryvomaz Date: Tue, 15 Feb 2022 10:25:10 +0200 Subject: [PATCH 09/10] Fix formatting Signed-off-by: Constantine Kryvomaz --- wasi/types.go | 12 ++++++------ wasi/wasi_test.go | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/wasi/types.go b/wasi/types.go index 599b1886f4..1a0c63ccf2 100644 --- a/wasi/types.go +++ b/wasi/types.go @@ -367,19 +367,19 @@ const ( FunctionProcExit = "proc_exit" FunctionProcRaise = "proc_raise" FunctionSchedYield = "sched_yield" - + // FunctionRandomGet write random data in buffer // // See ImportRandomGet // See API.RandomGet // See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno - FunctionRandomGet = "random_get" - + FunctionRandomGet = "random_get" + // ImportRandomGet is the WebAssembly 1.0 (MVP) Text format import of FunctionRandomGet ImportRandomGet = `(import "wasi_snapshot_preview1" "random_get" (func $wasi.random_get (param $buf i32) (param $buf_len i32) (result (;errno;) i32)))` - FunctionSockRecv = "sock_recv" - FunctionSockSend = "sock_send" - FunctionSockShutdown = "sock_shutdown" + FunctionSockRecv = "sock_recv" + FunctionSockSend = "sock_send" + FunctionSockShutdown = "sock_shutdown" ) diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index ca95df5a94..6a44cfda95 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -307,7 +307,6 @@ func TestAPI_ClockTimeGet_Errors(t *testing.T) { // TODO: TestAPI_ProcRaise TestAPI_ProcRaise_Errors // TODO: TestAPI_SchedYield TestAPI_SchedYield_Errors - func TestAPI_RandomGet(t *testing.T) { store, wasiAPI := instantiateWasmStore(t, FunctionRandomGet, ImportRandomGet, "test") maskLength := 7 // number of bytes to write '?' to tell what we've written From ba6212ea2bf35144895ec1a04ac452fe6a28f2a3 Mon Sep 17 00:00:00 2001 From: Constantine Kryvomaz Date: Tue, 15 Feb 2022 13:11:16 +0200 Subject: [PATCH 10/10] Remove unused .wat file Add test case for random source error Signed-off-by: Constantine Kryvomaz --- wasi/testdata/random.wat | 24 ------------------------ wasi/wasi_test.go | 21 +++++++++++++++++---- 2 files changed, 17 insertions(+), 28 deletions(-) delete mode 100644 wasi/testdata/random.wat diff --git a/wasi/testdata/random.wat b/wasi/testdata/random.wat deleted file mode 100644 index 29c61230ac..0000000000 --- a/wasi/testdata/random.wat +++ /dev/null @@ -1,24 +0,0 @@ -;; This is a wat file to just export clock WASI API to the host environment for testing the APIs. -;; This is currently separated as a wat file and pre-compiled because our text parser doesn't -;; implement 'memory' yet. After it supports 'memory', we can remove this file and embed this -;; wat file in the Go test code. -;; -;; Note: Although this is a raw wat file which should be moved under /tests/wasi in principle, -;; this file is put here for now, because this is a temporary file until the parser supports -;; the enough syntax, and this file will be embedded in unit test codes after that. -(module - (import "wasi_snapshot_preview1" "random_get" - (func $wasi.random_get (param $buf i32) (param $buf_len i32) (result (;errno;) i32))) - (memory 1) ;; just an arbitrary size big enough for tests - (export "memory" (memory 0)) - ;; Define wrapper functions instead of just exporting the imported WASI APIS for now - ;; because wazero's interpreter has a bug that it crashes when an imported-and-exported host function - ;; is called from the host environment, which will be fixed soon. - ;; After it's fixed, these wrapper functions are no longer necessary. - (func $random_get (param i32 i32) (result i32) - local.get 0 - local.get 1 - call $wasi.random_get - ) - (export "random_get" (func $random_get)) - ) diff --git a/wasi/wasi_test.go b/wasi/wasi_test.go index 6a44cfda95..ff96ed879a 100644 --- a/wasi/wasi_test.go +++ b/wasi/wasi_test.go @@ -3,6 +3,7 @@ package wasi import ( "context" _ "embed" + "errors" "fmt" "math/rand" "testing" @@ -330,7 +331,6 @@ func TestAPI_RandomGet(t *testing.T) { t.Run("API.RandomGet", func(t *testing.T) { maskMemory(store, maskLength) - // provide a host context with a seed value for random generator hContext := wasm.NewHostFunctionCallContext(context.Background(), store.Memories[0]) errno := wasiAPI.RandomGet(hContext, buf, bufLen) @@ -340,10 +340,10 @@ func TestAPI_RandomGet(t *testing.T) { } func TestAPI_RandomGet_Errors(t *testing.T) { - store, _ := instantiateWasmStore(t, FunctionRandomGet, ImportRandomGet, "test") + store, wasiAPI := instantiateWasmStore(t, FunctionRandomGet, ImportRandomGet, "test") memorySize := uint32(len(store.Memories[0].Buffer)) - validAddress := uint32(0) // arbitrary valid address as arguments to args_sizes_get. We chose 0 here. + validAddress := uint32(0) // arbitrary valid address tests := []struct { name string buf uint32 @@ -356,7 +356,7 @@ func TestAPI_RandomGet_Errors(t *testing.T) { }, { - name: "random buffer size exceeds the maximum valid address by 1", + name: "random buffer size exceeds maximum valid address by 1", buf: validAddress, bufLen: memorySize + 1, }, @@ -371,6 +371,19 @@ func TestAPI_RandomGet_Errors(t *testing.T) { require.Equal(t, uint64(ErrnoInval), ret[0]) // ret[0] is returned errno }) } + + t.Run("API.RandomGet returns ErrnoIO on random source err", func(t *testing.T) { + hContext := wasm.NewHostFunctionCallContext(context.Background(), store.Memories[0]) + + wasiAPI.(*api).randSource = func(p []byte) error { + return errors.New("random source error") + } + var bufLen = uint32(5) // arbitrary buffer size, + var buf = uint32(1) // and offset + errno := wasiAPI.RandomGet(hContext, buf, bufLen) + require.Equal(t, ErrnoIo, errno) + }) + } // TODO: TestAPI_SockRecv TestAPI_SockRecv_Errors