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"
)
// 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.
// It is passed through to all rule functions as they are called. Rules may use
// this data in conjunction withe the encoutered AST node.
@ -37,6 +44,7 @@ type Context struct {
Pkg *types.Package
Root *ast.File
Config map[string]interface{}
Imports *ImportInfo
}
// 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),
ruleset: make(RuleSet),
Issues: make([]Issue, 0),
context: Context{token.NewFileSet(), nil, nil, nil, nil, nil},
logger: logger,
context: Context{
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
@ -110,6 +130,10 @@ func (gas *Analyzer) process(filename string, source interface{}) error {
return err
}
for _, pkg := range gas.context.Pkg.Imports() {
gas.context.Imports.Imported[pkg.Path()] = pkg.Name()
}
ast.Walk(gas, root)
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.
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
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 {
for _, rule := range val {
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
}
// MatchCallByObject ses the type checker to resolve the associated object with a
// particular *ast.CallExpr. This object is used to determine if the
// package and identifier name matches the passed in parameters.
// MatchCallByPackage ensures that the specified package is imported,
// adjusts the name for any aliases and ignores cases that are
// initialization only imports.
//
// 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) {
call, obj := GetCallObject(n, c)
if obj != nil && obj.Pkg().Path() == pkg && obj.Name() == name {
return call, obj
func MatchCallByPackage(n ast.Node, c *Context, pkg string, names ...string) (*ast.CallExpr, bool) {
importName, imported := c.Imports.Imported[pkg]
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.

View file

@ -16,33 +16,38 @@ package rules
import (
"go/ast"
"regexp"
gas "github.com/GoASTScanner/gas/core"
)
type UsesWeakCryptography struct {
gas.MetaData
pattern *regexp.Regexp
blacklist map[string][]string
}
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
}
// Uses des.* md5.* or rc4.*
func NewUsesWeakCryptography(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
r = &UsesWeakCryptography{
pattern: regexp.MustCompile(`des\.NewCipher|des\.NewTripleDESCipher|md5\.New|md5\.Sum|rc4\.NewCipher`),
func NewUsesWeakCryptography(conf map[string]interface{}) (gas.Rule, ast.Node) {
calls := make(map[string][]string)
calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
calls["crypto/md5"] = []string{"New", "Sum"}
calls["crypto/rc4"] = []string{"NewCipher"}
rule := &UsesWeakCryptography{
blacklist: calls,
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: "Use of weak cryptographic primitive",
},
}
n = (*ast.CallExpr)(nil)
return
return rule, (*ast.CallExpr)(nil)
}