Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions gogo/gogo.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/chainreactors/gogo/v2/engine"
"github.com/chainreactors/gogo/v2/pkg"
"github.com/chainreactors/logs"
neutrontemplates "github.com/chainreactors/neutron/templates"
sdkfingers "github.com/chainreactors/sdk/fingers"
"github.com/chainreactors/sdk/neutron"
"github.com/chainreactors/sdk/pkg/types"
Expand Down Expand Up @@ -84,6 +85,23 @@ func buildTemplateMap(templates []*types.Template) map[string][]*types.Template
return templateMap
}

// buildChainExecutor 构建 gogo 漏洞扫描所需的 chain 执行器。
//
// gogo 的 engine.NeutronScan 依赖包级全局 pkg.ChainExec 来解析模板链,而该全局
// 原本只由 pkg.LoadTemplates 赋值。SDK 注入模板时走的是 buildTemplateMap 直接填
// pkg.TemplateMap 的旁路,绕过了 LoadTemplates,因此必须在这里同步构建 ChainExec,
// 否则开启 exploit 扫描(Exploit != "none")时 ChainExec.Execute 会打在 nil 上导致
// ants worker panic。语义与 pkg.LoadTemplates 中的 chainExec 构建保持一致。
func buildChainExecutor(templates []*types.Template) *neutrontemplates.ChainExecutor {
chainExec := neutrontemplates.NewChainExecutor(neutrontemplates.ChainConfig{})
for _, template := range templates {
if template.Id != "" {
chainExec.Add(template.Id, template.Chains)
}
}
return chainExec
}

// Init 初始化引擎(加载指纹库等)
func (e *Engine) Init() error {
e.mu.Lock()
Expand Down Expand Up @@ -166,6 +184,7 @@ func (e *Engine) applyInjectedNeutron() bool {
}
templateMap := buildTemplateMap(templates)
pkg.TemplateMap = templateMap
pkg.ChainExec = buildChainExecutor(templates)
templateCount := 0
for _, values := range templateMap {
templateCount += len(values)
Expand Down
46 changes: 46 additions & 0 deletions gogo/gogo_chainexec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package gogo

import (
"testing"

neutrontemplates "github.com/chainreactors/neutron/templates"
"github.com/chainreactors/sdk/pkg/types"
)

// Regression for the nil-pointer panic in engine.NeutronScan: when the SDK
// injects templates it populates pkg.TemplateMap directly (buildTemplateMap),
// bypassing pkg.LoadTemplates which is the only other place that builds
// pkg.ChainExec. buildChainExecutor must rebuild that executor so exploit
// scans don't call ChainExec.Execute on a nil receiver.
func TestBuildChainExecutorRegistersIDsAndChains(t *testing.T) {
tmpls := []*types.Template{
{Id: "entry", Chains: []string{"leaf"}},
{Id: "leaf"},
{Id: ""}, // no id: must be skipped without panicking
}

ce := buildChainExecutor(tmpls)
if ce == nil {
t.Fatal("buildChainExecutor returned nil executor")
}
if !ce.Has("entry") || !ce.Has("leaf") {
t.Fatalf("registered ids = entry:%v leaf:%v, want both true", ce.Has("entry"), ce.Has("leaf"))
}

// "leaf" is a chain target of "entry", so only "entry" is an entrypoint.
eps := ce.Entrypoints()
if len(eps) != 1 || eps[0] != "entry" {
t.Fatalf("entrypoints = %v, want [entry]", eps)
}

// Executing from the entrypoint must walk entry -> leaf without panic
// (the panic being fixed is exactly this call on a nil receiver).
var visited []string
ce.Execute(eps, func(id string, _ map[string]interface{}) *neutrontemplates.ChainResult {
visited = append(visited, id)
return &neutrontemplates.ChainResult{}
})
if len(visited) != 2 {
t.Fatalf("chain walk visited %v, want entry+leaf", visited)
}
}
6 changes: 5 additions & 1 deletion neutron/config_templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,14 @@ func TestExecuteTaskAndResultHelpers(t *testing.T) {
t.Fatal("expected explicit empty templates to fail validation")
}

// OperatorResult (parsers.NeutronResult) embeds parsers.Result, so Matched is
// a promoted field and can't be set in a composite literal — assign it directly.
matchedResult := &types.OperatorResult{}
matchedResult.Matched = true
matched := &ExecuteResult{
success: true,
template: &types.Template{Id: "demo"},
data: &NeutronResult{Result: &types.OperatorResult{Matched: true}},
data: &NeutronResult{Result: matchedResult},
}
if !matched.Success() || !matched.Matched() || matched.Template().Id != "demo" || matched.Data() != matched.Result() {
t.Fatalf("unexpected matched result helpers: %+v", matched)
Expand Down
5 changes: 4 additions & 1 deletion proton/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,10 @@ func TestEngine_ConfigFilter_IDs(t *testing.T) {
})

eng := mustEngine(t, NewConfig().WithTemplatePaths(tmplDir).WithIDs("password-extract"))
findings := eng.ScanData([]byte("PRIVATE KEY\npassword = test\n"), "all.txt")
// The file extractor drops dummy values like "test"; use a realistic value so
// only the ID filter (not the FP filter) decides the outcome. "PRIVATE KEY" stays
// in the input to assert the excluded private-key-detect rule does not fire.
findings := eng.ScanData([]byte("PRIVATE KEY\npassword = test123\n"), "all.txt")
if len(findings) != 1 {
t.Fatalf("expected 1 finding, got %d", len(findings))
}
Expand Down