From 7593def620325942648e74ce3dfc1c23031dbd84 Mon Sep 17 00:00:00 2001 From: Ed Minnix Date: Tue, 25 Feb 2025 23:15:22 -0500 Subject: [PATCH 1/2] Model gorqlite package --- .../2025-02-25-go-database-rqlite-sources.md | 4 + .../ext/github.com.rqlite.gorqlite.model.yml | 19 ++ go/ql/lib/semmle/go/frameworks/Gorqlite.qll | 35 +++ .../local/database/test_rqlite_gorqlite.go | 80 +++++++ .../vendor/github.com/rqlite/gorqlite/stub.go | 205 ++++++++++++++++++ 5 files changed, 343 insertions(+) create mode 100644 go/ql/lib/change-notes/2025-02-25-go-database-rqlite-sources.md create mode 100644 go/ql/lib/semmle/go/frameworks/Gorqlite.qll create mode 100644 go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_rqlite_gorqlite.go create mode 100644 go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/github.com/rqlite/gorqlite/stub.go diff --git a/go/ql/lib/change-notes/2025-02-25-go-database-rqlite-sources.md b/go/ql/lib/change-notes/2025-02-25-go-database-rqlite-sources.md new file mode 100644 index 000000000000..d767137a00ff --- /dev/null +++ b/go/ql/lib/change-notes/2025-02-25-go-database-rqlite-sources.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added `database` source models for the `github.com/rqlite/gorqlite` package. diff --git a/go/ql/lib/ext/github.com.rqlite.gorqlite.model.yml b/go/ql/lib/ext/github.com.rqlite.gorqlite.model.yml index 62e24f2c920b..e65050f8aa24 100644 --- a/go/ql/lib/ext/github.com.rqlite.gorqlite.model.yml +++ b/go/ql/lib/ext/github.com.rqlite.gorqlite.model.yml @@ -3,8 +3,21 @@ extensions: pack: codeql/go-all extensible: packageGrouping data: + - ["gorqlite", "github.com/kanikanema/gorqlite"] - ["gorqlite", "github.com/rqlite/gorqlite"] - ["gorqlite", "github.com/raindog308/gorqlite"] + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["group:gorqlite", "Connection", True, "Query", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorqlite", "Connection", True, "QueryContext", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorqlite", "Connection", True, "QueryOne", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorqlite", "Connection", True, "QueryOneContext", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorqlite", "Connection", True, "QueryOneParameterized", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorqlite", "Connection", True, "QueryOneParameterizedContext", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorqlite", "Connection", True, "QueryParameterized", "", "", "ReturnValue[0]", "database", "manual"] + - ["group:gorqlite", "Connection", True, "QueryParameterizedContext", "", "", "ReturnValue[0]", "database", "manual"] - addsTo: pack: codeql/go-all extensible: sinkModel @@ -33,3 +46,9 @@ extensions: - ["group:gorqlite", "Connection", True, "WriteOneParameterizedContext", "", "", "Argument[1]", "sql-injection", "manual"] - ["group:gorqlite", "Connection", True, "WriteParameterized", "", "", "Argument[0]", "sql-injection", "manual"] - ["group:gorqlite", "Connection", True, "WriteParameterizedContext", "", "", "Argument[1]", "sql-injection", "manual"] + - addsTo: + pack: codeql/go-all + extensible: summaryModel + data: + - ["group:gorqlite", "QueryResult", True, "Map", "", "", "Argument[receiver]", "ReturnValue[0]", "taint", "manual"] + - ["group:gorqlite", "QueryResult", True, "Slice", "", "", "Argument[receiver]", "ReturnValue[0]", "taint", "manual"] diff --git a/go/ql/lib/semmle/go/frameworks/Gorqlite.qll b/go/ql/lib/semmle/go/frameworks/Gorqlite.qll new file mode 100644 index 000000000000..65ac5d88baba --- /dev/null +++ b/go/ql/lib/semmle/go/frameworks/Gorqlite.qll @@ -0,0 +1,35 @@ +/** + * Provides classes modeling security-relevant aspects of the `gorqlite` package. + */ + +import go + +/** + * Provides classes modeling security-relevant aspects of the `gorqlite` package. + */ +module Gorqlite { + private string packagePath() { + result = + package([ + "github.com/rqlite/gorqlite", "github.com/raindog308/gorqlite", + "github.com/kanikanema/gorqlite" + ], "") + } + + // These are expressed using TaintTracking::FunctionModel because varargs functions don't work with Models-as-Data summaries yet. + private class QueryResultScan extends TaintTracking::FunctionModel, Method { + FunctionInput inp; + FunctionOutput outp; + + QueryResultScan() { + // signature: func (qr *QueryResult) Scan(dest ...interface{}) error + this.hasQualifiedName(packagePath(), "QueryResult", "Scan") and + inp.isReceiver() and + outp.isParameter(_) + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input = inp and output = outp + } + } +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_rqlite_gorqlite.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_rqlite_gorqlite.go new file mode 100644 index 000000000000..da86395d4ea9 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_rqlite_gorqlite.go @@ -0,0 +1,80 @@ +package test + +//go:generate depstubber -vendor github.com/rqlite/gorqlite Connection,QueryResult + +import ( + "context" + + "github.com/rqlite/gorqlite" +) + +func parameterize(query string) gorqlite.ParameterizedStatement { + return gorqlite.ParameterizedStatement{ + Query: query, + Arguments: []interface{}{}, + } +} + +func test_rqlite_gorqlite(conn *gorqlite.Connection, ctx context.Context, query []string) { + v1, err := conn.Query(query) // $ source + if err != nil { + return + } + + sink(v1) // $ hasTaintFlow="v1" + + v2, err := conn.QueryContext(ctx, query) // $ source + if err != nil { + return + } + + sink(v2) // $ hasTaintFlow="v2" + + v3, err := conn.QueryOne(query[0]) // $ source + if err != nil { + return + } + + r3, err := v3.Slice() + if err != nil { + return + } + + sink(r3) // $ hasTaintFlow="r3" + + v4, err := conn.QueryOneContext(ctx, query[0]) // $ source + if err != nil { + return + } + + var r41, r42, r43 string + v4.Scan(&r41, &r42, &r43) + + v5, err := conn.QueryOneParameterized(parameterize(query[0])) // $ source + if err != nil { + return + } + + sink(v5) // $ hasTaintFlow="v5" + + v6, err := conn.QueryOneParameterizedContext(ctx, parameterize(query[0])) // $ source + if err != nil { + return + } + + sink(v6) // $ hasTaintFlow="v6" + + v7, err := conn.QueryParameterized([]gorqlite.ParameterizedStatement{parameterize(query[0])}) // $ source + if err != nil { + return + } + + sink(v7) // $ hasTaintFlow="v7" + + v8, err := conn.QueryParameterizedContext(ctx, []gorqlite.ParameterizedStatement{parameterize(query[0])}) // $ source + if err != nil { + return + } + + sink(v8) // $ hasTaintFlow="v8" +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/github.com/rqlite/gorqlite/stub.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/github.com/rqlite/gorqlite/stub.go new file mode 100644 index 000000000000..a6633a8f9ee8 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/vendor/github.com/rqlite/gorqlite/stub.go @@ -0,0 +1,205 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/rqlite/gorqlite, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/rqlite/gorqlite (exports: Connection,QueryResult; functions: ) + +// Package gorqlite is a stub of github.com/rqlite/gorqlite, generated by depstubber. +package gorqlite + +import ( + context "context" +) + +type Connection struct { + ID string +} + +func (_ *Connection) Close() {} + +func (_ *Connection) ConsistencyLevel() (string, error) { + return "", nil +} + +func (_ *Connection) Leader() (string, error) { + return "", nil +} + +func (_ *Connection) Peers() ([]string, error) { + return nil, nil +} + +func (_ *Connection) Query(_ []string) ([]QueryResult, error) { + return nil, nil +} + +func (_ *Connection) QueryContext(_ context.Context, _ []string) ([]QueryResult, error) { + return nil, nil +} + +func (_ *Connection) QueryOne(_ string) (QueryResult, error) { + return QueryResult{}, nil +} + +func (_ *Connection) QueryOneContext(_ context.Context, _ string) (QueryResult, error) { + return QueryResult{}, nil +} + +func (_ *Connection) QueryOneParameterized(_ ParameterizedStatement) (QueryResult, error) { + return QueryResult{}, nil +} + +func (_ *Connection) QueryOneParameterizedContext(_ context.Context, _ ParameterizedStatement) (QueryResult, error) { + return QueryResult{}, nil +} + +func (_ *Connection) QueryParameterized(_ []ParameterizedStatement) ([]QueryResult, error) { + return nil, nil +} + +func (_ *Connection) QueryParameterizedContext(_ context.Context, _ []ParameterizedStatement) ([]QueryResult, error) { + return nil, nil +} + +func (_ *Connection) Queue(_ []string) (int64, error) { + return 0, nil +} + +func (_ *Connection) QueueContext(_ context.Context, _ []string) (int64, error) { + return 0, nil +} + +func (_ *Connection) QueueOne(_ string) (int64, error) { + return 0, nil +} + +func (_ *Connection) QueueOneContext(_ context.Context, _ string) (int64, error) { + return 0, nil +} + +func (_ *Connection) QueueOneParameterized(_ ParameterizedStatement) (int64, error) { + return 0, nil +} + +func (_ *Connection) QueueOneParameterizedContext(_ context.Context, _ ParameterizedStatement) (int64, error) { + return 0, nil +} + +func (_ *Connection) QueueParameterized(_ []ParameterizedStatement) (int64, error) { + return 0, nil +} + +func (_ *Connection) QueueParameterizedContext(_ context.Context, _ []ParameterizedStatement) (int64, error) { + return 0, nil +} + +func (_ *Connection) Request(_ []string) ([]RequestResult, error) { + return nil, nil +} + +func (_ *Connection) RequestContext(_ context.Context, _ []string) ([]RequestResult, error) { + return nil, nil +} + +func (_ *Connection) RequestParameterized(_ []ParameterizedStatement) ([]RequestResult, error) { + return nil, nil +} + +func (_ *Connection) RequestParameterizedContext(_ context.Context, _ []ParameterizedStatement) ([]RequestResult, error) { + return nil, nil +} + +func (_ *Connection) SetConsistencyLevel(_ interface{}) error { + return nil +} + +func (_ *Connection) SetExecutionWithTransaction(_ bool) error { + return nil +} + +func (_ *Connection) Write(_ []string) ([]WriteResult, error) { + return nil, nil +} + +func (_ *Connection) WriteContext(_ context.Context, _ []string) ([]WriteResult, error) { + return nil, nil +} + +func (_ *Connection) WriteOne(_ string) (WriteResult, error) { + return WriteResult{}, nil +} + +func (_ *Connection) WriteOneContext(_ context.Context, _ string) (WriteResult, error) { + return WriteResult{}, nil +} + +func (_ *Connection) WriteOneParameterized(_ ParameterizedStatement) (WriteResult, error) { + return WriteResult{}, nil +} + +func (_ *Connection) WriteOneParameterizedContext(_ context.Context, _ ParameterizedStatement) (WriteResult, error) { + return WriteResult{}, nil +} + +func (_ *Connection) WriteParameterized(_ []ParameterizedStatement) ([]WriteResult, error) { + return nil, nil +} + +func (_ *Connection) WriteParameterizedContext(_ context.Context, _ []ParameterizedStatement) ([]WriteResult, error) { + return nil, nil +} + +type ParameterizedStatement struct { + Query string + Arguments []interface{} +} + +type QueryResult struct { + Err error + Timing float64 +} + +func (_ *QueryResult) Columns() []string { + return nil +} + +func (_ *QueryResult) Map() (map[string]interface{}, error) { + return nil, nil +} + +func (_ *QueryResult) Next() bool { + return false +} + +func (_ *QueryResult) NumRows() int64 { + return 0 +} + +func (_ *QueryResult) RowNumber() int64 { + return 0 +} + +func (_ *QueryResult) Scan(_ ...interface{}) error { + return nil +} + +func (_ *QueryResult) Slice() ([]interface{}, error) { + return nil, nil +} + +func (_ *QueryResult) Types() []string { + return nil +} + +type RequestResult struct { + Err error + Query *QueryResult + Write *WriteResult +} + +type WriteResult struct { + Err error + Timing float64 + RowsAffected int64 + LastInsertID int64 +} From 4f204ee40fbb1dd8dbd49b2a03ff3d12dc425d57 Mon Sep 17 00:00:00 2001 From: Ed Minnix Date: Wed, 26 Feb 2025 13:26:23 -0500 Subject: [PATCH 2/2] Add test for QueryResult::Map --- .../flowsources/local/database/test_rqlite_gorqlite.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_rqlite_gorqlite.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_rqlite_gorqlite.go index da86395d4ea9..4b383ff5d609 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_rqlite_gorqlite.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/database/test_rqlite_gorqlite.go @@ -55,7 +55,11 @@ func test_rqlite_gorqlite(conn *gorqlite.Connection, ctx context.Context, query return } - sink(v5) // $ hasTaintFlow="v5" + r5, err := v5.Map() + + r5Name := r5["name"] + + sink(r5Name) // $ hasTaintFlow="r5Name" v6, err := conn.QueryOneParameterizedContext(ctx, parameterize(query[0])) // $ source if err != nil {