Skip to content

Commit 7457825

Browse files
committed
rules/sdk: more accurately determine overflow for *int*(len(...)) by type & 32/64-bit architectures
Depending on the machine that the rule is being run on, determine if the result of an integer cast to len(value) will overflow e.g. * int8, uint8, int16, uint16 (len(value)) will always overflow on 32/64-bits * uint32(len(value)) shall overflow on 64-bit machines but not on 32-bit * int64(len(value)) cannot overflow on 64-bit machines nor on 32-bit * int(len(value)) cannot overflow on either 64-bit or 32-bit machines * uint(len(value)) cannot overflow on either 64-bit or 32-bit machines Fixes #54
1 parent d5d527a commit 7457825

File tree

2 files changed

+154
-2
lines changed

2 files changed

+154
-2
lines changed

rules/sdk/int_end_conversion_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package sdk
2+
3+
import "testing"
4+
5+
func TestCanOverflowChecks32Bits(t *testing.T) {
6+
if !is32Bit {
7+
t.Skip("Not running on a 64-bit machine!")
8+
}
9+
10+
cases := []struct {
11+
endKind string
12+
wantOverflow bool
13+
}{
14+
{"int8", true},
15+
{"int16", true},
16+
{"int32", false},
17+
{"int64", false},
18+
{"int", false},
19+
{"uint8", true},
20+
{"uint16", true},
21+
{"uint32", false},
22+
{"uint64", false},
23+
{"uint", false},
24+
}
25+
26+
for _, tt := range cases {
27+
tt := tt
28+
t.Run(tt.endKind, func(t *testing.T) {
29+
if got := canLenOverflow32(tt.endKind); got != tt.wantOverflow {
30+
t.Fatalf("Mismatch\n\tGot: %t\n\tWant:%t", got, tt.wantOverflow)
31+
}
32+
})
33+
}
34+
}
35+
36+
func TestCanOverflowChecks64Bits(t *testing.T) {
37+
if is32Bit {
38+
t.Skip("Not running on a 32-bit machine!")
39+
}
40+
41+
cases := []struct {
42+
endKind string
43+
wantOverflow bool
44+
}{
45+
{"int8", true},
46+
{"int16", true},
47+
{"int32", true},
48+
{"int", false},
49+
{"int64", false},
50+
{"uint8", true},
51+
{"uint16", true},
52+
{"uint32", true},
53+
{"uint64", false},
54+
{"uint", false},
55+
}
56+
57+
for _, tt := range cases {
58+
tt := tt
59+
t.Run(tt.endKind, func(t *testing.T) {
60+
if got := canLenOverflow64(tt.endKind); got != tt.wantOverflow {
61+
t.Fatalf("Mismatch\n\tGot: %t\n\tWant:%t", got, tt.wantOverflow)
62+
}
63+
})
64+
}
65+
}

rules/sdk/integer.go

+89-2
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,23 @@ func (i *integerOverflowCheck) Match(node ast.Node, ctx *gosec.Context) (*gosec.
7272
switch arg := arg.(type) {
7373
case *ast.CallExpr:
7474
// len() returns an int that is always >= 0, so it will fit in a uint, uint64, or int64.
75-
if argFun, ok := arg.Fun.(*ast.Ident); ok && argFun.Name == "len" && (fun.Name == "uint" || fun.Name == "uint64" || fun.Name == "int64") {
76-
return nil, nil
75+
argFun, ok := arg.Fun.(*ast.Ident)
76+
if !ok || argFun.Name != "len" {
77+
break
78+
}
79+
80+
// Please see the rules for determining if *int*(len(...)) can overflow
81+
// as per: https://github.com/cosmos/gosec/issues/54
82+
lenCanOverflow := canLenOverflow64
83+
if is32Bit {
84+
lenCanOverflow = canLenOverflow32
7785
}
86+
87+
if lenCanOverflow(fun.Name) {
88+
return gosec.NewIssue(ctx, n, i.ID(), i.What, i.Severity, i.Confidence), nil
89+
}
90+
return nil, nil
91+
7892
case *ast.SelectorExpr:
7993
// If the argument is being cast to its underlying type, there's no risk.
8094
if ctx.Info.TypeOf(arg).Underlying() == ctx.Info.TypeOf(fun) {
@@ -104,3 +118,76 @@ func NewIntegerCast(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
104118
},
105119
}, []ast.Node{(*ast.FuncDecl)(nil), (*ast.AssignStmt)(nil), (*ast.CallExpr)(nil)}
106120
}
121+
122+
// Please see the rules at https://github.com/cosmos/gosec/issues/54
123+
func canLenOverflow64(destKind string) bool {
124+
switch destKind {
125+
case "int8", "uint8", "int16", "uint16":
126+
return true
127+
128+
case "uint64":
129+
// uint64([0, maxInt64])
130+
return false
131+
132+
case "uint32":
133+
// uint32([0, maxInt64])
134+
return true
135+
136+
case "uint":
137+
// uint => uint64 => uint64([0, maxInt64])
138+
return false
139+
140+
case "int64":
141+
// int64([0, maxInt64])
142+
return false
143+
144+
case "int32":
145+
// int32([0, maxInt64])
146+
return true
147+
148+
case "int":
149+
// int64([0, maxInt64])
150+
return false
151+
152+
default:
153+
return true
154+
}
155+
}
156+
157+
const s = 1
158+
const is32Bit = (^uint(s-1))>>32 == 0 // #nosec
159+
160+
// Please see the rules at https://github.com/cosmos/gosec/issues/54
161+
func canLenOverflow32(destKind string) bool {
162+
switch destKind {
163+
case "int8", "uint8", "int16", "uint16":
164+
return true
165+
166+
case "uint64":
167+
// uint64([0, maxInt32])
168+
return false
169+
170+
case "uint32":
171+
// uint32([0, maxInt32])
172+
return false
173+
174+
case "uint":
175+
// uint => uint32 => uint32([0, maxInt32])
176+
return false
177+
178+
case "int64":
179+
// int64([0, maxInt32])
180+
return false
181+
182+
case "int32":
183+
// int32([0, maxInt32])
184+
return false
185+
186+
case "int":
187+
// int => int32 => int32([0, maxInt32])
188+
return false
189+
190+
default:
191+
return true
192+
}
193+
}

0 commit comments

Comments
 (0)