ITADN

[BUG] fatal error: concurrent map read and map write

#7376Opengnuletik 创建于 2026-05-04
Type: Bug
G
gnuletikcommented
### Is there an existing issue for this? - [x] I have searched the existing issues. ### Current Behavior Nuclei crashes with a Go runtime fatal (`concurrent map read and map write`) when a flow template executes a JS protocol step under load. The crash originates inside the `goja` JS engine's `templatedObject` while the flow executor is invoking a JS protocol request: ``` nuclei/pkg/tmplexec/flow.(*FlowExecutor).requestExecutor → nuclei/pkg/protocols/javascript.(*Request).ExecuteWithResults (js.go:296) → nuclei/pkg/js/compiler.(*Compiler).ExecuteWithOptions (compiler.go:121) → nuclei/pkg/js/compiler.executeWithRuntime (pool.go:85) → goja.(*Runtime).RunProgram → ... goja vm ... → goja.(*Object).Delete (runtime.go:1833) → goja.(*templatedObject).deleteStr (object_template.go:249) → goja.(*templatedObject).getOwnPropStr (object_template.go:109) ← FATAL ``` The fatal map error is non-recoverable — the entire process dies. ### Root cause analysis `templatedObject.getOwnPropStr` lazily materialises template-backed properties into the per-instance `o.values` map without locking: https://github.com/Mzack9999/goja/blob/e46100e9c697/object_template.go#L108-L118 ```go func (o *templatedObject) getOwnPropStr(p unistring.String) Value { if v, exists := o.values[p]; exists { // read return v } if f := o.tmpl.props[p]; f != nil { v := f(o.val.runtime) o.values[p] = v // write — first-touch materialisation return v } return nil } ``` deleteStr then reads the same map again. None of baseObject.values access in goja is locked — by design. goja's documented concurrency model is one goroutine per *goja.Runtime, and templatedObject is one of many places that assume it. The fatal therefore proves that two goroutines are operating on the same *goja.Runtime concurrently. The most plausible source is pkg/js/compiler/pool.go checking out a runtime that is still in use by another goroutine — most likely because a flow template's JS callback dispatches work that re-enters the JS pool, or because the pool returns the same runtime to two callers. ### Expected Behavior Nuclei should not crash. ### Steps To Reproduce Hard to reproduce or maybe with `-race` ? ### Relevant log output ```shell fatal error: concurrent map read and map write goroutine 9800382 [running]: internal/runtime/maps.fatal({0x32764ae?, 0x0?}) runtime/panic.go:1181 +0x18 github.com/Mzack9999/goja.(*templatedObject).getOwnPropStr(0x7940fbeec00, {0x318d975, 0x8}) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/object_template.go:109 +0x3a github.com/Mzack9999/goja.(*templatedObject).deleteStr(0x7940fbeec00, {0x318d975, 0x8}, 0x1) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/object_template.go:249 +0x2b github.com/Mzack9999/goja.(*Object).Delete.func1() github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/value.go:916 +0x4c github.com/Mzack9999/goja.(*vm).try(0x79416c70fc0, 0x7940482c3a8) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/vm.go:864 +0x21b github.com/Mzack9999/goja.(*Runtime).try(...) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/runtime.go:2648 github.com/Mzack9999/goja.(*Object).Delete(0x1?, {0x318d975?, 0x793fd6319a8?}) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/value.go:915 +0x45 github.com/Mzack9999/goja.(*Runtime).toValue.func1({{0x3abd230, 0x6b197e0}, {0x793fed5c560, 0x0, 0x2}}) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/runtime.go:1833 +0x3d github.com/Mzack9999/goja.(*nativeFuncObject).vmCall(0x7940280c1e0, 0x793fdeb4fc0, 0x0) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/func.go:563 +0x184 github.com/Mzack9999/goja.call.exec(0xa?, 0x793fdeb4fc0) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/vm.go:3642 +0x66 github.com/Mzack9999/goja.(*vm).run(0x793fdeb4fc0) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/vm.go:635 +0x5b github.com/Mzack9999/goja.(*vm).runTryInner(0x79424e70910?) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/vm.go:887 +0x52 github.com/Mzack9999/goja.(*vm).runTry(0x793fdeb4fc0) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/vm.go:873 +0x1b4 github.com/Mzack9999/goja.(*Runtime).RunProgram(0x7940e1b6008, 0x794370e59a0) github.com/Mzack9999/goja@v0.0.0-20250507184235-e46100e9c697/runtime.go:1476 +0x425 github.com/projectdiscovery/nuclei/v3/pkg/js/compiler.executeWithRuntime.func1() github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/js/compiler/pool.go:85 +0x4d github.com/projectdiscovery/nuclei/v3/pkg/js/compiler.executeWithRuntime(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/js/compiler/pool.go:134 +0x595 github.com/projectdiscovery/nuclei/v3/pkg/js/compiler.executeWithPoolingProgram(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/js/compiler/pool.go:218 +0x42e github.com/projectdiscovery/nuclei/v3/pkg/js/compiler.ExecuteProgram(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/js/compiler/pool.go:152 +0xa9 github.com/projectdiscovery/nuclei/v3/pkg/js/compiler.(*Compiler).ExecuteWithOptions(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/js/compiler/compiler.go:121 +0x38f github.com/projectdiscovery/nuclei/v3/pkg/protocols/javascript.(*Request).executeRequestWithPayloads(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/protocols/javascript/js.go:563 +0x445 github.com/projectdiscovery/nuclei/v3/pkg/protocols/javascript.(*Request).executeWithResults(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/protocols/javascript/js.go:456 +0xcff github.com/projectdiscovery/nuclei/v3/pkg/protocols/javascript.(*Request).ExecuteWithResults(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/protocols/javascript/js.go:296 +0x105 github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow.(*FlowExecutor).requestExecutor(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/tmplexec/flow/flow_internal.go:41 +0x306 github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow.(*FlowExecutor).Compile.func1(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/tmplexec/flow/flow_executor.go:174 +0x1d4 ... goja vm frames ... github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow.(*FlowExecutor).ExecuteWithResults(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/tmplexec/flow/flow_executor.go:255 +0x551 github.com/projectdiscovery/nuclei/v3/pkg/tmplexec.(*TemplateExecuter).Execute(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/tmplexec/exec.go:211 +0x3df github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateOnInput(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/core/executors.go:266 +0x137 github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets.func2.1(...) github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/core/executors.go:111 +0xe5 github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets.func2() github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/core/executors.go:116 +0x112 created by github.com/projectdiscovery/nuclei/v3/pkg/core.(*Engine).executeTemplateWithTargets in goroutine 9800381 github.com/projectdiscovery/nuclei/v3@v3.8.0/pkg/core/executors.go:97 +0x2e8 ``` ### Environment ```markdown - OS: Linux - Nuclei: v3.8.0 - Go: 1.26 ``` ### Anything else? The Mzack9999/goja fork has issues disabled, and upstream dop251/goja shows no commit touching object_template.go since the original templatedObject implementation. Any fix that doesn't redesign goja's concurrency model has to live in Nuclei's JS runtime pool.
0 条评论