diff --git a/go/ql/lib/change-notes/2025-01-05-gorm-database-sources.md b/go/ql/lib/change-notes/2025-01-05-gorm-database-sources.md new file mode 100644 index 000000000000..a8ae4792feb5 --- /dev/null +++ b/go/ql/lib/change-notes/2025-01-05-gorm-database-sources.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* Added `database` source models for database methods from the `gorm.io/gorm` package. +` diff --git a/go/ql/lib/ext/gorm.io.gorm.model.yml b/go/ql/lib/ext/gorm.io.gorm.model.yml index bfcf1fa66a76..7cea07e18ff8 100644 --- a/go/ql/lib/ext/gorm.io.gorm.model.yml +++ b/go/ql/lib/ext/gorm.io.gorm.model.yml @@ -6,6 +6,25 @@ extensions: - ["gorm", "gorm.io/gorm"] - ["gorm", "github.com/jinzhu/gorm"] - ["gorm", "github.com/go-gorm/gorm"] + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["group:gorm", "Association", True, "Find", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "ConnPool", True, "QueryContext", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorm", "ConnPool", True, "QueryRowContext", "", "", "ReturnValue", "database", "manual"] + - ["group:gorm", "DB", True, "Find", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "DB", True, "FindInBatches", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "DB", True, "First", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "DB", True, "FirstOrCreate", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "DB", True, "FirstOrInit", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "DB", True, "Last", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "DB", True, "Model", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "DB", True, "Pluck", "", "", "Argument[1]", "database", "manual"] + - ["group:gorm", "DB", True, "Row", "", "", "ReturnValue", "database", "manual"] + - ["group:gorm", "DB", True, "Rows", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorm", "DB", True, "Scan", "", "", "Argument[0]", "database", "manual"] + - ["group:gorm", "DB", True, "Take", "", "", "Argument[0]", "database", "manual"] - addsTo: pack: codeql/go-all extensible: sinkModel @@ -23,3 +42,8 @@ extensions: - ["group:gorm", "DB", True, "Exec", "", "", "Argument[0]", "sql-injection", "manual"] - ["group:gorm", "DB", True, "Distinct", "", "", "Argument[0]", "sql-injection", "manual"] - ["group:gorm", "DB", True, "Pluck", "", "", "Argument[0]", "sql-injection", "manual"] + - addsTo: + pack: codeql/go-all + extensible: summaryModel + data: + - ["group:gorm", "DB", True, "ScanRows", "", "", "Argument[0]", "Argument[1]", "taint", "manual"] diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/go.mod b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/go.mod new file mode 100644 index 000000000000..d9fb9e8f9870 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/go.mod @@ -0,0 +1,7 @@ +module test + +go 1.22.5 + +require ( + gorm.io/gorm v1.23.0 +) diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_gorm.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_gorm.go new file mode 100644 index 000000000000..9fc1de0de4ef --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_gorm.go @@ -0,0 +1,59 @@ +package test + +import "gorm.io/gorm" + +// test querying an Association +func test_gorm_AssociationQuery(association *gorm.Association) { + association.Find(&User{}) // $ source +} + +// test querying a ConnPool +func test_gorm_ConnPoolQuery(connPool gorm.ConnPool) { + rows, err := connPool.QueryContext(nil, "SELECT * FROM users") // $ source + + if err != nil { + return + } + + defer rows.Close() + + userRow := connPool.QueryRowContext(nil, "SELECT * FROM users WHERE id = 1") // $ source + + ignore(userRow) +} + +// test querying a DB +func test_gorm_db(db *gorm.DB) { + db.Find(&User{}) // $ source + + db.FindInBatches(&User{}, 10, nil) // $ source + + db.FirstOrCreate(&User{}) // $ source + + db.FirstOrInit(&User{}) // $ source + + db.First(&User{}) // $ source + + db.Last(&User{}) // $ source + + db.Take(&User{}) // $ source + + db.Scan(&User{}) // $ source + + var user User + db.Model(&user) // $ source + + row := db.Row() // $ source + ignore(row) + + rows, err := db.Rows() // $ source + ignore(err) + + var user2 User + db.ScanRows(rows, &user2) + + sink(user2) // $ hasTaintFlow="user2" + + var names []string + db.Pluck("name", &names) // $ source +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/user.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/user.go new file mode 100644 index 000000000000..f5a03dbe8b12 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/user.go @@ -0,0 +1,3 @@ +package test + +type User struct{} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/gorm.io/gorm/stub.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/gorm.io/gorm/stub.go new file mode 100644 index 000000000000..d17686106452 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/gorm.io/gorm/stub.go @@ -0,0 +1,77 @@ +package gorm + +import ( + "context" + "database/sql" +) + +type DB struct{} + +func (db *DB) Find(dest interface{}, conds ...interface{}) *DB { + return db +} + +func (db *DB) FindInBatches(dest interface{}, batchSize int, fc func(tx *DB, batch int) error) *DB { + return db +} + +func (db *DB) FirstOrCreate(dest interface{}, conds ...interface{}) *DB { + return db +} + +func (db *DB) FirstOrInit(dest interface{}, conds ...interface{}) *DB { + return db +} + +func (db *DB) First(dest interface{}, conds ...interface{}) *DB { + return db +} + +func (db *DB) Model(value interface{}) *DB { + return db +} + +func (db *DB) Last(dest interface{}, conds ...interface{}) *DB { + return db +} + +func (db *DB) Pluck(column string, dest interface{}) *DB { + return db +} + +func (db *DB) Take(dest interface{}, conds ...interface{}) *DB { + return db +} + +func (db *DB) Scan(dest interface{}) *DB { + return db +} + +func (db *DB) ScanRows(rows *sql.Rows, result interface{}) error { + return nil +} + +func (db *DB) Row() *sql.Row { + return nil +} + +func (db *DB) Rows() (*sql.Rows, error) { + return nil, nil +} + +type Association struct { + DB *DB +} + +func (a *Association) Find(dest interface{}) *Association { + return a +} + +type ConnPool interface { + PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) + QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) + QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row +} + +type Model interface{} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/modules.txt b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/modules.txt new file mode 100644 index 000000000000..ff139a75895e --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/modules.txt @@ -0,0 +1,3 @@ +# gorm.io/gorm v1.23.0 +## explicit +gorm.io/gorm \ No newline at end of file