Skip to content

Commit 09a28c4

Browse files
author
Stephan Brandauer
committed
base implementation of Spanner model on models-as-data
1 parent 132e0bf commit 09a28c4

File tree

5 files changed

+40
-155
lines changed

5 files changed

+40
-155
lines changed

javascript/ql/lib/semmle/javascript/Concepts.qll

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,6 @@ abstract class DatabaseAccess extends DataFlow::Node {
8989
DataFlow::Node getAResult() {
9090
none() // Overridden in subclass
9191
}
92-
93-
/**
94-
* Holds if the data returned can be a user-controlled object,
95-
* such as a JSON object parsed from user-controlled data.
96-
*/
97-
predicate returnsUserControlledObject() {
98-
// NB: Most data bases support JSON data (some via plugins),
99-
// which is why this has a default implementation.
100-
any()
101-
}
10292
}
10393

10494
/**

javascript/ql/lib/semmle/javascript/frameworks/SQL.qll

Lines changed: 16 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,10 @@ private module SpannerCsv {
574574
"@google-cloud/spanner;;@google-cloud/spanner;;Member[Spanner]",
575575
"@google-cloud/spanner;Database;@google-cloud/spanner;;ReturnValue.Member[instance].ReturnValue.Member[database].ReturnValue",
576576
"@google-cloud/spanner;v1.SpannerClient;@google-cloud/spanner;;Member[v1].Member[SpannerClient].Instance",
577-
"@google-cloud/spanner;Transaction;@google-cloud/spanner;Database;Member[runTransaction,runTransactionAsync].Argument[0..1].Parameter[1]",
577+
"@google-cloud/spanner;Transaction;@google-cloud/spanner;Database;Member[runTransaction,runTransactionAsync,getTransaction].Argument[0..1].Parameter[1]",
578+
"@google-cloud/spanner;Transaction;@google-cloud/spanner;Database;Member[getTransaction].ReturnValue.Awaited",
579+
"@google-cloud/spanner;Snapshot;@google-cloud/spanner;Database;Member[getSnapshot].Argument[0..1].Parameter[1]",
580+
"@google-cloud/spanner;Snapshot;@google-cloud/spanner;Database;Member[getSnapshot].ReturnValue.Awaited",
578581
"@google-cloud/spanner;BatchTransaction;@google-cloud/spanner;Database;Member[batchTransaction].ReturnValue",
579582
"@google-cloud/spanner;BatchTransaction;@google-cloud/spanner;Database;Member[createBatchTransaction].ReturnValue.Awaited",
580583
"@google-cloud/spanner;~SqlExecutorDirect;@google-cloud/spanner;Database;Member[run,runPartitionedUpdate,runStream]",
@@ -597,146 +600,22 @@ private module SpannerCsv {
597600
]
598601
}
599602
}
600-
}
601603

602-
/**
603-
* Provides classes modeling the Google Cloud Spanner library.
604-
*/
605-
private module Spanner {
606-
/**
607-
* Gets a node that refers to the `Spanner` class
608-
*/
609-
API::Node spanner() {
610-
// older versions
611-
result = API::moduleImport("@google-cloud/spanner")
612-
or
613-
// newer versions
614-
result = API::moduleImport("@google-cloud/spanner").getMember("Spanner")
615-
}
604+
class SpannerSources extends ModelInput::SourceModelCsv {
605+
string spannerClass() { result = ["v1.SpannerClient", "Database", "Transaction", "Snapshot",] }
616606

617-
/**
618-
* Gets a node that refers to an instance of the `Database` class.
619-
*/
620-
API::Node database() {
621-
result =
622-
spanner().getReturn().getMember("instance").getReturn().getMember("database").getReturn()
623-
or
624-
result = API::Node::ofType("@google-cloud/spanner", "Database")
625-
}
626-
627-
/**
628-
* Gets a node that refers to an instance of the `v1.SpannerClient` class.
629-
*/
630-
API::Node v1SpannerClient() {
631-
result = spanner().getMember("v1").getMember("SpannerClient").getInstance()
632-
or
633-
result = API::Node::ofType("@google-cloud/spanner", "v1.SpannerClient")
634-
}
635-
636-
/**
637-
* Gets a node that refers to a transaction object.
638-
*/
639-
API::Node transaction() {
640-
result =
641-
database()
642-
.getMember(["runTransaction", "runTransactionAsync"])
643-
.getParameter([0, 1])
644-
.getParameter(1)
645-
or
646-
result = API::Node::ofType("@google-cloud/spanner", "Transaction")
647-
}
648-
649-
/**
650-
* Gets a node that refers to a snapshot object.
651-
*/
652-
API::Node snapshot() {
653-
result = database().getMember("getSnapshot").getParameter([0, 1]).getParameter(1)
654-
or
655-
result = API::Node::ofType("@google-cloud/spanner", "Snapshot")
656-
}
657-
658-
/** Gets an API node referring to a `BatchTransaction` object. */
659-
API::Node batchTransaction() {
660-
result = database().getMember("batchTransaction").getReturn()
661-
or
662-
result = database().getMember("createBatchTransaction").getReturn().getPromised()
663-
or
664-
result = API::Node::ofType("@google-cloud/spanner", "BatchTransaction")
665-
}
666-
667-
/**
668-
* A call to a Spanner method that executes a SQL query.
669-
*/
670-
abstract class SqlExecution extends DatabaseAccess, DataFlow::InvokeNode { }
671-
672-
/**
673-
* A SQL execution that takes the input directly in the first argument or in the `sql` option.
674-
*/
675-
class SqlExecutionDirect extends SqlExecution {
676-
SqlExecutionDirect() {
677-
this = database().getMember(["run", "runPartitionedUpdate", "runStream"]).getACall()
678-
or
679-
this = transaction().getMember(["run", "runStream", "runUpdate"]).getACall()
680-
or
681-
this = batchTransaction().getMember("createQueryPartitions").getACall()
682-
or
683-
this = snapshot().getMember(["run", "runStream"]).getACall()
684-
}
685-
686-
override DataFlow::Node getAResult() {
687-
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
688-
or
689-
this = [database(), transaction(), snapshot()].getMember("run").getACall() and
690-
result = this.getCallback(_).getParameter(1)
691-
}
692-
693-
override DataFlow::Node getAQueryArgument() {
694-
result = this.getArgument(0)
695-
or
696-
result = this.getOptionArgument(0, "sql")
697-
}
698-
}
699-
700-
/**
701-
* A SQL execution that takes an array of SQL strings or { sql: string } objects.
702-
*/
703-
class SqlExecutionBatch extends SqlExecution, API::CallNode {
704-
SqlExecutionBatch() { this = transaction().getMember("batchUpdate").getACall() }
705-
706-
override DataFlow::Node getAResult() {
707-
none() // no results, batch update callbacks get only row counts.
708-
}
709-
710-
override DataFlow::Node getAQueryArgument() {
711-
// just use the whole array as the query argument, as arrays becomes tainted if one of the elements
712-
// are tainted
713-
result = this.getArgument(0)
714-
or
715-
result = this.getParameter(0).getUnknownMember().getMember("sql").getARhs()
716-
}
717-
}
718-
719-
/**
720-
* A SQL execution that only takes the input in the `sql` option, and do not accept query strings
721-
* directly.
722-
*/
723-
class SqlExecutionWithOption extends SqlExecution, DataFlow::CallNode {
724-
SqlExecutionWithOption() {
725-
this = v1SpannerClient().getMember(["executeSql", "executeStreamingSql"]).getACall()
607+
string resultPath() {
608+
result =
609+
[
610+
"Member[executeSql].Argument[0..].Parameter[1]",
611+
"Member[executeSql].ReturnValue.Awaited.Member[0]", "Member[run].ReturnValue.Awaited",
612+
"Member[run].Argument[0..].Parameter[1]",
613+
]
726614
}
727615

728-
override DataFlow::Node getAResult() {
729-
this = v1SpannerClient().getMember("executeSql").getACall() and
730-
result = this.getCallback(_).getParameter(1)
616+
override predicate row(string row) {
617+
row =
618+
"@google-cloud/spanner;" + spannerClass() + ";" + resultPath() + ";database-access-result"
731619
}
732-
733-
override DataFlow::Node getAQueryArgument() { result = this.getOptionArgument(0, "sql") }
734-
}
735-
736-
/**
737-
* An expression that is interpreted as a SQL string.
738-
*/
739-
class QueryString extends SQL::SqlString {
740-
QueryString() { this = any(SqlExecution se).getAQueryArgument().asExpr() }
741620
}
742621
}

javascript/ql/lib/semmle/javascript/frameworks/data/internal/Shared.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
* - Instance: the value returned by a constructor call
3232
* - Awaited: the value from a resolved promise/future-like object
3333
* - WithArity[n]: match a call with the given arity. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
34-
* - Other langauge-specific tokens mentioned in `ModelsAsData.qll`.
34+
* - Other language-specific tokens mentioned in `ModelsAsData.qll`.
3535
* 4. The `input` and `output` columns specify how data enters and leaves the element selected by the
3636
* first `(package, type, path)` tuple. Both strings are `.`-separated access paths
3737
* of the same syntax as the `path` column.

javascript/ql/lib/semmle/javascript/heuristics/AdditionalSources.qll

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,21 @@ class DatabaseAccessResultRemoteFlowSource extends HeuristicSource, RemoteFlowSo
6161

6262
override string getSourceType() { result = "Database query result" }
6363

64+
override predicate isUserControlledObject() { any() }
65+
}
66+
67+
/**
68+
* A remote flow source originating from a database access.
69+
*/
70+
private class RemoteFlowSourceFromDBAccess extends RemoteFlowSource, HeuristicSource {
71+
RemoteFlowSourceFromDBAccess() {
72+
this = ModelOutput::getASourceNode("database-access-result").getAUse()
73+
}
74+
75+
override string getSourceType() { result = "Database access" }
76+
6477
override predicate isUserControlledObject() {
65-
this.(DatabaseAccess).returnsUserControlledObject()
78+
// NB. supported databases all might return JSON.
79+
any()
6680
}
6781
}

javascript/ql/test/library-tests/Security/heuristics/sources.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
const pg = require('pg');
2-
const { Stream } = require('stream');
3-
const pool = new pg.Pool({});
4-
5-
function sink(data) {}
6-
71
(function() {
82
const password = '1234';
93
sink(password); // NOT OK
@@ -130,6 +124,13 @@ function sink(data) {}
130124
const db = new Spanner({projectId: 'test'})
131125
.instance('instanceid')
132126
.database('databaseid');
127+
128+
db.executeSql('SELECT * FROM users', {}, function (err, users) {
129+
sink(users); // NOT OK
130+
});
131+
132+
const [users] = (await db.executeSql('SELECT * FROM users', {}));
133+
sink(users); // NOT OK
133134

134135
const spannerpromise = db.run({
135136
sql: 'SELECT * FROM users'
@@ -152,6 +153,7 @@ function sink(data) {}
152153
txn.run('SELECT * FROM users', function (err, users) {
153154
sink(users); // NOT OK
154155
});
156+
txn.commit(function () {});
155157
});
156158

157159
db.getSnapshot(function (err, txn) {

0 commit comments

Comments
 (0)