MatchCallByPackage updated to avoid GetCallObject

There seems to be an inconsistency in the way that the type.Info.Uses
map is populated by the type checker in Go 1.5 and the latest release.

It is possible to ascertain the package that relates to an object 1.7.x
release but this does not work for earlier Go versions.

To work around this limitation we now track imports, and monitor if they
are aliased or initalization only imports.
This commit is contained in:
Grant Murphy 2016-11-07 09:13:20 -08:00
parent d16326051f
commit 7a275fd0ad
3 changed files with 89 additions and 20 deletions

View file

@ -27,6 +27,13 @@ import (
"strings" "strings"
) )
// ImportInfo is used to track aliased and initialization only imports.
type ImportInfo struct {
Imported map[string]string
Aliased map[string]string
InitOnly map[string]bool
}
// The Context is populated with data parsed from the source code as it is scanned. // The Context is populated with data parsed from the source code as it is scanned.
// It is passed through to all rule functions as they are called. Rules may use // It is passed through to all rule functions as they are called. Rules may use
// this data in conjunction withe the encoutered AST node. // this data in conjunction withe the encoutered AST node.
@ -37,6 +44,7 @@ type Context struct {
Pkg *types.Package Pkg *types.Package
Root *ast.File Root *ast.File
Config map[string]interface{} Config map[string]interface{}
Imports *ImportInfo
} }
// The Rule interface used by all rules supported by GAS. // The Rule interface used by all rules supported by GAS.
@ -77,8 +85,20 @@ func NewAnalyzer(conf map[string]interface{}, logger *log.Logger) Analyzer {
ignoreNosec: conf["ignoreNosec"].(bool), ignoreNosec: conf["ignoreNosec"].(bool),
ruleset: make(RuleSet), ruleset: make(RuleSet),
Issues: make([]Issue, 0), Issues: make([]Issue, 0),
context: Context{token.NewFileSet(), nil, nil, nil, nil, nil}, context: Context{
logger: logger, token.NewFileSet(),
nil,
nil,
nil,
nil,
nil,
&ImportInfo{
make(map[string]string),
make(map[string]string),
make(map[string]bool),
},
},
logger: logger,
} }
// TODO(tkelsey): use the inc/exc lists // TODO(tkelsey): use the inc/exc lists
@ -110,6 +130,10 @@ func (gas *Analyzer) process(filename string, source interface{}) error {
return err return err
} }
for _, pkg := range gas.context.Pkg.Imports() {
gas.context.Imports.Imported[pkg.Path()] = pkg.Name()
}
ast.Walk(gas, root) ast.Walk(gas, root)
gas.Stats.NumFiles++ gas.Stats.NumFiles++
} }
@ -169,6 +193,20 @@ func (gas *Analyzer) ignore(n ast.Node) bool {
// Rule methods added with AddRule will be invoked as necessary. // Rule methods added with AddRule will be invoked as necessary.
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor { func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
if !gas.ignore(n) { if !gas.ignore(n) {
// Track aliased and initialization imports
if imported, ok := n.(*ast.ImportSpec); ok {
if imported.Name != nil {
path := strings.Trim(imported.Path.Value, `"`)
if imported.Name.Name == "_" {
// Initialization import
gas.context.Imports.InitOnly[path] = true
} else {
// Aliased import
gas.context.Imports.Aliased[imported.Name.Name] = path
}
}
}
if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok { if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok {
for _, rule := range val { for _, rule := range val {
ret, err := rule.Match(n, &gas.context) ret, err := rule.Match(n, &gas.context)

View file

@ -47,19 +47,45 @@ func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
return nil return nil
} }
// MatchCallByObject ses the type checker to resolve the associated object with a // MatchCallByPackage ensures that the specified package is imported,
// particular *ast.CallExpr. This object is used to determine if the // adjusts the name for any aliases and ignores cases that are
// package and identifier name matches the passed in parameters. // initialization only imports.
// //
// Usage: // Usage:
// node, obj := MatchCall(n, ctx, "math/rand", "Read") // node, matched := MatchCallByPackage(n, ctx, "math/rand", "Read")
// //
func MatchCallByObject(n ast.Node, c *Context, pkg, name string) (*ast.CallExpr, types.Object) { func MatchCallByPackage(n ast.Node, c *Context, pkg string, names ...string) (*ast.CallExpr, bool) {
call, obj := GetCallObject(n, c)
if obj != nil && obj.Pkg().Path() == pkg && obj.Name() == name { importName, imported := c.Imports.Imported[pkg]
return call, obj if !imported {
return nil, false
} }
return nil, nil
if _, initonly := c.Imports.InitOnly[pkg]; initonly {
return nil, false
}
if alias, ok := c.Imports.Aliased[pkg]; ok {
importName = alias
}
switch node := n.(type) {
case *ast.CallExpr:
switch fn := node.Fun.(type) {
case *ast.SelectorExpr:
switch expr := fn.X.(type) {
case *ast.Ident:
if expr.Name == importName {
for _, name := range names {
if fn.Sel.Name == name {
return node, true
}
}
}
}
}
}
return nil, false
} }
// MatchCompLit will match an ast.CompositeLit if its string value obays the given regex. // MatchCompLit will match an ast.CompositeLit if its string value obays the given regex.

View file

@ -16,33 +16,38 @@ package rules
import ( import (
"go/ast" "go/ast"
"regexp"
gas "github.com/GoASTScanner/gas/core" gas "github.com/GoASTScanner/gas/core"
) )
type UsesWeakCryptography struct { type UsesWeakCryptography struct {
gas.MetaData gas.MetaData
pattern *regexp.Regexp blacklist map[string][]string
} }
func (r *UsesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (r *UsesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node := gas.MatchCall(n, r.pattern); node != nil {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil for pkg, funcs := range r.blacklist {
if _, matched := gas.MatchCallByPackage(n, c, pkg, funcs...); matched {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
} }
return nil, nil return nil, nil
} }
// Uses des.* md5.* or rc4.* // Uses des.* md5.* or rc4.*
func NewUsesWeakCryptography(conf map[string]interface{}) (r gas.Rule, n ast.Node) { func NewUsesWeakCryptography(conf map[string]interface{}) (gas.Rule, ast.Node) {
r = &UsesWeakCryptography{ calls := make(map[string][]string)
pattern: regexp.MustCompile(`des\.NewCipher|des\.NewTripleDESCipher|md5\.New|md5\.Sum|rc4\.NewCipher`), calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
calls["crypto/md5"] = []string{"New", "Sum"}
calls["crypto/rc4"] = []string{"NewCipher"}
rule := &UsesWeakCryptography{
blacklist: calls,
MetaData: gas.MetaData{ MetaData: gas.MetaData{
Severity: gas.Medium, Severity: gas.Medium,
Confidence: gas.High, Confidence: gas.High,
What: "Use of weak cryptographic primitive", What: "Use of weak cryptographic primitive",
}, },
} }
n = (*ast.CallExpr)(nil) return rule, (*ast.CallExpr)(nil)
return
} }