mirror of
https://github.com/securego/gosec.git
synced 2024-12-27 21:15:52 +00:00
d4dc2d2df5
* Add G307 sample code. The sample should reflect a defered close that leads to data loss. Due to IDE auto-complete people tend at least log errors, but not really care about handling. * Add more G307 sample code. Propose a way to implement * Remove unused code. Add example that should not return an error but does * Remove test for synced closed file for now. Will add this later Co-authored-by: Cosmin Cojocar <cosmin.cojocar@gmx.ch>
105 lines
2.5 KiB
Go
105 lines
2.5 KiB
Go
package rules
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"strings"
|
|
|
|
"github.com/securego/gosec/v2"
|
|
)
|
|
|
|
type deferType struct {
|
|
typ string
|
|
methods []string
|
|
}
|
|
|
|
type badDefer struct {
|
|
gosec.MetaData
|
|
types []deferType
|
|
}
|
|
|
|
func (r *badDefer) ID() string {
|
|
return r.MetaData.ID
|
|
}
|
|
|
|
func normalize(typ string) string {
|
|
return strings.TrimPrefix(typ, "*")
|
|
}
|
|
|
|
func contains(methods []string, method string) bool {
|
|
for _, m := range methods {
|
|
if m == method {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r *badDefer) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
|
|
if deferStmt, ok := n.(*ast.DeferStmt); ok {
|
|
for _, deferTyp := range r.types {
|
|
if issue := r.checkChild(n, c, deferStmt.Call, deferTyp); issue != nil {
|
|
return issue, nil
|
|
}
|
|
if issue := r.checkFunction(n, c, deferStmt, deferTyp); issue != nil {
|
|
return issue, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *badDefer) checkChild(n ast.Node, c *gosec.Context, callExp *ast.CallExpr, deferTyp deferType) *gosec.Issue {
|
|
if typ, method, err := gosec.GetCallInfo(callExp, c); err == nil {
|
|
if normalize(typ) == deferTyp.typ && contains(deferTyp.methods, method) {
|
|
return gosec.NewIssue(c, n, r.ID(), fmt.Sprintf(r.What, method, typ), r.Severity, r.Confidence)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *badDefer) checkFunction(n ast.Node, c *gosec.Context, deferStmt *ast.DeferStmt, deferTyp deferType) *gosec.Issue {
|
|
if anonFunc, isAnonFunc := deferStmt.Call.Fun.(*ast.FuncLit); isAnonFunc {
|
|
for _, subElem := range anonFunc.Body.List {
|
|
if issue := r.checkStmt(n, c, subElem, deferTyp); issue != nil {
|
|
return issue
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *badDefer) checkStmt(n ast.Node, c *gosec.Context, subElem ast.Stmt, deferTyp deferType) *gosec.Issue {
|
|
switch stmt := subElem.(type) {
|
|
case *ast.AssignStmt:
|
|
for _, rh := range stmt.Rhs {
|
|
if e, isCallExp := rh.(*ast.CallExpr); isCallExp {
|
|
return r.checkChild(n, c, e, deferTyp)
|
|
}
|
|
}
|
|
case *ast.IfStmt:
|
|
if s, is := stmt.Init.(*ast.AssignStmt); is {
|
|
return r.checkStmt(n, c, s, deferTyp)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewDeferredClosing detects unsafe defer of error returning methods
|
|
func NewDeferredClosing(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
|
|
return &badDefer{
|
|
types: []deferType{
|
|
{
|
|
typ: "os.File",
|
|
methods: []string{"Close"},
|
|
},
|
|
},
|
|
MetaData: gosec.MetaData{
|
|
ID: id,
|
|
Severity: gosec.Medium,
|
|
Confidence: gosec.High,
|
|
What: "Deferring unsafe method %q on type %q",
|
|
},
|
|
}, []ast.Node{(*ast.DeferStmt)(nil)}
|
|
}
|