Skip to content

Commit 9cb83e1

Browse files
rungccojocar
authored andcommitted
Add a rule which detects when there is potential integer overflow (#422)
* Add G109(Potential Integer OverFlow Detection) Signed-off-by: Hiroki Suezawa <[email protected]> * add CWE to G109(Potential Integer Overflow) Signed-off-by: Hiroki Suezawa <[email protected]> * Modify G109 to use gosec.Context Signed-off-by: Hiroki Suezawa <[email protected]>
1 parent f43a957 commit 9cb83e1

File tree

9 files changed

+191
-18
lines changed

9 files changed

+191
-18
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ directory you can supply `./...` as the input argument.
7171
- G106: Audit the use of ssh.InsecureIgnoreHostKey
7272
- G107: Url provided to HTTP request as taint input
7373
- G108: Profiling endpoint automatically exposed on /debug/pprof
74+
- G109: Potential Integer overflow made by strconv.Atoi result conversion to int16/32
7475
- G201: SQL query construction using format string
7576
- G202: SQL query construction using string concatenation
7677
- G203: Use of unescaped data in HTML templates

analyzer.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,16 @@ const LoadMode = packages.NeedName |
4747
// It is passed through to all rule functions as they are called. Rules may use
4848
// this data in conjunction withe the encountered AST node.
4949
type Context struct {
50-
FileSet *token.FileSet
51-
Comments ast.CommentMap
52-
Info *types.Info
53-
Pkg *types.Package
54-
PkgFiles []*ast.File
55-
Root *ast.File
56-
Config Config
57-
Imports *ImportTracker
58-
Ignores []map[string]bool
50+
FileSet *token.FileSet
51+
Comments ast.CommentMap
52+
Info *types.Info
53+
Pkg *types.Package
54+
PkgFiles []*ast.File
55+
Root *ast.File
56+
Config Config
57+
Imports *ImportTracker
58+
Ignores []map[string]bool
59+
PassedValues map[string]interface{}
5960
}
6061

6162
// Metrics used when reporting information about a scanning run.
@@ -204,6 +205,7 @@ func (gosec *Analyzer) Check(pkg *packages.Package) {
204205
gosec.context.PkgFiles = pkg.Syntax
205206
gosec.context.Imports = NewImportTracker()
206207
gosec.context.Imports.TrackFile(file)
208+
gosec.context.PassedValues = make(map[string]interface{})
207209
ast.Walk(gosec, file)
208210
gosec.stats.NumFiles++
209211
gosec.stats.NumLines += pkg.Fset.File(file.Pos()).LineCount()

issue.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ var IssueToCWE = map[string]Cwe{
5353
"G104": GetCwe("703"),
5454
"G106": GetCwe("322"),
5555
"G107": GetCwe("88"),
56+
"G109": GetCwe("190"),
5657
"G201": GetCwe("89"),
5758
"G202": GetCwe("89"),
5859
"G203": GetCwe("79"),

output/formatter_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,9 @@ var _ = Describe("Formatter", func() {
250250
Context("When using different report formats", func() {
251251

252252
grules := []string{"G101", "G102", "G103", "G104", "G106",
253-
"G107", "G201", "G202", "G203", "G204", "G301",
254-
"G302", "G303", "G304", "G305", "G401", "G402",
255-
"G403", "G404", "G501", "G502", "G503", "G504", "G505"}
253+
"G107", "G109", "G201", "G202", "G203", "G204",
254+
"G301", "G302", "G303", "G304", "G305", "G401",
255+
"G402", "G403", "G404", "G501", "G502", "G503", "G504", "G505"}
256256

257257
It("csv formatted report should contain the CWE mapping", func() {
258258
for _, rule := range grules {

rules/integer_overflow.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
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 rules
16+
17+
import (
18+
"fmt"
19+
"github.com/securego/gosec"
20+
"go/ast"
21+
)
22+
23+
type integerOverflowCheck struct {
24+
gosec.MetaData
25+
calls gosec.CallList
26+
}
27+
28+
func (i *integerOverflowCheck) ID() string {
29+
return i.MetaData.ID
30+
}
31+
32+
func (i *integerOverflowCheck) Match(node ast.Node, ctx *gosec.Context) (*gosec.Issue, error) {
33+
var atoiVarName map[string]ast.Node
34+
35+
// To check multiple lines, ctx.PassedValues is used to store temporary data.
36+
if _, ok := ctx.PassedValues[i.ID()]; !ok {
37+
atoiVarName = make(map[string]ast.Node)
38+
ctx.PassedValues[i.ID()] = atoiVarName
39+
} else if pv, ok := ctx.PassedValues[i.ID()].(map[string]ast.Node); ok {
40+
atoiVarName = pv
41+
} else {
42+
return nil, fmt.Errorf("PassedValues[%s] of Context is not map[string]ast.Node, but %T", i.ID(), ctx.PassedValues[i.ID()])
43+
}
44+
45+
// strconv.Atoi is a common function.
46+
// To reduce false positives, This rule detects code which is converted to int32/int16 only.
47+
switch n := node.(type) {
48+
case *ast.FuncDecl:
49+
// Clear atoiVarName by function
50+
ctx.PassedValues[i.ID()] = make(map[string]ast.Node)
51+
case *ast.AssignStmt:
52+
for _, expr := range n.Rhs {
53+
if callExpr, ok := expr.(*ast.CallExpr); ok && i.calls.ContainsCallExpr(callExpr, ctx, false) != nil {
54+
if idt, ok := n.Lhs[0].(*ast.Ident); ok && idt.Name != "_" {
55+
// Example:
56+
// v, _ := strconv.Atoi("1111")
57+
// Add "v" to atoiVarName map
58+
atoiVarName[idt.Name] = n
59+
}
60+
}
61+
}
62+
case *ast.CallExpr:
63+
if fun, ok := n.Fun.(*ast.Ident); ok {
64+
if fun.Name == "int32" || fun.Name == "int16" {
65+
if idt, ok := n.Args[0].(*ast.Ident); ok {
66+
if n, ok := atoiVarName[idt.Name]; ok {
67+
// Detect int32(v) and int16(v)
68+
return gosec.NewIssue(ctx, n, i.ID(), i.What, i.Severity, i.Confidence), nil
69+
}
70+
}
71+
}
72+
}
73+
}
74+
75+
return nil, nil
76+
}
77+
78+
// NewIntegerOverflowCheck detects if there is potential Integer OverFlow
79+
func NewIntegerOverflowCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
80+
calls := gosec.NewCallList()
81+
calls.Add("strconv", "Atoi")
82+
return &integerOverflowCheck{
83+
MetaData: gosec.MetaData{
84+
ID: id,
85+
Severity: gosec.High,
86+
Confidence: gosec.Medium,
87+
What: "Potential Integer overflow made by strconv.Atoi result conversion to int16/32",
88+
},
89+
calls: calls,
90+
}, []ast.Node{(*ast.FuncDecl)(nil), (*ast.AssignStmt)(nil), (*ast.CallExpr)(nil)}
91+
}

rules/rulelist.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func Generate(filters ...RuleFilter) RuleList {
6666
{"G106", "Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
6767
{"G107", "Url provided to HTTP request as taint input", NewSSRFCheck},
6868
{"G108", "Profiling endpoint is automatically exposed", NewPprofCheck},
69+
{"G109", "Converting strconv.Atoi result to int32/int16", NewIntegerOverflowCheck},
6970

7071
// injection
7172
{"G201", "SQL query construction using format string", NewSQLStrFormat},

rules/rules_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ var _ = Describe("gosec rules", func() {
8383
runner("G108", testutils.SampleCodeG108)
8484
})
8585

86+
It("should detect integer overflow", func() {
87+
runner("G109", testutils.SampleCodeG109)
88+
})
89+
8690
It("should detect sql injection via format strings", func() {
8791
runner("G201", testutils.SampleCodeG201)
8892
})

testutils/pkg.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,13 @@ func (p *TestPackage) CreateContext(filename string) *gosec.Context {
109109
pkgFile = strings.TrimPrefix(pkgFile, strip)
110110
if pkgFile == filename {
111111
ctx := &gosec.Context{
112-
FileSet: pkg.Fset,
113-
Root: file,
114-
Config: gosec.NewConfig(),
115-
Info: pkg.TypesInfo,
116-
Pkg: pkg.Types,
117-
Imports: gosec.NewImportTracker(),
112+
FileSet: pkg.Fset,
113+
Root: file,
114+
Config: gosec.NewConfig(),
115+
Info: pkg.TypesInfo,
116+
Pkg: pkg.Types,
117+
Imports: gosec.NewImportTracker(),
118+
PassedValues: make(map[string]interface{}),
118119
}
119120
ctx.Imports.TrackPackages(ctx.Pkg.Imports()...)
120121
return ctx

testutils/source.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,78 @@ func main() {
522522
})
523523
log.Fatal(http.ListenAndServe(":8080", nil))
524524
}`}, 0, gosec.NewConfig()}}
525+
526+
// SampleCodeG109 - Potential Integer OverFlow
527+
SampleCodeG109 = []CodeSample{
528+
// Bind to all networks explicitly
529+
{[]string{`
530+
package main
531+
532+
import (
533+
"fmt"
534+
"strconv"
535+
)
536+
537+
func main() {
538+
bigValue, err := strconv.Atoi("2147483648")
539+
if err != nil {
540+
panic(err)
541+
}
542+
value := int32(bigValue)
543+
fmt.Println(value)
544+
}`}, 1, gosec.NewConfig()}, {[]string{`
545+
package main
546+
547+
import (
548+
"fmt"
549+
"strconv"
550+
)
551+
552+
func main() {
553+
bigValue, err := strconv.Atoi("32768")
554+
if err != nil {
555+
panic(err)
556+
}
557+
if int16(bigValue) < 0 {
558+
fmt.Println(bigValue)
559+
}
560+
}`}, 1, gosec.NewConfig()}, {[]string{`
561+
package main
562+
563+
import (
564+
"fmt"
565+
"strconv"
566+
)
567+
568+
func main() {
569+
bigValue, err := strconv.Atoi("2147483648")
570+
if err != nil {
571+
panic(err)
572+
}
573+
fmt.Println(bigValue)
574+
}`}, 0, gosec.NewConfig()}, {[]string{`
575+
package main
576+
577+
import (
578+
"fmt"
579+
"strconv"
580+
)
581+
582+
func main() {
583+
bigValue, err := strconv.Atoi("2147483648")
584+
if err != nil {
585+
panic(err)
586+
}
587+
fmt.Println(bigValue)
588+
test()
589+
}
590+
591+
func test() {
592+
bigValue := 30
593+
value := int32(bigValue)
594+
fmt.Println(value)
595+
}`}, 0, gosec.NewConfig()}}
596+
525597
// SampleCodeG201 - SQL injection via format string
526598
SampleCodeG201 = []CodeSample{
527599
{[]string{`

0 commit comments

Comments
 (0)