From 6d60a126f97f763288c2c0ea10218acee568502b Mon Sep 17 00:00:00 2001 From: Zxilly Date: Mon, 16 Dec 2024 01:54:55 +0800 Subject: [PATCH 1/5] wasm: terminate instance while met stackoverflow --- lib/wasm/wasm_exec.js | 12 +++++++++++- src/runtime/panic.go | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/wasm/wasm_exec.js b/lib/wasm/wasm_exec.js index d71af9e97e8ca9..0ef3737054cc28 100644 --- a/lib/wasm/wasm_exec.js +++ b/lib/wasm/wasm_exec.js @@ -556,7 +556,17 @@ if (this.exited) { throw new Error("Go program has already exited"); } - this._inst.exports.resume(); + try { + this._inst.exports.resume(); + } catch (err) { + if (err instanceof RangeError) { + // runtime.throw + this._inst.exports.throw(err.message); + this.exited = true; + this._resolveExitPromise(); + throw err; + } + } if (this.exited) { this._resolveExitPromise(); } diff --git a/src/runtime/panic.go b/src/runtime/panic.go index dc7a7fe357e091..e14c5e50917596 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -1086,6 +1086,7 @@ func internal_sync_fatal(s string) { // See go.dev/issue/67401. // //go:linkname throw +//go:wasmexport throw //go:nosplit func throw(s string) { // Everything throw does should be recursively nosplit so it From cd32989b9e262291c88d138cc5f9991143b5c71f Mon Sep 17 00:00:00 2001 From: Zxilly Date: Mon, 16 Dec 2024 20:01:43 +0800 Subject: [PATCH 2/5] also protect entry --- lib/wasm/wasm_exec.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/wasm/wasm_exec.js b/lib/wasm/wasm_exec.js index 0ef3737054cc28..353f6171bd656e 100644 --- a/lib/wasm/wasm_exec.js +++ b/lib/wasm/wasm_exec.js @@ -545,19 +545,16 @@ throw new Error("total length of command line and environment variables exceeds limit"); } - this._inst.exports.run(argc, argv); + this._runCatch(() => this._inst.exports.run(argc, argv)); if (this.exited) { this._resolveExitPromise(); } await this._exitPromise; } - _resume() { - if (this.exited) { - throw new Error("Go program has already exited"); - } + _runCatch(fn) { try { - this._inst.exports.resume(); + fn(); } catch (err) { if (err instanceof RangeError) { // runtime.throw @@ -567,6 +564,15 @@ throw err; } } + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + + this._runCatch(() => this._inst.exports.resume()); + if (this.exited) { this._resolveExitPromise(); } From 7cc4ebd29e46dac621898df6db1050ad4f6ed550 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 17 Dec 2024 02:49:21 +0800 Subject: [PATCH 3/5] add test --- lib/wasm/wasm_exec.js | 17 +++++--- src/runtime/mem_js.go | 10 +++++ test/wasmstackoverflow.go | 88 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 test/wasmstackoverflow.go diff --git a/lib/wasm/wasm_exec.js b/lib/wasm/wasm_exec.js index 353f6171bd656e..15eb3ab34be2f1 100644 --- a/lib/wasm/wasm_exec.js +++ b/lib/wasm/wasm_exec.js @@ -556,13 +556,18 @@ try { fn(); } catch (err) { - if (err instanceof RangeError) { + // mostly for debugging + try { + // insert message to mem + const bytes = encoder.encode(err.message + "\0"); + const ptr = this._inst.exports.allocErrorString(BigInt(bytes.length)) + new Uint8Array(this.mem.buffer, ptr, bytes.length).set(bytes); // runtime.throw - this._inst.exports.throw(err.message); - this.exited = true; - this._resolveExitPromise(); - throw err; - } + this._inst.exports.throw(ptr, bytes.length); + } catch (err) {} + this.exited = true; + this._resolveExitPromise(); + throw err; } } diff --git a/src/runtime/mem_js.go b/src/runtime/mem_js.go index 080b1abc6701cc..b390e140c377c3 100644 --- a/src/runtime/mem_js.go +++ b/src/runtime/mem_js.go @@ -6,8 +6,18 @@ package runtime +import "unsafe" + // resetMemoryDataView signals the JS front-end that WebAssembly's memory.grow instruction has been used. // This allows the front-end to replace the old DataView object with a new one. // //go:wasmimport gojs runtime.resetMemoryDataView func resetMemoryDataView() + +// allocErrorString allocates a space to store the error string passed from the JS front-end. +// +//go:wasmexport allocErrorString +func allocErrorString(s int64) uintptr { + tmp := make([]byte, s) + return uintptr(unsafe.Pointer(&tmp[0])) +} diff --git a/test/wasmstackoverflow.go b/test/wasmstackoverflow.go new file mode 100644 index 00000000000000..4d653e9c49d942 --- /dev/null +++ b/test/wasmstackoverflow.go @@ -0,0 +1,88 @@ +// run + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This test ensure stackoverflow was caught correctly + +package main + +import ( + "bytes" + "os" + "os/exec" + "path/filepath" +) + +const errorCode = ` +package main + +func growStack(n int64) { + if n > 0 { + growStack(n - 1) + } +} + +func main() { + growStack(998244353) +} +` + +func main() { + tmpDir, err := os.MkdirTemp("", "wasm-stackoverflow") + if err != nil { + panic(err) + } + defer os.RemoveAll(tmpDir) + + if err := os.WriteFile(tmpDir+"/test.go", []byte(errorCode), 0666); err != nil { + panic(err) + } + + cmd := exec.Command("go", "build", "-o", tmpDir+"/test.wasm", tmpDir+"/test.go") + cmd.Dir = tmpDir + cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm") + if err := cmd.Run(); err != nil { + panic(err) + } + + //input, err := os.ReadFile(tmpDir + "/test.wasm") + //if err != nil { + // panic(err) + //} + // + //err = os.WriteFile("T:/wasm/test.wasm", input, 0666) + //if err != nil { + // panic(err) + //} + + node, err := exec.LookPath("node") + if err != nil { + // skip wasm stackoverflow test because node is not found + return + } + + // ensure ../lib/wasm/wasm_exec_node.js exist + _, err = os.Stat("../lib/wasm/wasm_exec_node.js") + if err != nil { + panic(err) + } + exec_node := "../lib/wasm/wasm_exec_node.js" + exec_node, err = filepath.Abs(exec_node) + if err != nil { + panic(err) + } + + out := bytes.NewBuffer(nil) + cmd = exec.Command(node, exec_node, tmpDir+"/test.wasm") + cmd.Stdout = out + cmd.Stderr = out + if err := cmd.Run(); err == nil { + panic("expected error, got nil") + } + + if !bytes.Contains(out.Bytes(), []byte("Maximum call stack size exceeded")) { + panic("expected stackoverflow error") + } +} From 2aca1ed88e2c6f7f3785a738e0f3d0a1df3a1fae Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 17 Dec 2024 02:51:17 +0800 Subject: [PATCH 4/5] remove debug code --- test/wasmstackoverflow.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/wasmstackoverflow.go b/test/wasmstackoverflow.go index 4d653e9c49d942..8953520b66b2c9 100644 --- a/test/wasmstackoverflow.go +++ b/test/wasmstackoverflow.go @@ -47,16 +47,6 @@ func main() { panic(err) } - //input, err := os.ReadFile(tmpDir + "/test.wasm") - //if err != nil { - // panic(err) - //} - // - //err = os.WriteFile("T:/wasm/test.wasm", input, 0666) - //if err != nil { - // panic(err) - //} - node, err := exec.LookPath("node") if err != nil { // skip wasm stackoverflow test because node is not found From 4335be44d223e0295d4d2346f5566399826643e3 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 17 Dec 2024 02:52:21 +0800 Subject: [PATCH 5/5] update var --- test/wasmstackoverflow.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/wasmstackoverflow.go b/test/wasmstackoverflow.go index 8953520b66b2c9..9278f1d1465a79 100644 --- a/test/wasmstackoverflow.go +++ b/test/wasmstackoverflow.go @@ -53,23 +53,18 @@ func main() { return } - // ensure ../lib/wasm/wasm_exec_node.js exist - _, err = os.Stat("../lib/wasm/wasm_exec_node.js") - if err != nil { - panic(err) - } - exec_node := "../lib/wasm/wasm_exec_node.js" - exec_node, err = filepath.Abs(exec_node) + p := "../lib/wasm/wasm_exec_node.js" + p, err = filepath.Abs(p) if err != nil { panic(err) } out := bytes.NewBuffer(nil) - cmd = exec.Command(node, exec_node, tmpDir+"/test.wasm") + cmd = exec.Command(node, p, tmpDir+"/test.wasm") cmd.Stdout = out cmd.Stderr = out if err := cmd.Run(); err == nil { - panic("expected error, got nil") + panic("expected stackoverflow error, got nil") } if !bytes.Contains(out.Bytes(), []byte("Maximum call stack size exceeded")) {