Skip to content

Commit 0cc9e01

Browse files
authored
Refine G407 to improve detection and coverage of hardcoded nonces (#1460)
* Refine G407 to improve detection and coverage of hardcoded nonces * chore: consolidate common analyzer patterns into util.go and improve G602 coverage * Optimize G602 and G115 with state caching and regex pre-compilation * Improve G115 overflow detection and fix false positives and false negatives * golangci-lint workaround
1 parent 303f84d commit 0cc9e01

10 files changed

Lines changed: 2672 additions & 597 deletions

File tree

analyzers/bench_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package analyzers_test
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
10+
"golang.org/x/tools/go/analysis"
11+
"golang.org/x/tools/go/analysis/passes/buildssa"
12+
"golang.org/x/tools/go/analysis/passes/ctrlflow"
13+
"golang.org/x/tools/go/analysis/passes/inspect"
14+
"golang.org/x/tools/go/packages"
15+
16+
"github.com/securego/gosec/v2"
17+
"github.com/securego/gosec/v2/analyzers"
18+
"github.com/securego/gosec/v2/testutils"
19+
)
20+
21+
func benchmarkAnalyzerStress(b *testing.B, analyzerID string, generator func() string) {
22+
logger, _ := testutils.NewLogger()
23+
code := generator()
24+
25+
// SETUP: Create temp dir and main.go
26+
tmpDir, err := os.MkdirTemp("", "gosec_bench")
27+
if err != nil {
28+
b.Fatalf("failed to create temp dir: %v", err)
29+
}
30+
defer os.RemoveAll(tmpDir)
31+
32+
mainGo := filepath.Join(tmpDir, "main.go")
33+
if err := os.WriteFile(mainGo, []byte(code), 0o600); err != nil {
34+
b.Fatalf("failed to write main.go: %v", err)
35+
}
36+
37+
// Create a dummy go.mod to ensure we are in a module
38+
goMod := filepath.Join(tmpDir, "go.mod")
39+
if err := os.WriteFile(goMod, []byte("module bench\n\ngo 1.24\n"), 0o600); err != nil {
40+
b.Fatalf("failed to write go.mod: %v", err)
41+
}
42+
43+
conf := &packages.Config{
44+
Mode: gosec.LoadMode,
45+
Dir: tmpDir,
46+
}
47+
pkgs, err := packages.Load(conf, ".")
48+
if err != nil {
49+
b.Fatalf("failed to load package: %v", err)
50+
}
51+
if len(pkgs) == 0 {
52+
b.Fatalf("no packages loaded")
53+
}
54+
if len(pkgs[0].Errors) > 0 {
55+
b.Fatalf("errors loading package: %v", pkgs[0].Errors)
56+
}
57+
58+
// Prepare analysis context
59+
pass := &analysis.Pass{
60+
Fset: pkgs[0].Fset,
61+
Files: pkgs[0].Syntax,
62+
Pkg: pkgs[0].Types,
63+
TypesInfo: pkgs[0].TypesInfo,
64+
TypesSizes: pkgs[0].TypesSizes,
65+
ResultOf: make(map[*analysis.Analyzer]any),
66+
Report: func(d analysis.Diagnostic) {},
67+
}
68+
69+
pass.Analyzer = inspect.Analyzer
70+
i, _ := inspect.Analyzer.Run(pass)
71+
pass.ResultOf[inspect.Analyzer] = i
72+
73+
pass.Analyzer = ctrlflow.Analyzer
74+
cf, _ := ctrlflow.Analyzer.Run(pass)
75+
pass.ResultOf[ctrlflow.Analyzer] = cf
76+
77+
pass.Analyzer = buildssa.Analyzer
78+
ssaRes, err := buildssa.Analyzer.Run(pass)
79+
if err != nil {
80+
b.Fatalf("failed to build SSA: %v", err)
81+
}
82+
ssaResult := ssaRes.(*buildssa.SSA)
83+
84+
if len(ssaResult.SrcFuncs) == 0 {
85+
b.Fatalf("SSA has 0 source functions.")
86+
}
87+
88+
// Find targeted analyzer
89+
var target *analysis.Analyzer
90+
analyzerList := analyzers.Generate(false)
91+
if def, ok := analyzerList.Analyzers[analyzerID]; ok {
92+
target = def.Create(def.ID, def.Description)
93+
} else {
94+
b.Fatalf("analyzer %s not found", analyzerID)
95+
}
96+
97+
resultMap := map[*analysis.Analyzer]any{
98+
buildssa.Analyzer: &analyzers.SSAAnalyzerResult{
99+
Config: gosec.NewConfig(),
100+
Logger: logger,
101+
SSA: ssaResult,
102+
},
103+
}
104+
105+
runPass := &analysis.Pass{
106+
Analyzer: target,
107+
Fset: pkgs[0].Fset,
108+
Files: pkgs[0].Syntax,
109+
Pkg: pkgs[0].Types,
110+
TypesInfo: pkgs[0].TypesInfo,
111+
TypesSizes: pkgs[0].TypesSizes,
112+
ResultOf: resultMap,
113+
Report: func(d analysis.Diagnostic) {},
114+
}
115+
116+
b.ResetTimer()
117+
for range b.N {
118+
_, err := target.Run(runPass)
119+
if err != nil {
120+
b.Fatalf("failed to run analyzer: %v", err)
121+
}
122+
}
123+
}
124+
125+
// Generators
126+
127+
func generateG115Deep(nesting, conversions int) string {
128+
var sb strings.Builder
129+
sb.WriteString("package main\nimport \"math\"\nfunc run_stress(x int64) {\n")
130+
for i := range nesting {
131+
fmt.Fprintf(&sb, "if x > %d && x < math.MaxInt64 {\n", i)
132+
}
133+
for range conversions {
134+
fmt.Fprintf(&sb, "_ = int8(x)\n")
135+
}
136+
for range nesting {
137+
sb.WriteString("}\n")
138+
}
139+
sb.WriteString("}\n")
140+
return sb.String()
141+
}
142+
143+
func generateG602Wide(levels, accesses int) string {
144+
var sb strings.Builder
145+
sb.WriteString("package main\nfunc run_stress() {\n")
146+
sb.WriteString("s := make([]byte, 100000)\n")
147+
for i := range levels {
148+
fmt.Fprintf(&sb, "s%d := s[%d:]\n", i, i)
149+
for j := range accesses {
150+
fmt.Fprintf(&sb, "_ = s%d[%d]\n", i, j)
151+
fmt.Fprintf(&sb, "_ = s%d[%d]\n", i, j+1)
152+
}
153+
}
154+
sb.WriteString("}\n")
155+
return sb.String()
156+
}
157+
158+
func generateG407Stress(depth int) string {
159+
var sb strings.Builder
160+
sb.WriteString("package main\nimport \"crypto/cipher\"\nfunc run_stress(gcm cipher.AEAD, data []byte) {\n")
161+
sb.WriteString("nonce := []byte(\"hardcoded_nonce_value\")\n")
162+
// Chain of assignments
163+
for i := range depth {
164+
fmt.Fprintf(&sb, "n%d := nonce\n", i)
165+
if i > 0 {
166+
fmt.Fprintf(&sb, "n%d = n%d\n", i, i-1)
167+
}
168+
}
169+
// Use the last nonce in the chain
170+
fmt.Fprintf(&sb, "gcm.Seal(nil, n%d, data, nil)\n", depth-1)
171+
fmt.Fprintf(&sb, "}\n")
172+
return sb.String()
173+
}
174+
175+
// Benchmarks (Logic Only)
176+
177+
func BenchmarkAnalysisG115_Deep(b *testing.B) {
178+
benchmarkAnalyzerStress(b, "G115", func() string { return generateG115Deep(300, 1000) })
179+
}
180+
181+
func BenchmarkAnalysisG602_Wide(b *testing.B) {
182+
benchmarkAnalyzerStress(b, "G602", func() string { return generateG602Wide(500, 200) })
183+
}
184+
185+
func BenchmarkAnalysisG407_Deep(b *testing.B) {
186+
benchmarkAnalyzerStress(b, "G407", func() string { return generateG407Stress(1000) })
187+
}

0 commit comments

Comments
 (0)