@@ -2,6 +2,7 @@ package injection
22
33import (
44 "fmt"
5+ "go/types"
56 "strings"
67
78 "github.com/picatz/taint"
@@ -15,13 +16,17 @@ import (
1516// userControlledValues are the sources of user controlled values that
1617// can be tained and end up in a SQL query.
1718var userControlledValues = taint .NewSources (
18- // Function (and method) calls
19+ // Function (and method) calls that are user controlled
20+ // over the netork. These are all taken into account as
21+ // part of *net/http.Request, but are listed here for
22+ // demonstration purposes.
23+ //
1924 // "(net/url.Values).Get",
2025 // "(*net/url.URL).Query",
2126 // "(*net/url.URL).Redacted",
2227 // "(*net/url.URL).EscapedFragment",
2328 // "(*net/url.Userinfo).Username",
24- // "(*net/url.Userinfo).Passworde ",
29+ // "(*net/url.Userinfo).Password ",
2530 // "(*net/url.Userinfo).String",
2631 // "(*net/http.Request).FormFile",
2732 // "(*net/http.Request).FormValue",
@@ -39,7 +44,7 @@ var userControlledValues = taint.NewSources(
3944 //
4045 // TODO: add more, consider pointer variants and specific fields on types
4146 // TODO: consider support for protobuf defined *Request types...
42- // TODO: consider supprot for gRPC request metadata (HTTP2 headers)
47+ // TODO: consider support for gRPC request metadata (HTTP2 headers)
4348 // TODO: consider support for msgpack-rpc?
4449)
4550
@@ -68,6 +73,139 @@ var injectableSQLMethods = taint.NewSinks(
6873 "(*github.com/jinzhu/gorm.DB).Raw" ,
6974 "(*github.com/jinzhu/gorm.DB).Exec" ,
7075 "(*github.com/jinzhu/gorm.DB).Order" ,
76+ // GORM v2
77+ "(*gorm.io/gorm.DB).Where" ,
78+ "(*gorm.io/gorm.DB).Or" ,
79+ "(*gorm.io/gorm.DB).Not" ,
80+ "(*gorm.io/gorm.DB).Group" ,
81+ "(*gorm.io/gorm.DB).Having" ,
82+ "(*gorm.io/gorm.DB).Joins" ,
83+ "(*gorm.io/gorm.DB).Select" ,
84+ "(*gorm.io/gorm.DB).Distinct" ,
85+ "(*gorm.io/gorm.DB).Pluck" ,
86+ "(*gorm.io/gorm.DB).Raw" ,
87+ "(*gorm.io/gorm.DB).Exec" ,
88+ "(*gorm.io/gorm.DB).Order" ,
89+ // Alternative GORM v2 import path
90+ "(*github.com/go-gorm/gorm.DB).Where" ,
91+ "(*github.com/go-gorm/gorm.DB).Or" ,
92+ "(*github.com/go-gorm/gorm.DB).Not" ,
93+ "(*github.com/go-gorm/gorm.DB).Group" ,
94+ "(*github.com/go-gorm/gorm.DB).Having" ,
95+ "(*github.com/go-gorm/gorm.DB).Joins" ,
96+ "(*github.com/go-gorm/gorm.DB).Select" ,
97+ "(*github.com/go-gorm/gorm.DB).Distinct" ,
98+ "(*github.com/go-gorm/gorm.DB).Pluck" ,
99+ "(*github.com/go-gorm/gorm.DB).Raw" ,
100+ "(*github.com/go-gorm/gorm.DB).Exec" ,
101+ "(*github.com/go-gorm/gorm.DB).Order" ,
102+ // sqlx
103+ "(*github.com/jmoiron/sqlx.DB).Queryx" ,
104+ "(*github.com/jmoiron/sqlx.DB).QueryRowx" ,
105+ "(*github.com/jmoiron/sqlx.DB).Query" ,
106+ "(*github.com/jmoiron/sqlx.DB).QueryRow" ,
107+ "(*github.com/jmoiron/sqlx.DB).Select" ,
108+ "(*github.com/jmoiron/sqlx.DB).Get" ,
109+ "(*github.com/jmoiron/sqlx.DB).Exec" ,
110+ "(*github.com/jmoiron/sqlx.Tx).Queryx" ,
111+ "(*github.com/jmoiron/sqlx.Tx).QueryRowx" ,
112+ "(*github.com/jmoiron/sqlx.Tx).Query" ,
113+ "(*github.com/jmoiron/sqlx.Tx).QueryRow" ,
114+ "(*github.com/jmoiron/sqlx.Tx).Select" ,
115+ "(*github.com/jmoiron/sqlx.Tx).Get" ,
116+ "(*github.com/jmoiron/sqlx.Tx).Exec" ,
117+ // xorm
118+ "(*xorm.io/xorm.Engine).Query" ,
119+ "(*xorm.io/xorm.Engine).Exec" ,
120+ "(*xorm.io/xorm.Engine).QueryString" ,
121+ "(*xorm.io/xorm.Engine).QueryInterface" ,
122+ "(*xorm.io/xorm.Engine).SQL" ,
123+ "(*xorm.io/xorm.Engine).Where" ,
124+ "(*xorm.io/xorm.Engine).And" ,
125+ "(*xorm.io/xorm.Engine).Or" ,
126+ "(*xorm.io/xorm.Engine).Alias" ,
127+ "(*xorm.io/xorm.Engine).NotIn" ,
128+ "(*xorm.io/xorm.Engine).In" ,
129+ "(*xorm.io/xorm.Engine).Select" ,
130+ "(*xorm.io/xorm.Engine).SetExpr" ,
131+ "(*xorm.io/xorm.Engine).OrderBy" ,
132+ "(*xorm.io/xorm.Engine).Having" ,
133+ "(*xorm.io/xorm.Engine).GroupBy" ,
134+ "(*xorm.io/xorm.Engine).Join" ,
135+ "(*xorm.io/xorm.Session).Query" ,
136+ "(*xorm.io/xorm.Session).Exec" ,
137+ "(*xorm.io/xorm.Session).QueryString" ,
138+ "(*xorm.io/xorm.Session).QueryInterface" ,
139+ "(*xorm.io/xorm.Session).SQL" ,
140+ "(*xorm.io/xorm.Session).Where" ,
141+ "(*xorm.io/xorm.Session).And" ,
142+ "(*xorm.io/xorm.Session).Or" ,
143+ "(*xorm.io/xorm.Session).Alias" ,
144+ "(*xorm.io/xorm.Session).NotIn" ,
145+ "(*xorm.io/xorm.Session).In" ,
146+ "(*xorm.io/xorm.Session).Select" ,
147+ "(*xorm.io/xorm.Session).SetExpr" ,
148+ "(*xorm.io/xorm.Session).OrderBy" ,
149+ "(*xorm.io/xorm.Session).Having" ,
150+ "(*xorm.io/xorm.Session).GroupBy" ,
151+ "(*xorm.io/xorm.Session).Join" ,
152+ // Alternative xorm import path
153+ "(*github.com/go-xorm/xorm.Engine).Query" ,
154+ "(*github.com/go-xorm/xorm.Engine).Exec" ,
155+ "(*github.com/go-xorm/xorm.Engine).QueryString" ,
156+ "(*github.com/go-xorm/xorm.Engine).QueryInterface" ,
157+ "(*github.com/go-xorm/xorm.Engine).SQL" ,
158+ "(*github.com/go-xorm/xorm.Engine).Where" ,
159+ "(*github.com/go-xorm/xorm.Engine).And" ,
160+ "(*github.com/go-xorm/xorm.Engine).Or" ,
161+ "(*github.com/go-xorm/xorm.Engine).Alias" ,
162+ "(*github.com/go-xorm/xorm.Engine).NotIn" ,
163+ "(*github.com/go-xorm/xorm.Engine).In" ,
164+ "(*github.com/go-xorm/xorm.Engine).Select" ,
165+ "(*github.com/go-xorm/xorm.Engine).SetExpr" ,
166+ "(*github.com/go-xorm/xorm.Engine).OrderBy" ,
167+ "(*github.com/go-xorm/xorm.Engine).Having" ,
168+ "(*github.com/go-xorm/xorm.Engine).GroupBy" ,
169+ "(*github.com/go-xorm/xorm.Engine).Join" ,
170+ "(*github.com/go-xorm/xorm.Session).Query" ,
171+ "(*github.com/go-xorm/xorm.Session).Exec" ,
172+ "(*github.com/go-xorm/xorm.Session).QueryString" ,
173+ "(*github.com/go-xorm/xorm.Session).QueryInterface" ,
174+ "(*github.com/go-xorm/xorm.Session).SQL" ,
175+ "(*github.com/go-xorm/xorm.Session).Where" ,
176+ "(*github.com/go-xorm/xorm.Session).And" ,
177+ "(*github.com/go-xorm/xorm.Session).Or" ,
178+ "(*github.com/go-xorm/xorm.Session).Alias" ,
179+ "(*github.com/go-xorm/xorm.Session).NotIn" ,
180+ "(*github.com/go-xorm/xorm.Session).In" ,
181+ "(*github.com/go-xorm/xorm.Session).Select" ,
182+ "(*github.com/go-xorm/xorm.Session).SetExpr" ,
183+ "(*github.com/go-xorm/xorm.Session).OrderBy" ,
184+ "(*github.com/go-xorm/xorm.Session).Having" ,
185+ "(*github.com/go-xorm/xorm.Session).GroupBy" ,
186+ "(*github.com/go-xorm/xorm.Session).Join" ,
187+ // go-pg
188+ "(*github.com/go-pg/pg.DB).Query" ,
189+ "(*github.com/go-pg/pg.DB).QueryOne" ,
190+ "(*github.com/go-pg/pg.DB).Exec" ,
191+ "(*github.com/go-pg/pg.DB).ExecOne" ,
192+ "(*github.com/go-pg/pg.Tx).Query" ,
193+ "(*github.com/go-pg/pg.Tx).QueryOne" ,
194+ "(*github.com/go-pg/pg.Tx).Exec" ,
195+ "(*github.com/go-pg/pg.Tx).ExecOne" ,
196+ // rqlite
197+ "(*github.com/rqlite/gorqlite.Connection).Query" ,
198+ "(*github.com/rqlite/gorqlite.Connection).QueryOne" ,
199+ "(*github.com/rqlite/gorqlite.Connection).Write" ,
200+ "(*github.com/rqlite/gorqlite.Connection).WriteOne" ,
201+ "(*github.com/raindog308/gorqlite.Connection).Query" ,
202+ "(*github.com/raindog308/gorqlite.Connection).QueryOne" ,
203+ "(*github.com/raindog308/gorqlite.Connection).Write" ,
204+ "(*github.com/raindog308/gorqlite.Connection).WriteOne" ,
205+ // Squirrel
206+ "github.com/Masterminds/squirrel.Expr" ,
207+ "gopkg.in/Masterminds/squirrel.v1.Expr" ,
208+ "github.com/lann/squirrel.Expr" ,
71209 //
72210 // TODO: add more, consider (non-)pointer variants?
73211)
@@ -83,27 +221,50 @@ var Analyzer = &analysis.Analyzer{
83221
84222// imports returns true if the package imports any of the given packages.
85223func imports (pass * analysis.Pass , pkgs ... string ) bool {
86- var imported bool
87- for _ , imp := range pass .Pkg .Imports () {
224+ visited := make (map [* types.Package ]bool )
225+ var walk func (* types.Package ) bool
226+ walk = func (p * types.Package ) bool {
227+ if visited [p ] {
228+ return false
229+ }
230+ visited [p ] = true
88231 for _ , pkg := range pkgs {
89- if strings .HasSuffix (imp .Path (), pkg ) {
90- imported = true
91- break
232+ if strings .HasSuffix (p .Path (), pkg ) {
233+ return true
92234 }
93235 }
94- if imported {
95- break
236+ for _ , imp := range p .Imports () {
237+ if walk (imp ) {
238+ return true
239+ }
96240 }
241+ return false
97242 }
98- return imported
243+ return walk (pass .Pkg )
244+ }
245+
246+ var supportedSQLPackages = []string {
247+ "database/sql" ,
248+ "github.com/mattn/go-sqlite3" ,
249+ "github.com/jinzhu/gorm" ,
250+ "gorm.io/gorm" ,
251+ "github.com/go-gorm/gorm" ,
252+ "github.com/jmoiron/sqlx" ,
253+ "xorm.io/xorm" ,
254+ "github.com/go-xorm/xorm" ,
255+ "github.com/go-pg/pg" ,
256+ "github.com/rqlite/gorqlite" ,
257+ "github.com/raindog308/gorqlite" ,
258+ "github.com/Masterminds/squirrel" ,
259+ "gopkg.in/Masterminds/squirrel.v1" ,
260+ "github.com/lann/squirrel" ,
99261}
100262
101263func run (pass * analysis.Pass ) (interface {}, error ) {
102- // Require the database/sql or GORM v1 packages are imported in the
103- // program being analyzed before running the analysis.
104- //
105- // This prevents wasting time analyzing programs that don't use SQL.
106- if ! imports (pass , "database/sql" , "github.com/jinzhu/gorm" ) {
264+ // Require at least one supported SQL package to be imported before
265+ // running the analysis. This avoids wasting time analyzing programs
266+ // that do not use SQL.
267+ if ! imports (pass , supportedSQLPackages ... ) {
107268 return nil , nil
108269 }
109270
@@ -132,7 +293,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
132293 // Today, I believe the callgraphutil package is the most
133294 // accurate, but I'd love to be proven wrong.
134295
135- // Note: this actually panis for testcase b
296+ // Note: this actually panics for testcase b
136297 // ptares, err := pointer.Analyze(&pointer.Config{
137298 // Mains: []*ssa.Package{buildSSA.Pkg},
138299 // BuildCallGraph: true,
@@ -170,14 +331,28 @@ func run(pass *analysis.Pass) (interface{}, error) {
170331 // (first argument after context).
171332 queryEdge := result .Path [len (result .Path )- 1 ]
172333
173- // Get the query arguments, skipping the first element, pointer to the DB.
174- queryArgs := queryEdge .Site .Common ().Args [1 :]
334+ // Get the query arguments. If the sink is a method call, the
335+ // first argument is the receiver, which we skip.
336+ queryArgs := queryEdge .Site .Common ().Args
337+ if queryEdge .Site .Common ().Signature ().Recv () != nil {
338+ if len (queryArgs ) < 1 {
339+ continue
340+ }
341+ queryArgs = queryArgs [1 :]
342+ }
175343
176344 // Skip the context argument, if using a *Context query variant.
177- if strings .HasPrefix (queryEdge .Site .Value ().Call .Value .String (), "Context" ) {
345+ if strings .HasSuffix (queryEdge .Site .Value ().Call .Value .String (), "Context" ) {
346+ if len (queryArgs ) < 2 {
347+ continue
348+ }
178349 queryArgs = queryArgs [1 :]
179350 }
180351
352+ if len (queryArgs ) == 0 {
353+ continue
354+ }
355+
181356 // Get the query function parameter.
182357 query := queryArgs [0 ]
183358
0 commit comments