Skip to content

Commit eb252ba

Browse files
authored
Fix G602 analyzer panic that kills gosec process (#1491)
* update go version to 1.25.7 * Fix G602 analyzer panic that kills gosec process * guard against nil block * add tests for nil guard fixes
1 parent 20d71a0 commit eb252ba

2 files changed

Lines changed: 140 additions & 4 deletions

File tree

analyzers/slice_bounds.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,14 @@ func (s *sliceBoundsState) Reset() {
138138
clear(s.trackCache)
139139
}
140140

141-
func runSliceBounds(pass *analysis.Pass) (any, error) {
141+
func runSliceBounds(pass *analysis.Pass) (result any, err error) {
142+
defer func() {
143+
if r := recover(); r != nil {
144+
result = nil
145+
err = nil // Return nil error to allow other analyzers to continue
146+
}
147+
}()
148+
142149
ssaResult, err := getSSAResult(pass)
143150
if err != nil {
144151
return nil, err
@@ -201,6 +208,9 @@ func runSliceBounds(pass *analysis.Pass) (any, error) {
201208
}
202209
}
203210
case *ssa.IndexAddr:
211+
if instr.X == nil {
212+
break
213+
}
204214
switch indexInstr := instr.X.(type) {
205215
case *ssa.Const:
206216
if _, ok := indexInstr.Type().Underlying().(*types.Slice); ok {
@@ -257,7 +267,12 @@ func runSliceBounds(pass *analysis.Pass) (any, error) {
257267
}
258268
}
259269

260-
for i, block := range ifref.Block().Succs {
270+
// Guard against nil Block()
271+
ifBlock := ifref.Block()
272+
if ifBlock == nil {
273+
continue
274+
}
275+
for i, block := range ifBlock.Succs {
261276
if i == 1 {
262277
bound = invBound(bound)
263278
}
@@ -312,8 +327,11 @@ func runSliceBounds(pass *analysis.Pass) (any, error) {
312327
}
313328
}
314329
} else if nestedIfInstr, ok := instr.(*ssa.If); ok {
315-
for _, nestedBlock := range nestedIfInstr.Block().Succs {
316-
processBlock(nestedBlock, depth)
330+
// Guard against nil Block()
331+
if nestedIfBlock := nestedIfInstr.Block(); nestedIfBlock != nil {
332+
for _, nestedBlock := range nestedIfBlock.Succs {
333+
processBlock(nestedBlock, depth)
334+
}
317335
}
318336
}
319337
}
@@ -336,6 +354,9 @@ func runSliceBounds(pass *analysis.Pass) (any, error) {
336354
// extractLenBound checks if the binop is of form "Var < Len + Offset" or equivalent patterns
337355
// (including offsets on the left-hand side like "(Var + Const) < Len")
338356
func extractLenBound(binop *ssa.BinOp) (ssa.Value, int, bool) {
357+
if binop == nil {
358+
return nil, 0, false
359+
}
339360
// Only handle Less Than for now
340361
if binop.Op != token.LSS {
341362
return nil, 0, false
@@ -564,6 +585,10 @@ func (s *sliceBoundsState) extractIntValueIndexAddr(refinstr *ssa.IndexAddr, sli
564585
var hasStart bool
565586
var next ssa.Value
566587
for _, edge := range p.Edges {
588+
// Guard against nil edges
589+
if edge == nil {
590+
continue
591+
}
567592
eBase, eOffset := decomposeIndex(edge)
568593
if val, ok := GetConstantInt64(eBase); ok {
569594
start = int(val) + eOffset
@@ -797,6 +822,9 @@ func invBound(bound bound) bound {
797822
var errExtractBinOp = errors.New("unable to extract constant from binop")
798823

799824
func extractBinOpBound(binop *ssa.BinOp) (bound, int, error) {
825+
if binop == nil {
826+
return lowerUnbounded, 0, errExtractBinOp
827+
}
800828
if binop.X != nil {
801829
if x, ok := binop.X.(*ssa.Const); ok {
802830
if x.Value == nil {

analyzers/slice_bounds_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// (c) Copyright gosec's authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package analyzers
16+
17+
import (
18+
"go/token"
19+
"testing"
20+
21+
"golang.org/x/tools/go/ssa"
22+
)
23+
24+
// TestExtractBinOpBound_NilGuards tests nil safety in extractBinOpBound
25+
func TestExtractBinOpBound_NilGuards(t *testing.T) {
26+
// Test nil binop
27+
bound, value, err := extractBinOpBound(nil)
28+
if err == nil {
29+
t.Error("expected error for nil binop")
30+
}
31+
if bound != lowerUnbounded {
32+
t.Errorf("expected lowerUnbounded, got %v", bound)
33+
}
34+
if value != 0 {
35+
t.Errorf("expected value 0, got %d", value)
36+
}
37+
}
38+
39+
// TestExtractLenBound_NilGuards tests nil safety in extractLenBound
40+
func TestExtractLenBound_NilGuards(t *testing.T) {
41+
// Test nil binop
42+
val, offset, ok := extractLenBound(nil)
43+
if ok {
44+
t.Error("expected false for nil binop")
45+
}
46+
if val != nil {
47+
t.Errorf("expected nil value, got %v", val)
48+
}
49+
if offset != 0 {
50+
t.Errorf("expected offset 0, got %d", offset)
51+
}
52+
}
53+
54+
// TestSliceBoundsNilSafety tests that the analyzer doesn't crash on nil values
55+
func TestSliceBoundsNilSafety(t *testing.T) {
56+
t.Run("extractBinOpBound with nil", func(t *testing.T) {
57+
defer func() {
58+
if r := recover(); r != nil {
59+
t.Errorf("extractBinOpBound panicked on nil input: %v", r)
60+
}
61+
}()
62+
_, _, _ = extractBinOpBound(nil)
63+
})
64+
65+
t.Run("extractLenBound with nil", func(t *testing.T) {
66+
defer func() {
67+
if r := recover(); r != nil {
68+
t.Errorf("extractLenBound panicked on nil input: %v", r)
69+
}
70+
}()
71+
_, _, _ = extractLenBound(nil)
72+
})
73+
74+
t.Run("extractBinOpBound with binop having nil X and Y", func(t *testing.T) {
75+
defer func() {
76+
if r := recover(); r != nil {
77+
t.Errorf("extractBinOpBound panicked on binop with nil X/Y: %v", r)
78+
}
79+
}()
80+
binop := &ssa.BinOp{Op: token.LSS}
81+
// X and Y are nil by default
82+
_, _, _ = extractBinOpBound(binop)
83+
})
84+
}
85+
86+
// TestInvBound tests the invBound function
87+
func TestInvBound(t *testing.T) {
88+
tests := []struct {
89+
name string
90+
input bound
91+
expected bound
92+
}{
93+
{"lowerUnbounded", lowerUnbounded, upperUnbounded},
94+
{"upperUnbounded", upperUnbounded, lowerUnbounded},
95+
{"upperBounded", upperBounded, unbounded},
96+
{"unbounded", unbounded, upperBounded},
97+
{"bounded", bounded, bounded},
98+
}
99+
100+
for _, tt := range tests {
101+
t.Run(tt.name, func(t *testing.T) {
102+
result := invBound(tt.input)
103+
if result != tt.expected {
104+
t.Errorf("invBound(%v) = %v, want %v", tt.input, result, tt.expected)
105+
}
106+
})
107+
}
108+
}

0 commit comments

Comments
 (0)