Skip to content

Commit 7525fe4

Browse files
authored
Rule for defering methods which return errors (#441)
1 parent a2ac0bf commit 7525fe4

File tree

6 files changed

+135
-2
lines changed

6 files changed

+135
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ directory you can supply `./...` as the input argument.
8383
- G304: File path provided as taint input
8484
- G305: File traversal when extracting zip archive
8585
- G306: Poor file permissions used when writing to a new file
86+
- G307: Deferring a method which returns an error
8687
- G401: Detect the usage of DES, RC4, MD5 or SHA1
8788
- G402: Look for bad TLS connection settings
8889
- G403: Ensure minimum RSA key length of 2048 bits

cmd/gosec/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func loadConfig(configFile string) (gosec.Config, error) {
153153
if err != nil {
154154
return nil, err
155155
}
156-
defer file.Close()
156+
defer file.Close() // #nosec G307
157157
if _, err := config.ReadFrom(file); err != nil {
158158
return nil, err
159159
}
@@ -201,7 +201,7 @@ func saveOutput(filename, format string, paths []string, issues []*gosec.Issue,
201201
if err != nil {
202202
return err
203203
}
204-
defer outfile.Close()
204+
defer outfile.Close() // #nosec G307
205205
err = output.CreateReport(outfile, format, rootPaths, issues, metrics, errors)
206206
if err != nil {
207207
return err

rules/bad_defer.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package rules
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
"strings"
7+
8+
"github.com/securego/gosec"
9+
)
10+
11+
type deferType struct {
12+
typ string
13+
methods []string
14+
}
15+
16+
type badDefer struct {
17+
gosec.MetaData
18+
types []deferType
19+
}
20+
21+
func (r *badDefer) ID() string {
22+
return r.MetaData.ID
23+
}
24+
25+
func normalize(typ string) string {
26+
return strings.TrimPrefix(typ, "*")
27+
}
28+
29+
func contains(methods []string, method string) bool {
30+
for _, m := range methods {
31+
if m == method {
32+
return true
33+
}
34+
}
35+
return false
36+
}
37+
38+
func (r *badDefer) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
39+
if deferStmt, ok := n.(*ast.DeferStmt); ok {
40+
for _, deferTyp := range r.types {
41+
if typ, method, err := gosec.GetCallInfo(deferStmt.Call, c); err == nil {
42+
if normalize(typ) == deferTyp.typ && contains(deferTyp.methods, method) {
43+
return gosec.NewIssue(c, n, r.ID(), fmt.Sprintf(r.What, typ, method), r.Severity, r.Confidence), nil
44+
}
45+
}
46+
}
47+
48+
}
49+
50+
return nil, nil
51+
}
52+
53+
// NewDeferredClosing detects unsafe defer of error returning methods
54+
func NewDeferredClosing(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
55+
return &badDefer{
56+
types: []deferType{
57+
{
58+
typ: "os.File",
59+
methods: []string{"Close"},
60+
},
61+
},
62+
MetaData: gosec.MetaData{
63+
ID: id,
64+
Severity: gosec.Medium,
65+
Confidence: gosec.High,
66+
What: "Deferring unsafe method %q on type %q",
67+
},
68+
}, []ast.Node{(*ast.DeferStmt)(nil)}
69+
}

rules/rulelist.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func Generate(filters ...RuleFilter) RuleList {
8282
{"G304", "File path provided as taint input", NewReadFile},
8383
{"G305", "File path traversal when extracting zip archive", NewArchive},
8484
{"G306", "Poor file permissions used when writing to a file", NewWritePerms},
85+
{"G307", "Unsafe defer call of a method returning an error", NewDeferredClosing},
8586

8687
// crypto
8788
{"G401", "Detect the usage of DES, RC4, MD5 or SHA1", NewUsesWeakCryptography},

rules/rules_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ var _ = Describe("gosec rules", func() {
131131
runner("G306", testutils.SampleCodeG306)
132132
})
133133

134+
It("should detect unsafe defer of os.Close", func() {
135+
runner("G307", testutils.SampleCodeG307)
136+
})
137+
134138
It("should detect weak crypto algorithms", func() {
135139
runner("G401", testutils.SampleCodeG401)
136140
})

testutils/source.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,57 @@ func main() {
14351435
14361436
d2 := []byte{115, 111, 109, 101, 10}
14371437
n2, err := f.Write(d2)
1438+
1439+
defer check(err)
1440+
fmt.Printf("wrote %d bytes\n", n2)
1441+
1442+
n3, err := f.WriteString("writes\n")
1443+
fmt.Printf("wrote %d bytes\n", n3)
1444+
1445+
f.Sync()
1446+
1447+
w := bufio.NewWriter(f)
1448+
n4, err := w.WriteString("buffered\n")
1449+
fmt.Printf("wrote %d bytes\n", n4)
1450+
1451+
w.Flush()
1452+
1453+
}`}, 1, gosec.NewConfig()}}
1454+
// SampleCodeG307 - Unsafe defer of os.Close
1455+
SampleCodeG307 = []CodeSample{
1456+
{[]string{`package main
1457+
1458+
import (
1459+
"bufio"
1460+
"fmt"
1461+
"io/ioutil"
1462+
"os"
1463+
)
1464+
1465+
func check(e error) {
1466+
if e != nil {
1467+
panic(e)
1468+
}
1469+
}
1470+
1471+
func main() {
1472+
1473+
d1 := []byte("hello\ngo\n")
1474+
err := ioutil.WriteFile("/tmp/dat1", d1, 0744)
1475+
check(err)
1476+
1477+
allowed := ioutil.WriteFile("/tmp/dat1", d1, 0600)
1478+
check(allowed)
1479+
1480+
f, err := os.Create("/tmp/dat2")
14381481
check(err)
1482+
1483+
defer f.Close()
1484+
1485+
d2 := []byte{115, 111, 109, 101, 10}
1486+
n2, err := f.Write(d2)
1487+
1488+
defer check(err)
14391489
fmt.Printf("wrote %d bytes\n", n2)
14401490
14411491
n3, err := f.WriteString("writes\n")
@@ -1462,13 +1512,21 @@ import (
14621512
"log"
14631513
"os"
14641514
)
1515+
14651516
func main() {
14661517
f, err := os.Open("file.txt")
14671518
if err != nil {
14681519
log.Fatal(err)
14691520
}
14701521
defer f.Close()
14711522
1523+
defer func() {
1524+
err := f.Close()
1525+
if err != nil {
1526+
log.Printf("error closing the file: %s", err)
1527+
}
1528+
}()
1529+
14721530
h := md5.New()
14731531
if _, err := io.Copy(h, f); err != nil {
14741532
log.Fatal(err)

0 commit comments

Comments
 (0)