Skip to content

Commit 0ae0174

Browse files
authored
Refactor to support duplicate imports with different aliases (#865)
The existing code assumed imports to be either imported, or imported with an alias. Badly formatted files may have duplicate imports for a package, using different aliases. This patch refactors the code, and; Introduces a new `GetImportedNames` function, which returns all name(s) and aliase(s) for a package, which effectively combines `GetAliasedName` and `GetImportedName`, but adding support for duplicate imports. The old `GetAliasedName` and `GetImportedName` functions have been rewritten to use the new function and marked deprecated, but could be removed if there are no external consumers. With this patch, the linter is able to detect issues in files such as; package main import ( crand "crypto/rand" "math/big" "math/rand" rand2 "math/rand" rand3 "math/rand" ) func main() { _, _ = crand.Int(crand.Reader, big.NewInt(int64(2))) // good _ = rand.Intn(2) // bad _ = rand2.Intn(2) // bad _ = rand3.Intn(2) // bad } Before this patch, only a single issue would be detected: gosec --quiet . [main.go:14] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH) 13: > 14: _ = rand.Intn(2) // bad 15: _ = rand2.Intn(2) // bad With this patch, all issues are identified: gosec --quiet . [main.go:16] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH) 15: _ = rand2.Intn(2) // bad > 16: _ = rand3.Intn(2) // bad 17: } [main.go:15] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH) 14: _ = rand.Intn(2) // bad > 15: _ = rand2.Intn(2) // bad 16: _ = rand3.Intn(2) // bad [main.go:14] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH) 13: > 14: _ = rand.Intn(2) // bad 15: _ = rand2.Intn(2) // bad While working on this change, I noticed that ImportTracker.TrackFile() was not able to find import aliases; Analyser.Check() called both ImportTracker.TrackFile() and ast.Walk(), which (with the updated ImportTracker) resulted in importes to be in- correctly included multiple times (once with the correct alias, once with the default). I updated ImportTracker.TrackFile() to fix this, but with the updated ImportTracker, Analyser.Check() no longer has to call ImportTracker.TrackFile() separately, as ast.Walk() already handles the file, and will find all imports. Signed-off-by: Sebastiaan van Stijn <[email protected]> Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent a2719d3 commit 0ae0174

File tree

5 files changed

+71
-78
lines changed

5 files changed

+71
-78
lines changed

analyzer.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,9 @@ func (gosec *Analyzer) Process(buildTags []string, packagePaths ...string) error
172172
for {
173173
select {
174174
case s := <-j:
175-
packages, err := gosec.load(s, config)
175+
pkgs, err := gosec.load(s, config)
176176
select {
177-
case r <- result{pkgPath: s, pkgs: packages, err: err}:
177+
case r <- result{pkgPath: s, pkgs: pkgs, err: err}:
178178
case <-quit:
179179
// we've been told to stop, probably an error while
180180
// processing a previous result.
@@ -296,7 +296,6 @@ func (gosec *Analyzer) Check(pkg *packages.Package) {
296296
gosec.context.Pkg = pkg.Types
297297
gosec.context.PkgFiles = pkg.Syntax
298298
gosec.context.Imports = NewImportTracker()
299-
gosec.context.Imports.TrackFile(file)
300299
gosec.context.PassedValues = make(map[string]interface{})
301300
ast.Walk(gosec, file)
302301
gosec.stats.NumFiles++
@@ -434,6 +433,12 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
434433
}
435434
return gosec
436435
}
436+
switch i := n.(type) {
437+
case *ast.File:
438+
// Using ast.File instead of ast.ImportSpec, so that we can track
439+
// all imports at once.
440+
gosec.context.Imports.TrackFile(i)
441+
}
437442

438443
// Get any new rule exclusions.
439444
ignoredRules := gosec.ignore(n)
@@ -453,9 +458,6 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
453458
// Push the new set onto the stack.
454459
gosec.context.Ignores = append([]map[string][]SuppressionInfo{ignores}, gosec.context.Ignores...)
455460

456-
// Track aliased and initialization imports
457-
gosec.context.Imports.TrackImport(n)
458-
459461
for _, rule := range gosec.ruleset.RegisteredFor(n) {
460462
// Check if all rules are ignored.
461463
generalSuppressions, generalIgnored := ignores[aliasOfAllRules]

helpers.go

+17-42
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,20 @@ import (
3737
//
3838
// node, matched := MatchCallByPackage(n, ctx, "math/rand", "Read")
3939
func MatchCallByPackage(n ast.Node, c *Context, pkg string, names ...string) (*ast.CallExpr, bool) {
40-
importedName, found := GetAliasedName(pkg, c)
40+
importedNames, found := GetImportedNames(pkg, c)
4141
if !found {
42-
importedName, found = GetImportedName(pkg, c)
43-
if !found {
44-
return nil, false
45-
}
42+
return nil, false
4643
}
4744

4845
if callExpr, ok := n.(*ast.CallExpr); ok {
4946
packageName, callName, err := GetCallInfo(callExpr, c)
5047
if err != nil {
5148
return nil, false
5249
}
53-
if packageName == importedName {
50+
for _, in := range importedNames {
51+
if packageName != in {
52+
continue
53+
}
5454
for _, name := range names {
5555
if callName == name {
5656
return callExpr, true
@@ -247,48 +247,23 @@ func GetBinaryExprOperands(be *ast.BinaryExpr) []ast.Node {
247247
return result
248248
}
249249

250-
// GetImportedName returns the name used for the package within the
251-
// code. It will ignore initialization only imports.
252-
func GetImportedName(path string, ctx *Context) (string, bool) {
253-
importName, imported := ctx.Imports.Imported[path]
254-
if !imported {
255-
return "", false
256-
}
257-
258-
if _, initonly := ctx.Imports.InitOnly[path]; initonly {
259-
return "", false
260-
}
261-
262-
return importName, true
263-
}
264-
265-
// GetAliasedName returns the aliased name used for the package within the
266-
// code. It will ignore initialization only imports.
267-
func GetAliasedName(path string, ctx *Context) (string, bool) {
268-
importName, imported := ctx.Imports.Aliased[path]
269-
if !imported {
270-
return "", false
271-
}
272-
273-
if _, initonly := ctx.Imports.InitOnly[path]; initonly {
274-
return "", false
275-
}
276-
277-
return importName, true
250+
// GetImportedNames returns the name(s)/alias(es) used for the package within
251+
// the code. It ignores initialization-only imports.
252+
func GetImportedNames(path string, ctx *Context) (names []string, found bool) {
253+
importNames, imported := ctx.Imports.Imported[path]
254+
return importNames, imported
278255
}
279256

280257
// GetImportPath resolves the full import path of an identifier based on
281258
// the imports in the current context(including aliases).
282259
func GetImportPath(name string, ctx *Context) (string, bool) {
283260
for path := range ctx.Imports.Imported {
284-
if imported, ok := GetImportedName(path, ctx); ok && imported == name {
285-
return path, true
286-
}
287-
}
288-
289-
for path := range ctx.Imports.Aliased {
290-
if imported, ok := GetAliasedName(path, ctx); ok && imported == name {
291-
return path, true
261+
if imported, ok := GetImportedNames(path, ctx); ok {
262+
for _, n := range imported {
263+
if n == name {
264+
return path, true
265+
}
266+
}
292267
}
293268
}
294269

import_tracker.go

+25-28
Original file line numberDiff line numberDiff line change
@@ -22,54 +22,51 @@ import (
2222
// by a source file. It is able to differentiate between plain imports, aliased
2323
// imports and init only imports.
2424
type ImportTracker struct {
25-
Imported map[string]string
26-
Aliased map[string]string
27-
InitOnly map[string]bool
25+
// Imported is a map of Imported with their associated names/aliases.
26+
Imported map[string][]string
2827
}
2928

3029
// NewImportTracker creates an empty Import tracker instance
3130
func NewImportTracker() *ImportTracker {
3231
return &ImportTracker{
33-
make(map[string]string),
34-
make(map[string]string),
35-
make(map[string]bool),
32+
Imported: make(map[string][]string),
3633
}
3734
}
3835

3936
// TrackFile track all the imports used by the supplied file
4037
func (t *ImportTracker) TrackFile(file *ast.File) {
4138
for _, imp := range file.Imports {
42-
path := strings.Trim(imp.Path.Value, `"`)
43-
parts := strings.Split(path, "/")
44-
if len(parts) > 0 {
45-
name := parts[len(parts)-1]
46-
t.Imported[path] = name
47-
}
39+
t.TrackImport(imp)
4840
}
4941
}
5042

5143
// TrackPackages tracks all the imports used by the supplied packages
5244
func (t *ImportTracker) TrackPackages(pkgs ...*types.Package) {
5345
for _, pkg := range pkgs {
54-
t.Imported[pkg.Path()] = pkg.Name()
46+
t.Imported[pkg.Path()] = []string{pkg.Name()}
5547
}
5648
}
5749

58-
// TrackImport tracks imports and handles the 'unsafe' import
59-
func (t *ImportTracker) TrackImport(n ast.Node) {
60-
if imported, ok := n.(*ast.ImportSpec); ok {
61-
path := strings.Trim(imported.Path.Value, `"`)
62-
if imported.Name != nil {
63-
if imported.Name.Name == "_" {
64-
// Initialization only import
65-
t.InitOnly[path] = true
66-
} else {
67-
// Aliased import
68-
t.Aliased[path] = imported.Name.Name
69-
}
70-
}
71-
if path == "unsafe" {
72-
t.Imported[path] = path
50+
// TrackImport tracks imports.
51+
func (t *ImportTracker) TrackImport(imported *ast.ImportSpec) {
52+
importPath := strings.Trim(imported.Path.Value, `"`)
53+
if imported.Name != nil {
54+
if imported.Name.Name == "_" {
55+
// Initialization only import
56+
} else {
57+
// Aliased import
58+
t.Imported[importPath] = append(t.Imported[importPath], imported.Name.String())
7359
}
60+
} else {
61+
t.Imported[importPath] = append(t.Imported[importPath], importName(importPath))
62+
}
63+
}
64+
65+
func importName(importPath string) string {
66+
parts := strings.Split(importPath, "/")
67+
name := importPath
68+
if len(parts) > 0 {
69+
name = parts[len(parts)-1]
7470
}
71+
return name
7572
}

import_tracker_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ var _ = Describe("Import Tracker", func() {
2727
files := pkgs[0].Syntax
2828
Expect(files).Should(HaveLen(1))
2929
tracker.TrackFile(files[0])
30-
Expect(tracker.Imported).Should(Equal(map[string]string{"fmt": "fmt"}))
30+
Expect(tracker.Imported).Should(Equal(map[string][]string{"fmt": {"fmt"}}))
3131
})
3232
It("should parse the named imports from file", func() {
3333
tracker := gosec.NewImportTracker()
@@ -47,7 +47,7 @@ var _ = Describe("Import Tracker", func() {
4747
files := pkgs[0].Syntax
4848
Expect(files).Should(HaveLen(1))
4949
tracker.TrackFile(files[0])
50-
Expect(tracker.Imported).Should(Equal(map[string]string{"fmt": "fmt"}))
50+
Expect(tracker.Imported).Should(Equal(map[string][]string{"fmt": {"fm"}}))
5151
})
5252
})
5353
})

testutils/source.go

+19
Original file line numberDiff line numberDiff line change
@@ -3196,6 +3196,25 @@ func main() {
31963196
println(bad)
31973197
}
31983198
`}, 1, gosec.NewConfig()},
3199+
{[]string{`
3200+
package main
3201+
3202+
import (
3203+
crand "crypto/rand"
3204+
"math/big"
3205+
"math/rand"
3206+
rand2 "math/rand"
3207+
rand3 "math/rand"
3208+
)
3209+
3210+
func main() {
3211+
_, _ = crand.Int(crand.Reader, big.NewInt(int64(2))) // good
3212+
3213+
_ = rand.Intn(2) // bad
3214+
_ = rand2.Intn(2) // bad
3215+
_ = rand3.Intn(2) // bad
3216+
}
3217+
`}, 3, gosec.NewConfig()},
31993218
}
32003219

32013220
// SampleCodeG501 - Blocklisted import MD5

0 commit comments

Comments
 (0)