2016-07-20 11:02:01 +01:00
|
|
|
// (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.
|
|
|
|
|
2018-07-19 17:42:25 +01:00
|
|
|
package gosec
|
2016-07-20 11:02:01 +01:00
|
|
|
|
|
|
|
import (
|
2018-07-23 14:16:47 +01:00
|
|
|
"errors"
|
2016-07-20 11:02:01 +01:00
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
2016-11-04 18:20:28 +00:00
|
|
|
"go/types"
|
2018-07-23 14:16:47 +01:00
|
|
|
"os"
|
|
|
|
"os/user"
|
|
|
|
"path/filepath"
|
2019-04-25 11:47:13 +01:00
|
|
|
"regexp"
|
2018-07-23 14:16:47 +01:00
|
|
|
"runtime"
|
2016-07-20 11:02:01 +01:00
|
|
|
"strconv"
|
2018-07-23 14:16:47 +01:00
|
|
|
"strings"
|
2016-07-20 11:02:01 +01:00
|
|
|
)
|
|
|
|
|
2016-11-07 17:13:20 +00:00
|
|
|
// MatchCallByPackage ensures that the specified package is imported,
|
|
|
|
// adjusts the name for any aliases and ignores cases that are
|
|
|
|
// initialization only imports.
|
2016-11-04 18:20:28 +00:00
|
|
|
//
|
|
|
|
// Usage:
|
|
|
|
//
|
2022-08-08 08:28:41 +01:00
|
|
|
// node, matched := MatchCallByPackage(n, ctx, "math/rand", "Read")
|
2016-11-07 17:13:20 +00:00
|
|
|
func MatchCallByPackage(n ast.Node, c *Context, pkg string, names ...string) (*ast.CallExpr, bool) {
|
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 <github@gone.nl>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-10-17 09:59:18 +01:00
|
|
|
importedNames, found := GetImportedNames(pkg, c)
|
2016-11-18 22:09:10 +00:00
|
|
|
if !found {
|
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 <github@gone.nl>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-10-17 09:59:18 +01:00
|
|
|
return nil, false
|
2016-11-07 17:13:20 +00:00
|
|
|
}
|
|
|
|
|
2016-11-18 17:57:34 +00:00
|
|
|
if callExpr, ok := n.(*ast.CallExpr); ok {
|
|
|
|
packageName, callName, err := GetCallInfo(callExpr, c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
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 <github@gone.nl>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-10-17 09:59:18 +01:00
|
|
|
for _, in := range importedNames {
|
|
|
|
if packageName != in {
|
|
|
|
continue
|
|
|
|
}
|
2016-11-18 17:57:34 +00:00
|
|
|
for _, name := range names {
|
|
|
|
if callName == name {
|
|
|
|
return callExpr, true
|
2016-11-07 17:13:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-04 18:20:28 +00:00
|
|
|
}
|
2016-11-07 17:13:20 +00:00
|
|
|
return nil, false
|
2016-11-04 18:20:28 +00:00
|
|
|
}
|
|
|
|
|
2017-07-19 22:17:00 +01:00
|
|
|
// MatchCompLit will match an ast.CompositeLit based on the supplied type
|
|
|
|
func MatchCompLit(n ast.Node, ctx *Context, required string) *ast.CompositeLit {
|
|
|
|
if complit, ok := n.(*ast.CompositeLit); ok {
|
|
|
|
typeOf := ctx.Info.TypeOf(complit)
|
|
|
|
if typeOf.String() == required {
|
|
|
|
return complit
|
|
|
|
}
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-12 14:17:28 +01:00
|
|
|
// GetInt will read and return an integer value from an ast.BasicLit
|
2016-07-20 11:02:01 +01:00
|
|
|
func GetInt(n ast.Node) (int64, error) {
|
|
|
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.INT {
|
|
|
|
return strconv.ParseInt(node.Value, 0, 64)
|
|
|
|
}
|
|
|
|
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
|
|
|
}
|
|
|
|
|
2017-07-19 22:17:00 +01:00
|
|
|
// GetFloat will read and return a float value from an ast.BasicLit
|
2016-07-20 11:02:01 +01:00
|
|
|
func GetFloat(n ast.Node) (float64, error) {
|
|
|
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
|
|
|
|
return strconv.ParseFloat(node.Value, 64)
|
|
|
|
}
|
|
|
|
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
|
|
|
|
}
|
|
|
|
|
2017-07-19 22:17:00 +01:00
|
|
|
// GetChar will read and return a char value from an ast.BasicLit
|
2016-07-20 11:02:01 +01:00
|
|
|
func GetChar(n ast.Node) (byte, error) {
|
|
|
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
|
|
|
|
return node.Value[0], nil
|
|
|
|
}
|
|
|
|
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
|
|
|
}
|
|
|
|
|
2017-07-19 22:17:00 +01:00
|
|
|
// GetString will read and return a string value from an ast.BasicLit
|
2016-07-20 11:02:01 +01:00
|
|
|
func GetString(n ast.Node) (string, error) {
|
|
|
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
|
|
|
|
return strconv.Unquote(node.Value)
|
|
|
|
}
|
|
|
|
return "", fmt.Errorf("Unexpected AST node type: %T", n)
|
|
|
|
}
|
2016-11-04 21:39:22 +00:00
|
|
|
|
|
|
|
// GetCallObject returns the object and call expression and associated
|
|
|
|
// object for a given AST node. nil, nil will be returned if the
|
|
|
|
// object cannot be resolved.
|
|
|
|
func GetCallObject(n ast.Node, ctx *Context) (*ast.CallExpr, types.Object) {
|
|
|
|
switch node := n.(type) {
|
|
|
|
case *ast.CallExpr:
|
|
|
|
switch fn := node.Fun.(type) {
|
|
|
|
case *ast.Ident:
|
|
|
|
return node, ctx.Info.Uses[fn]
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
return node, ctx.Info.Uses[fn.Sel]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
2016-11-18 17:57:34 +00:00
|
|
|
|
|
|
|
// GetCallInfo returns the package or type and name associated with a
|
|
|
|
// call expression.
|
|
|
|
func GetCallInfo(n ast.Node, ctx *Context) (string, string, error) {
|
|
|
|
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.Obj != nil && expr.Obj.Kind == ast.Var {
|
|
|
|
t := ctx.Info.TypeOf(expr)
|
|
|
|
if t != nil {
|
|
|
|
return t.String(), fn.Sel.Name, nil
|
|
|
|
}
|
2017-07-19 22:17:00 +01:00
|
|
|
return "undefined", fn.Sel.Name, fmt.Errorf("missing type info")
|
2016-11-18 17:57:34 +00:00
|
|
|
}
|
2017-07-19 22:17:00 +01:00
|
|
|
return expr.Name, fn.Sel.Name, nil
|
2020-03-15 14:42:26 +00:00
|
|
|
case *ast.SelectorExpr:
|
|
|
|
if expr.Sel != nil {
|
|
|
|
t := ctx.Info.TypeOf(expr.Sel)
|
|
|
|
if t != nil {
|
|
|
|
return t.String(), fn.Sel.Name, nil
|
|
|
|
}
|
|
|
|
return "undefined", fn.Sel.Name, fmt.Errorf("missing type info")
|
|
|
|
}
|
2020-01-28 13:11:00 +00:00
|
|
|
case *ast.CallExpr:
|
|
|
|
switch call := expr.Fun.(type) {
|
|
|
|
case *ast.Ident:
|
|
|
|
if call.Name == "new" {
|
|
|
|
t := ctx.Info.TypeOf(expr.Args[0])
|
|
|
|
if t != nil {
|
|
|
|
return t.String(), fn.Sel.Name, nil
|
|
|
|
}
|
|
|
|
return "undefined", fn.Sel.Name, fmt.Errorf("missing type info")
|
|
|
|
}
|
|
|
|
if call.Obj != nil {
|
|
|
|
switch decl := call.Obj.Decl.(type) {
|
|
|
|
case *ast.FuncDecl:
|
|
|
|
ret := decl.Type.Results
|
|
|
|
if ret != nil && len(ret.List) > 0 {
|
|
|
|
ret1 := ret.List[0]
|
|
|
|
if ret1 != nil {
|
|
|
|
t := ctx.Info.TypeOf(ret1.Type)
|
|
|
|
if t != nil {
|
|
|
|
return t.String(), fn.Sel.Name, nil
|
|
|
|
}
|
|
|
|
return "undefined", fn.Sel.Name, fmt.Errorf("missing type info")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-18 17:57:34 +00:00
|
|
|
}
|
2016-11-18 22:09:10 +00:00
|
|
|
case *ast.Ident:
|
|
|
|
return ctx.Pkg.Name(), fn.Name, nil
|
2016-11-18 17:57:34 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-28 13:11:00 +00:00
|
|
|
|
2016-11-18 17:57:34 +00:00
|
|
|
return "", "", fmt.Errorf("unable to determine call info")
|
|
|
|
}
|
2016-11-18 22:09:10 +00:00
|
|
|
|
2018-12-02 15:28:51 +00:00
|
|
|
// GetCallStringArgsValues returns the values of strings arguments if they can be resolved
|
2023-03-20 09:08:49 +00:00
|
|
|
func GetCallStringArgsValues(n ast.Node, _ *Context) []string {
|
2018-12-02 14:36:02 +00:00
|
|
|
values := []string{}
|
|
|
|
switch node := n.(type) {
|
|
|
|
case *ast.CallExpr:
|
|
|
|
for _, arg := range node.Args {
|
|
|
|
switch param := arg.(type) {
|
|
|
|
case *ast.BasicLit:
|
|
|
|
value, err := GetString(param)
|
|
|
|
if err == nil {
|
|
|
|
values = append(values, value)
|
|
|
|
}
|
|
|
|
case *ast.Ident:
|
2018-12-02 15:28:51 +00:00
|
|
|
values = append(values, GetIdentStringValues(param)...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return values
|
|
|
|
}
|
2018-12-02 14:36:02 +00:00
|
|
|
|
2018-12-02 15:28:51 +00:00
|
|
|
// GetIdentStringValues return the string values of an Ident if they can be resolved
|
|
|
|
func GetIdentStringValues(ident *ast.Ident) []string {
|
|
|
|
values := []string{}
|
|
|
|
obj := ident.Obj
|
|
|
|
if obj != nil {
|
|
|
|
switch decl := obj.Decl.(type) {
|
|
|
|
case *ast.ValueSpec:
|
|
|
|
for _, v := range decl.Values {
|
|
|
|
value, err := GetString(v)
|
|
|
|
if err == nil {
|
|
|
|
values = append(values, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *ast.AssignStmt:
|
|
|
|
for _, v := range decl.Rhs {
|
|
|
|
value, err := GetString(v)
|
|
|
|
if err == nil {
|
|
|
|
values = append(values, value)
|
2018-12-02 14:36:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return values
|
|
|
|
}
|
|
|
|
|
2020-05-25 14:42:43 +01:00
|
|
|
// GetBinaryExprOperands returns all operands of a binary expression by traversing
|
|
|
|
// the expression tree
|
|
|
|
func GetBinaryExprOperands(be *ast.BinaryExpr) []ast.Node {
|
|
|
|
var traverse func(be *ast.BinaryExpr)
|
|
|
|
result := []ast.Node{}
|
|
|
|
traverse = func(be *ast.BinaryExpr) {
|
|
|
|
if lhs, ok := be.X.(*ast.BinaryExpr); ok {
|
|
|
|
traverse(lhs)
|
|
|
|
} else {
|
|
|
|
result = append(result, be.X)
|
|
|
|
}
|
|
|
|
if rhs, ok := be.Y.(*ast.BinaryExpr); ok {
|
|
|
|
traverse(rhs)
|
|
|
|
} else {
|
|
|
|
result = append(result, be.Y)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
traverse(be)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
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 <github@gone.nl>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-10-17 09:59:18 +01:00
|
|
|
// GetImportedNames returns the name(s)/alias(es) used for the package within
|
|
|
|
// the code. It ignores initialization-only imports.
|
|
|
|
func GetImportedNames(path string, ctx *Context) (names []string, found bool) {
|
|
|
|
importNames, imported := ctx.Imports.Imported[path]
|
|
|
|
return importNames, imported
|
2016-11-18 22:09:10 +00:00
|
|
|
}
|
|
|
|
|
2018-10-11 13:45:31 +01:00
|
|
|
// GetImportPath resolves the full import path of an identifier based on
|
2022-07-28 07:51:30 +01:00
|
|
|
// the imports in the current context(including aliases).
|
2016-11-18 22:09:10 +00:00
|
|
|
func GetImportPath(name string, ctx *Context) (string, bool) {
|
2017-07-19 22:17:00 +01:00
|
|
|
for path := range ctx.Imports.Imported {
|
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 <github@gone.nl>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-10-17 09:59:18 +01:00
|
|
|
if imported, ok := GetImportedNames(path, ctx); ok {
|
|
|
|
for _, n := range imported {
|
|
|
|
if n == name {
|
|
|
|
return path, true
|
|
|
|
}
|
|
|
|
}
|
2022-07-28 07:51:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-18 22:09:10 +00:00
|
|
|
return "", false
|
|
|
|
}
|
2016-12-02 18:20:23 +00:00
|
|
|
|
|
|
|
// GetLocation returns the filename and line number of an ast.Node
|
|
|
|
func GetLocation(n ast.Node, ctx *Context) (string, int) {
|
|
|
|
fobj := ctx.FileSet.File(n.Pos())
|
|
|
|
return fobj.Name(), fobj.Line(n.Pos())
|
|
|
|
}
|
2018-07-23 14:16:47 +01:00
|
|
|
|
|
|
|
// Gopath returns all GOPATHs
|
|
|
|
func Gopath() []string {
|
|
|
|
defaultGoPath := runtime.GOROOT()
|
|
|
|
if u, err := user.Current(); err == nil {
|
|
|
|
defaultGoPath = filepath.Join(u.HomeDir, "go")
|
|
|
|
}
|
|
|
|
path := Getenv("GOPATH", defaultGoPath)
|
|
|
|
paths := strings.Split(path, string(os.PathListSeparator))
|
|
|
|
for idx, path := range paths {
|
|
|
|
if abs, err := filepath.Abs(path); err == nil {
|
|
|
|
paths[idx] = abs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return paths
|
|
|
|
}
|
|
|
|
|
|
|
|
// Getenv returns the values of the environment variable, otherwise
|
2021-05-31 09:44:12 +01:00
|
|
|
// returns the default if variable is not set
|
2018-07-23 14:16:47 +01:00
|
|
|
func Getenv(key, userDefault string) string {
|
|
|
|
if val := os.Getenv(key); val != "" {
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
return userDefault
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPkgRelativePath returns the Go relative relative path derived
|
|
|
|
// form the given path
|
|
|
|
func GetPkgRelativePath(path string) (string, error) {
|
|
|
|
abspath, err := filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
abspath = path
|
|
|
|
}
|
|
|
|
if strings.HasSuffix(abspath, ".go") {
|
|
|
|
abspath = filepath.Dir(abspath)
|
|
|
|
}
|
|
|
|
for _, base := range Gopath() {
|
|
|
|
projectRoot := filepath.FromSlash(fmt.Sprintf("%s/src/", base))
|
|
|
|
if strings.HasPrefix(abspath, projectRoot) {
|
|
|
|
return strings.TrimPrefix(abspath, projectRoot), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", errors.New("no project relative path found")
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPkgAbsPath returns the Go package absolute path derived from
|
|
|
|
// the given path
|
|
|
|
func GetPkgAbsPath(pkgPath string) (string, error) {
|
|
|
|
absPath, err := filepath.Abs(pkgPath)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
|
|
|
return "", errors.New("no project absolute path found")
|
|
|
|
}
|
|
|
|
return absPath, nil
|
|
|
|
}
|
2018-08-19 18:57:36 +01:00
|
|
|
|
2018-10-11 13:45:31 +01:00
|
|
|
// ConcatString recursively concatenates strings from a binary expression
|
2018-08-19 18:57:36 +01:00
|
|
|
func ConcatString(n *ast.BinaryExpr) (string, bool) {
|
|
|
|
var s string
|
|
|
|
// sub expressions are found in X object, Y object is always last BasicLit
|
|
|
|
if rightOperand, ok := n.Y.(*ast.BasicLit); ok {
|
|
|
|
if str, err := GetString(rightOperand); err == nil {
|
|
|
|
s = str + s
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
if leftOperand, ok := n.X.(*ast.BinaryExpr); ok {
|
|
|
|
if recursion, ok := ConcatString(leftOperand); ok {
|
|
|
|
s = recursion + s
|
|
|
|
}
|
|
|
|
} else if leftOperand, ok := n.X.(*ast.BasicLit); ok {
|
|
|
|
if str, err := GetString(leftOperand); err == nil {
|
|
|
|
s = str + s
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
return s, true
|
|
|
|
}
|
2018-08-28 05:34:07 +01:00
|
|
|
|
|
|
|
// FindVarIdentities returns array of all variable identities in a given binary expression
|
|
|
|
func FindVarIdentities(n *ast.BinaryExpr, c *Context) ([]*ast.Ident, bool) {
|
|
|
|
identities := []*ast.Ident{}
|
|
|
|
// sub expressions are found in X object, Y object is always the last term
|
|
|
|
if rightOperand, ok := n.Y.(*ast.Ident); ok {
|
|
|
|
obj := c.Info.ObjectOf(rightOperand)
|
|
|
|
if _, ok := obj.(*types.Var); ok && !TryResolve(rightOperand, c) {
|
|
|
|
identities = append(identities, rightOperand)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if leftOperand, ok := n.X.(*ast.BinaryExpr); ok {
|
|
|
|
if leftIdentities, ok := FindVarIdentities(leftOperand, c); ok {
|
|
|
|
identities = append(identities, leftIdentities...)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if leftOperand, ok := n.X.(*ast.Ident); ok {
|
|
|
|
obj := c.Info.ObjectOf(leftOperand)
|
|
|
|
if _, ok := obj.(*types.Var); ok && !TryResolve(leftOperand, c) {
|
|
|
|
identities = append(identities, leftOperand)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(identities) > 0 {
|
|
|
|
return identities, true
|
|
|
|
}
|
|
|
|
// if nil or error, return false
|
|
|
|
return nil, false
|
|
|
|
}
|
2019-04-25 11:47:13 +01:00
|
|
|
|
|
|
|
// PackagePaths returns a slice with all packages path at given root directory
|
2019-09-09 13:01:36 +01:00
|
|
|
func PackagePaths(root string, excludes []*regexp.Regexp) ([]string, error) {
|
2019-04-25 11:47:13 +01:00
|
|
|
if strings.HasSuffix(root, "...") {
|
|
|
|
root = root[0 : len(root)-3]
|
|
|
|
} else {
|
|
|
|
return []string{root}, nil
|
|
|
|
}
|
|
|
|
paths := map[string]bool{}
|
|
|
|
err := filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
|
|
|
if filepath.Ext(path) == ".go" {
|
|
|
|
path = filepath.Dir(path)
|
2021-08-04 16:31:16 +01:00
|
|
|
if isExcluded(filepath.ToSlash(path), excludes) {
|
2019-04-25 11:47:13 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
paths[path] = true
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return []string{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
result := []string{}
|
|
|
|
for path := range paths {
|
|
|
|
result = append(result, path)
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
2019-06-24 13:10:51 +01:00
|
|
|
|
2019-09-09 13:01:36 +01:00
|
|
|
// isExcluded checks if a string matches any of the exclusion regexps
|
|
|
|
func isExcluded(str string, excludes []*regexp.Regexp) bool {
|
|
|
|
if excludes == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, exclude := range excludes {
|
|
|
|
if exclude != nil && exclude.MatchString(str) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExcludedDirsRegExp builds the regexps for a list of excluded dirs provided as strings
|
|
|
|
func ExcludedDirsRegExp(excludedDirs []string) []*regexp.Regexp {
|
|
|
|
var exps []*regexp.Regexp
|
|
|
|
for _, excludedDir := range excludedDirs {
|
2021-08-04 16:31:16 +01:00
|
|
|
str := fmt.Sprintf(`([\\/])?%s([\\/])?`, strings.ReplaceAll(filepath.ToSlash(excludedDir), "/", `\/`))
|
2019-09-09 13:01:36 +01:00
|
|
|
r := regexp.MustCompile(str)
|
|
|
|
exps = append(exps, r)
|
|
|
|
}
|
|
|
|
return exps
|
|
|
|
}
|
|
|
|
|
2019-06-24 13:10:51 +01:00
|
|
|
// RootPath returns the absolute root path of a scan
|
|
|
|
func RootPath(root string) (string, error) {
|
2021-06-17 13:56:27 +01:00
|
|
|
root = strings.TrimSuffix(root, "...")
|
2019-06-24 13:10:51 +01:00
|
|
|
return filepath.Abs(root)
|
|
|
|
}
|
2022-06-02 23:19:51 +01:00
|
|
|
|
|
|
|
// GoVersion returns parsed version of Go from runtime
|
|
|
|
func GoVersion() (int, int, int) {
|
2022-08-08 08:28:41 +01:00
|
|
|
return parseGoVersion(runtime.Version())
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseGoVersion parses Go version.
|
|
|
|
// example:
|
|
|
|
// - go1.19rc2
|
|
|
|
// - go1.19beta2
|
|
|
|
// - go1.19.4
|
|
|
|
// - go1.19
|
|
|
|
func parseGoVersion(version string) (int, int, int) {
|
|
|
|
exp := regexp.MustCompile(`go(\d+).(\d+)(?:.(\d+))?.*`)
|
|
|
|
parts := exp.FindStringSubmatch(version)
|
|
|
|
if len(parts) <= 1 {
|
|
|
|
return 0, 0, 0
|
|
|
|
}
|
|
|
|
|
|
|
|
major, _ := strconv.Atoi(parts[1])
|
|
|
|
minor, _ := strconv.Atoi(parts[2])
|
|
|
|
build, _ := strconv.Atoi(parts[3])
|
|
|
|
|
2022-06-02 23:19:51 +01:00
|
|
|
return major, minor, build
|
|
|
|
}
|