1// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package rules
16
17import (
18	"go/ast"
19	"go/types"
20
21	"github.com/securego/gosec/v2"
22)
23
24type readfile struct {
25	gosec.MetaData
26	gosec.CallList
27	pathJoin gosec.CallList
28	clean    gosec.CallList
29}
30
31// ID returns the identifier for this rule
32func (r *readfile) ID() string {
33	return r.MetaData.ID
34}
35
36// isJoinFunc checks if there is a filepath.Join or other join function
37func (r *readfile) isJoinFunc(n ast.Node, c *gosec.Context) bool {
38	if call := r.pathJoin.ContainsPkgCallExpr(n, c, false); call != nil {
39		for _, arg := range call.Args {
40			// edge case: check if one of the args is a BinaryExpr
41			if binExp, ok := arg.(*ast.BinaryExpr); ok {
42				// iterate and resolve all found identities from the BinaryExpr
43				if _, ok := gosec.FindVarIdentities(binExp, c); ok {
44					return true
45				}
46			}
47
48			// try and resolve identity
49			if ident, ok := arg.(*ast.Ident); ok {
50				obj := c.Info.ObjectOf(ident)
51				if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) {
52					return true
53				}
54			}
55		}
56	}
57	return false
58}
59
60// isFilepathClean checks if there is a filepath.Clean before assigning to a variable
61func (r *readfile) isFilepathClean(n *ast.Ident, c *gosec.Context) bool {
62	if n.Obj.Kind != ast.Var {
63		return false
64	}
65	if node, ok := n.Obj.Decl.(*ast.AssignStmt); ok {
66		if call, ok := node.Rhs[0].(*ast.CallExpr); ok {
67			if clean := r.clean.ContainsPkgCallExpr(call, c, false); clean != nil {
68				return true
69			}
70		}
71	}
72	return false
73}
74
75// Match inspects AST nodes to determine if the match the methods `os.Open` or `ioutil.ReadFile`
76func (r *readfile) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
77	if node := r.ContainsPkgCallExpr(n, c, false); node != nil {
78		for _, arg := range node.Args {
79			// handles path joining functions in Arg
80			// eg. os.Open(filepath.Join("/tmp/", file))
81			if callExpr, ok := arg.(*ast.CallExpr); ok {
82				if r.isJoinFunc(callExpr, c) {
83					return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
84				}
85			}
86			// handles binary string concatenation eg. ioutil.Readfile("/tmp/" + file + "/blob")
87			if binExp, ok := arg.(*ast.BinaryExpr); ok {
88				// resolve all found identities from the BinaryExpr
89				if _, ok := gosec.FindVarIdentities(binExp, c); ok {
90					return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
91				}
92			}
93
94			if ident, ok := arg.(*ast.Ident); ok {
95				obj := c.Info.ObjectOf(ident)
96				if _, ok := obj.(*types.Var); ok &&
97					!gosec.TryResolve(ident, c) &&
98					!r.isFilepathClean(ident, c) {
99					return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
100				}
101			}
102		}
103	}
104	return nil, nil
105}
106
107// NewReadFile detects cases where we read files
108func NewReadFile(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
109	rule := &readfile{
110		pathJoin: gosec.NewCallList(),
111		clean:    gosec.NewCallList(),
112		CallList: gosec.NewCallList(),
113		MetaData: gosec.MetaData{
114			ID:         id,
115			What:       "Potential file inclusion via variable",
116			Severity:   gosec.Medium,
117			Confidence: gosec.High,
118		},
119	}
120	rule.pathJoin.Add("path/filepath", "Join")
121	rule.pathJoin.Add("path", "Join")
122	rule.clean.Add("path/filepath", "Clean")
123	rule.clean.Add("path/filepath", "Rel")
124	rule.Add("io/ioutil", "ReadFile")
125	rule.Add("os", "Open")
126	rule.Add("os", "OpenFile")
127	return rule, []ast.Node{(*ast.CallExpr)(nil)}
128}
129