diff --git a/core/analyzer.go b/core/analyzer.go index 304e045..45b1b7b 100644 --- a/core/analyzer.go +++ b/core/analyzer.go @@ -92,9 +92,12 @@ func (gas *Analyzer) process(filename string, source interface{}) error { // here we get type info gas.context.Info = &types.Info{ - Types: make(map[ast.Expr]types.TypeAndValue), - Defs: make(map[*ast.Ident]types.Object), - Uses: make(map[*ast.Ident]types.Object), + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + Implicits: make(map[ast.Node]types.Object), } conf := types.Config{Importer: importer.Default()} diff --git a/core/resolve.go b/core/resolve.go new file mode 100644 index 0000000..6c5070c --- /dev/null +++ b/core/resolve.go @@ -0,0 +1,79 @@ +// (c) Copyright 2016 Hewlett Packard Enterprise Development LP +// +// 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 core + +import "go/ast" + +func resolveIdent(n *ast.Ident, c *Context) bool { + if n.Obj == nil || n.Obj.Kind != ast.Var { + return true + } + if node, ok := n.Obj.Decl.(ast.Node); ok { + return TryResolve(node, c) + } + return false +} + +func resolveAssign(n *ast.AssignStmt, c *Context) bool { + for _, arg := range n.Rhs { + if !TryResolve(arg, c) { + return false + } + } + return true +} + +func resolveCompLit(n *ast.CompositeLit, c *Context) bool { + for _, arg := range n.Elts { + if !TryResolve(arg, c) { + return false + } + } + return true +} + +func resolveBinExpr(n *ast.BinaryExpr, c *Context) bool { + return (TryResolve(n.X, c) && TryResolve(n.Y, c)) +} + +func resolveCallExpr(n *ast.CallExpr, c *Context) bool { + // TODO(tkelsey): next step, full function resolution + return false +} + +func TryResolve(n ast.Node, c *Context) bool { + switch node := n.(type) { + case *ast.BasicLit: + return true + + case *ast.CompositeLit: + return resolveCompLit(node, c) + + case *ast.Ident: + return resolveIdent(node, c) + + case *ast.AssignStmt: + return resolveAssign(node, c) + + case *ast.CallExpr: + return resolveCallExpr(node, c) + + case *ast.BinaryExpr: + return resolveBinExpr(node, c) + } + + ast.Print(c.FileSet, n) + return false +} diff --git a/rules/subproc.go b/rules/subproc.go index 99f8f68..d47ae26 100644 --- a/rules/subproc.go +++ b/rules/subproc.go @@ -15,10 +15,11 @@ package rules import ( - gas "github.com/HewlettPackard/gas/core" "go/ast" "regexp" "strings" + + gas "github.com/HewlettPackard/gas/core" ) type Subprocess struct { @@ -27,10 +28,8 @@ type Subprocess struct { func (r *Subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { if node := gas.MatchCall(n, r.pattern); node != nil { - // call with variable command or arguments for _, arg := range node.Args { - if _, test := arg.(*ast.BasicLit); !test { - // TODO: try to resolve the symbol ... + if !gas.TryResolve(arg, c) { what := "Subprocess launching with variable." return gas.NewIssue(c, n, what, gas.High, gas.High), nil } @@ -52,7 +51,7 @@ func (r *Subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func NewSubproc() (r gas.Rule, n ast.Node) { r = &Subprocess{ - pattern: regexp.MustCompile(`^exec.Command$`), + pattern: regexp.MustCompile(`^exec\.Command|syscall\.Exec$`), } n = (*ast.CallExpr)(nil) return diff --git a/rules/subproc_test.go b/rules/subproc_test.go index 4f9528c..d71ef7b 100644 --- a/rules/subproc_test.go +++ b/rules/subproc_test.go @@ -33,7 +33,8 @@ func TestSubprocess(t *testing.T) { ) func main() { - cmd := exec.Command("/bin/sleep", "5") + val := "/bin/" + "sleep" + cmd := exec.Command(val, "5") err := cmd.Start() if err != nil { log.Fatal(err) @@ -59,7 +60,7 @@ func TestSubprocessVar(t *testing.T) { ) func main() { - run := "sleep" + run := "sleep" + someFunc() cmd := exec.Command(run, "5") err := cmd.Start() if err != nil { @@ -98,3 +99,22 @@ func TestSubprocessPath(t *testing.T) { checkTestResults(t, issues, 1, "Subprocess launching with partial path.") } + +func TestSubprocessSyscall(t *testing.T) { + analyzer := gas.NewAnalyzer(false, nil, nil) + analyzer.AddRule(NewSubproc()) + + issues := gasTestRunner(` + package main + + import ( + "log" + "os/exec" + ) + + func main() { + syscall.Exec("/bin/cat", []string{ "/etc/passwd" }, nil) + }`, analyzer) + + checkTestResults(t, issues, 1, "Subprocess launching should be audited.") +}