Merge pull request #75 from GoASTScanner/experimental

Track package imports and aliases
This commit is contained in:
Grant Murphy 2016-11-07 19:40:26 -08:00 committed by GitHub
commit 9f54d257fe
5 changed files with 128 additions and 21 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,7 +85,19 @@ 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},
context: Context{
token.NewFileSet(),
nil,
nil,
nil,
nil,
nil,
&ImportInfo{
make(map[string]string),
make(map[string]string),
make(map[string]bool),
},
},
logger: logger,
}
@ -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

@ -27,7 +27,7 @@ type BlacklistImport struct {
func (r *BlacklistImport) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if node, ok := n.(*ast.ImportSpec); ok {
if r.Path == node.Path.Value {
if r.Path == node.Path.Value && node.Name.String() != "_" {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
}

38
rules/blacklist_test.go Normal file
View file

@ -0,0 +1,38 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
gas "github.com/GoASTScanner/gas/core"
"testing"
)
const initOnlyImportSrc = `
package main
import (
_ "crypto/md5"
"fmt"
)
func main() {
for _, arg := range os.Args {
fmt.Println(arg)
}
}`
func TestInitOnlyImport(t *testing.T) {
config := map[string]interface{}{"ignoreNosec": false}
analyzer := gas.NewAnalyzer(config, nil)
analyzer.AddRule(NewBlacklist_crypto_md5(config))
issues := gasTestRunner(initOnlyImportSrc, analyzer)
checkTestResults(t, issues, 0, "")
}

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 {
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)
}