mirror of
https://github.com/github/codeql.git
synced 2026-05-02 04:05:14 +02:00
Merge branch 'main' into js/shared-dataflow
This commit is contained in:
@@ -242,7 +242,9 @@ predicate isMultiLicenseBundle(TopLevel tl) {
|
||||
/**
|
||||
* Holds if this is a bundle with a "bundle" directive.
|
||||
*/
|
||||
predicate isDirectiveBundle(TopLevel tl) { exists(BundleDirective d | d.getTopLevel() = tl) }
|
||||
predicate isDirectiveBundle(TopLevel tl) {
|
||||
exists(Directive::BundleDirective d | d.getTopLevel() = tl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if toplevel `tl` contains code that looks like the output of a module bundler.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: typeModel
|
||||
data:
|
||||
# In Mongo version 2.x, a client and a database handle were the same concept, but in 3.x
|
||||
# they were separated. To handle everything with a single model, we treat them as the same here.
|
||||
- ['mongodb.Db', 'mongodb.MongoClient', '']
|
||||
# 'marsdb' has no typings and is archived.
|
||||
# We just model is as a variant of 'mongoose'.
|
||||
- ['mongoose.Model', 'marsdb', 'Member[Collection].Instance']
|
||||
- ['mongoose.Query', 'marsdb', 'Member[Collection].Instance']
|
||||
- ['mongoose.Query', 'mongoose.Query', 'Member[sortFunc].ReturnValue']
|
||||
@@ -21,14 +21,6 @@ module NoSql {
|
||||
* Provides classes modeling the `mongodb` and `mongoose` libraries.
|
||||
*/
|
||||
private module MongoDB {
|
||||
private class OldMongoDbAdapter extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// In Mongo version 2.x, a client and a database handle were the same concept, but in 3.x
|
||||
// they were separated. To handle everything with a single model, we treat them as the same here.
|
||||
row = "mongodb.Db;mongodb.MongoClient;"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that is interpreted as a MongoDB query.
|
||||
*/
|
||||
@@ -169,24 +161,6 @@ private module Mongoose {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes modeling the MarsDB library.
|
||||
*/
|
||||
private module MarsDB {
|
||||
// 'marsdb' has no typings and is archived.
|
||||
// We just model is as a variant of 'mongoose'.
|
||||
private class MongooseExtension extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"mongoose.Query;marsdb;Member[Collection].Instance",
|
||||
"mongoose.Model;marsdb;Member[Collection].Instance",
|
||||
"mongoose.Query;mongoose.Query;Member[sortFunc].ReturnValue",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes modeling the `Node Redis` library.
|
||||
*
|
||||
|
||||
19
javascript/ql/lib/semmle/javascript/frameworks/SQL.model.yml
Normal file
19
javascript/ql/lib/semmle/javascript/frameworks/SQL.model.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: sourceModel
|
||||
data:
|
||||
- ['@google-cloud/spanner.~SpannerObject', 'Member[executeSql].Argument[0..].Parameter[1]', 'database-access-result']
|
||||
- ['@google-cloud/spanner.~SpannerObject', 'Member[executeSql].ReturnValue.Awaited.Member[0]', 'database-access-result']
|
||||
- ['@google-cloud/spanner.~SpannerObject', 'Member[run].Argument[0..].Parameter[1]', 'database-access-result']
|
||||
- ['@google-cloud/spanner.~SpannerObject', 'Member[run].ReturnValue.Awaited', 'database-access-result']
|
||||
- ['sequelize.Sequelize', 'Member[query].ReturnValue.Awaited', 'database-access-result']
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: sinkModel
|
||||
data:
|
||||
- ['@google-cloud/spanner.Transaction', 'Member[batchUpdate].Argument[0]', 'sql-injection']
|
||||
- ['@google-cloud/spanner.Transaction', 'Member[batchUpdate].Argument[0].ArrayElement.Member[sql]', 'sql-injection']
|
||||
- ['@google-cloud/spanner.~SqlExecutorDirect', 'Argument[0]', 'sql-injection']
|
||||
- ['@google-cloud/spanner.~SqlExecutorDirect', 'Argument[0].Member[sql]', 'sql-injection']
|
||||
@@ -415,42 +415,3 @@ private module MsSql {
|
||||
override string getCredentialsKind() { result = kind }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes modeling the `sequelize` package.
|
||||
*/
|
||||
private module Sequelize {
|
||||
// Note: the sinks are specified directly in the MaD model
|
||||
class SequelizeSource extends ModelInput::SourceModelCsv {
|
||||
override predicate row(string row) {
|
||||
row = "sequelize.Sequelize;Member[query].ReturnValue.Awaited;database-access-result"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private module SpannerCsv {
|
||||
class SpannerSinks extends ModelInput::SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
// type; path; kind
|
||||
row =
|
||||
[
|
||||
"@google-cloud/spanner.~SqlExecutorDirect;Argument[0];sql-injection",
|
||||
"@google-cloud/spanner.~SqlExecutorDirect;Argument[0].Member[sql];sql-injection",
|
||||
"@google-cloud/spanner.Transaction;Member[batchUpdate].Argument[0];sql-injection",
|
||||
"@google-cloud/spanner.Transaction;Member[batchUpdate].Argument[0].ArrayElement.Member[sql];sql-injection",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class SpannerSources extends ModelInput::SourceModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"@google-cloud/spanner.~SpannerObject;Member[executeSql].Argument[0..].Parameter[1];database-access-result",
|
||||
"@google-cloud/spanner.~SpannerObject;Member[executeSql].ReturnValue.Awaited.Member[0];database-access-result",
|
||||
"@google-cloud/spanner.~SpannerObject;Member[run].ReturnValue.Awaited;database-access-result",
|
||||
"@google-cloud/spanner.~SpannerObject;Member[run].Argument[0..].Parameter[1];database-access-result",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import codeql.serverless.ServerLess
|
||||
private import codeql.serverless.ServerLess
|
||||
|
||||
private module YamlImpl implements Input {
|
||||
import semmle.javascript.Files
|
||||
|
||||
@@ -36,8 +36,6 @@ module Templating {
|
||||
|
||||
/** A placeholder tag for a templating engine. */
|
||||
class TemplatePlaceholderTag extends @template_placeholder_tag, Locatable {
|
||||
override Location getLocation() { hasLocation(this, result) }
|
||||
|
||||
override string toString() { template_placeholder_tag_info(this, _, result) }
|
||||
|
||||
/** Gets the full text of the template tag, including delimiters. */
|
||||
@@ -107,7 +105,12 @@ module Templating {
|
||||
* Gets the innermost JavaScript expression containing this template tag, if any.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
Expr getEnclosingExpr() { expr_contains_template_tag_location(result, this.getLocation()) }
|
||||
Expr getEnclosingExpr() {
|
||||
exists(@location loc |
|
||||
hasLocation(this, loc) and
|
||||
expr_contains_template_tag_location(result, loc)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
private import javascript
|
||||
private import internal.ApiGraphModels as Shared
|
||||
private import internal.ApiGraphModelsSpecific as Specific
|
||||
private import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
|
||||
import Shared::ModelInput as ModelInput
|
||||
import Shared::ModelOutput as ModelOutput
|
||||
|
||||
@@ -55,3 +56,110 @@ private class TaintStepFromSummary extends TaintTracking::SharedTaintStep {
|
||||
summaryStepNodes(pred, succ, "taint")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies which parts of the API graph to export in `ModelExport`.
|
||||
*/
|
||||
signature module ModelExportSig {
|
||||
/**
|
||||
* Holds if the exported model should contain `node`, if it is publicly accessible.
|
||||
*
|
||||
* This ensures that all ways to access `node` will be exported in type models.
|
||||
*/
|
||||
predicate shouldContain(API::Node node);
|
||||
|
||||
/**
|
||||
* Holds if `node` must be named if it is part of the exported graph.
|
||||
*/
|
||||
default predicate mustBeNamed(API::Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the exported model should preserve all paths leading to an instance of `type`,
|
||||
* including partial ones. It does not need to be closed transitively, `ModelExport` will
|
||||
* extend this to include type models from which `type` can be derived.
|
||||
*/
|
||||
default predicate shouldContainType(string type) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Module for exporting type models for a given set of nodes in the API graph.
|
||||
*/
|
||||
module ModelExport<ModelExportSig S> {
|
||||
private import codeql.mad.dynamic.GraphExport
|
||||
private import internal.ApiGraphModelsExport
|
||||
|
||||
private module GraphExportConfig implements GraphExportSig<Location, API::Node> {
|
||||
predicate edge = Specific::apiGraphHasEdge/3;
|
||||
|
||||
predicate shouldContain = S::shouldContain/1;
|
||||
|
||||
predicate shouldNotContain(API::Node node) {
|
||||
EndpointNaming::isPrivateLike(node)
|
||||
or
|
||||
node instanceof API::Use
|
||||
}
|
||||
|
||||
predicate mustBeNamed(API::Node node) {
|
||||
node.getAValueReachingSink() instanceof DataFlow::ClassNode
|
||||
or
|
||||
node = API::Internal::getClassInstance(_)
|
||||
or
|
||||
S::mustBeNamed(node)
|
||||
}
|
||||
|
||||
predicate exposedName(API::Node node, string type, string path) {
|
||||
exists(string moduleName |
|
||||
node = API::moduleExport(moduleName) and
|
||||
path = "" and
|
||||
type = "(" + moduleName + ")"
|
||||
)
|
||||
}
|
||||
|
||||
predicate suggestedName(API::Node node, string type) {
|
||||
exists(string package, string name |
|
||||
(
|
||||
EndpointNaming::sinkHasPrimaryName(node, package, name) and
|
||||
not EndpointNaming::aliasDefinition(_, _, _, _, node)
|
||||
or
|
||||
EndpointNaming::aliasDefinition(_, _, package, name, node)
|
||||
) and
|
||||
type = EndpointNaming::renderName(package, name)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[host]
|
||||
predicate hasTypeSummary(API::Node host, string path) {
|
||||
exists(string methodName |
|
||||
functionReturnsReceiver(host.getMember(methodName).getAValueReachingSink()) and
|
||||
path = "Member[" + methodName + "].ReturnValue"
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate functionReturnsReceiver(DataFlow::FunctionNode func) {
|
||||
getAReceiverRef(func).flowsTo(func.getReturnNode())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::MethodCallNode getAReceiverCall(DataFlow::FunctionNode func) {
|
||||
result = getAReceiverRef(func).getAMethodCall()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callReturnsReceiver(DataFlow::MethodCallNode call) {
|
||||
functionReturnsReceiver(call.getACallee().flow())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::SourceNode getAReceiverRef(DataFlow::FunctionNode func) {
|
||||
result = func.getReceiver()
|
||||
or
|
||||
result = getAReceiverCall(func) and
|
||||
callReturnsReceiver(result)
|
||||
}
|
||||
}
|
||||
|
||||
private module ExportedGraph = TypeGraphExport<GraphExportConfig, S::shouldContainType/1>;
|
||||
|
||||
import ExportedGraph
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/**
|
||||
* INTERNAL use only. This is an experimental API subject to change without notice.
|
||||
*
|
||||
* Provides classes and predicates for dealing with flow models specified in CSV format.
|
||||
* Provides classes and predicates for dealing with flow models specified in extensible predicates.
|
||||
*
|
||||
* The CSV specification has the following columns:
|
||||
* The extensible predicates have the following columns:
|
||||
* - Sources:
|
||||
* `type; path; kind`
|
||||
* `type, path, kind`
|
||||
* - Sinks:
|
||||
* `type; path; kind`
|
||||
* `type, path, kind`
|
||||
* - Summaries:
|
||||
* `type; path; input; output; kind`
|
||||
* `type, path, input, output, kind`
|
||||
* - Types:
|
||||
* `type1; type2; path`
|
||||
* `type1, type2, path`
|
||||
*
|
||||
* The interpretation of a row is similar to API-graphs with a left-to-right
|
||||
* reading.
|
||||
@@ -76,11 +76,13 @@ private import codeql.dataflow.internal.AccessPathSyntax
|
||||
/** Module containing hooks for providing input data to be interpreted as a model. */
|
||||
module ModelInput {
|
||||
/**
|
||||
* DEPRECATED: Use the extensible predicate `sourceModel` instead.
|
||||
*
|
||||
* A unit class for adding additional source model rows.
|
||||
*
|
||||
* Extend this class to add additional source definitions.
|
||||
*/
|
||||
class SourceModelCsv extends Unit {
|
||||
deprecated class SourceModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a source definition.
|
||||
*
|
||||
@@ -93,15 +95,17 @@ module ModelInput {
|
||||
*
|
||||
* The kind `remote` represents a general remote flow source.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
abstract deprecated predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional sink model rows.
|
||||
*
|
||||
* Extend this class to add additional sink definitions.
|
||||
*
|
||||
* DEPRECATED: Use the extensible predicate `sinkModel` instead.
|
||||
*/
|
||||
class SinkModelCsv extends Unit {
|
||||
deprecated class SinkModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a sink definition.
|
||||
*
|
||||
@@ -112,15 +116,17 @@ module ModelInput {
|
||||
* indicates that the value at `(type, path)` should be seen as a sink
|
||||
* of the given `kind`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
abstract deprecated predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional summary model rows.
|
||||
*
|
||||
* Extend this class to add additional flow summary definitions.
|
||||
*
|
||||
* DEPRECATED: Use the extensible predicate `summaryModel` instead.
|
||||
*/
|
||||
class SummaryModelCsv extends Unit {
|
||||
deprecated class SummaryModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a summary definition.
|
||||
*
|
||||
@@ -134,15 +140,18 @@ module ModelInput {
|
||||
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
|
||||
* respectively.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
abstract deprecated predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional type model rows.
|
||||
*
|
||||
* Extend this class to add additional type definitions.
|
||||
*
|
||||
* DEPRECATED: Use the extensible predicate `typeModel` or the class
|
||||
* `TypeModel` instead.
|
||||
*/
|
||||
class TypeModelCsv extends Unit {
|
||||
deprecated class TypeModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a type definition.
|
||||
*
|
||||
@@ -152,7 +161,7 @@ module ModelInput {
|
||||
* ```
|
||||
* indicates that `(type2, path)` should be seen as an instance of `type1`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
abstract deprecated predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,8 +195,10 @@ module ModelInput {
|
||||
|
||||
/**
|
||||
* A unit class for adding additional type variable model rows.
|
||||
*
|
||||
* DEPRECATED: Use the extensible predicate `typeVariableModel` instead.
|
||||
*/
|
||||
class TypeVariableModelCsv extends Unit {
|
||||
deprecated class TypeVariableModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a path through a type variable.
|
||||
*
|
||||
@@ -197,7 +208,7 @@ module ModelInput {
|
||||
* ```
|
||||
* means `path` can be substituted for a token `TypeVar[name]`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
abstract deprecated predicate row(string row);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,87 +227,141 @@ abstract class TestAllModels extends Unit { }
|
||||
* does not preserve empty trailing substrings.
|
||||
*/
|
||||
bindingset[result]
|
||||
private string inversePad(string s) { s = result + ";dummy" }
|
||||
deprecated private string inversePad(string s) { s = result + ";dummy" }
|
||||
|
||||
private predicate sourceModel(string row) { any(SourceModelCsv s).row(inversePad(row)) }
|
||||
deprecated private predicate sourceModel(string row) { any(SourceModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate sinkModel(string row) { any(SinkModelCsv s).row(inversePad(row)) }
|
||||
deprecated private predicate sinkModel(string row) { any(SinkModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate summaryModel(string row) { any(SummaryModelCsv s).row(inversePad(row)) }
|
||||
deprecated private predicate summaryModel(string row) {
|
||||
any(SummaryModelCsv s).row(inversePad(row))
|
||||
}
|
||||
|
||||
private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
|
||||
deprecated private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate typeVariableModel(string row) { any(TypeVariableModelCsv s).row(inversePad(row)) }
|
||||
deprecated private predicate typeVariableModel(string row) {
|
||||
any(TypeVariableModelCsv s).row(inversePad(row))
|
||||
}
|
||||
|
||||
private class DeprecationAdapter extends Unit {
|
||||
abstract predicate sourceModel(string type, string path, string kind);
|
||||
|
||||
abstract predicate sinkModel(string type, string path, string kind);
|
||||
|
||||
abstract predicate summaryModel(string type, string path, string input, string output, string kind);
|
||||
|
||||
abstract predicate typeModel(string type1, string type2, string path);
|
||||
|
||||
abstract predicate typeVariableModel(string name, string path);
|
||||
}
|
||||
|
||||
private class DeprecationAdapterImpl extends DeprecationAdapter {
|
||||
deprecated override predicate sourceModel(string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sourceModel(row) and
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = kind
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate sinkModel(string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sinkModel(row) and
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = kind
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate summaryModel(
|
||||
string type, string path, string input, string output, string kind
|
||||
) {
|
||||
exists(string row |
|
||||
summaryModel(row) and
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = input and
|
||||
row.splitAt(";", 3) = output and
|
||||
row.splitAt(";", 4) = kind
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate typeModel(string type1, string type2, string path) {
|
||||
exists(string row |
|
||||
typeModel(row) and
|
||||
row.splitAt(";", 0) = type1 and
|
||||
row.splitAt(";", 1) = type2 and
|
||||
row.splitAt(";", 2) = path
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate typeVariableModel(string name, string path) {
|
||||
exists(string row |
|
||||
typeVariableModel(row) and
|
||||
row.splitAt(";", 0) = name and
|
||||
row.splitAt(";", 1) = path
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if a source model exists for the given parameters. */
|
||||
predicate sourceModel(string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sourceModel(row) and
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = kind
|
||||
)
|
||||
predicate sourceModel(string type, string path, string kind, string model) {
|
||||
any(DeprecationAdapter a).sourceModel(type, path, kind) and
|
||||
model = "SourceModelCsv"
|
||||
or
|
||||
Extensions::sourceModel(type, path, kind)
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::sourceModel(type, path, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a sink model exists for the given parameters. */
|
||||
private predicate sinkModel(string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sinkModel(row) and
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = kind
|
||||
)
|
||||
private predicate sinkModel(string type, string path, string kind, string model) {
|
||||
any(DeprecationAdapter a).sinkModel(type, path, kind) and
|
||||
model = "SinkModelCsv"
|
||||
or
|
||||
Extensions::sinkModel(type, path, kind)
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::sinkModel(type, path, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a summary model `row` exists for the given parameters. */
|
||||
private predicate summaryModel(string type, string path, string input, string output, string kind) {
|
||||
exists(string row |
|
||||
summaryModel(row) and
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = input and
|
||||
row.splitAt(";", 3) = output and
|
||||
row.splitAt(";", 4) = kind
|
||||
)
|
||||
private predicate summaryModel(
|
||||
string type, string path, string input, string output, string kind, string model
|
||||
) {
|
||||
any(DeprecationAdapter a).summaryModel(type, path, input, output, kind) and
|
||||
model = "SummaryModelCsv"
|
||||
or
|
||||
Extensions::summaryModel(type, path, input, output, kind)
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::summaryModel(type, path, input, output, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a type model exists for the given parameters. */
|
||||
private predicate typeModel(string type1, string type2, string path) {
|
||||
exists(string row |
|
||||
typeModel(row) and
|
||||
row.splitAt(";", 0) = type1 and
|
||||
row.splitAt(";", 1) = type2 and
|
||||
row.splitAt(";", 2) = path
|
||||
)
|
||||
predicate typeModel(string type1, string type2, string path) {
|
||||
any(DeprecationAdapter a).typeModel(type1, type2, path)
|
||||
or
|
||||
Extensions::typeModel(type1, type2, path)
|
||||
}
|
||||
|
||||
/** Holds if a type variable model exists for the given parameters. */
|
||||
private predicate typeVariableModel(string name, string path) {
|
||||
exists(string row |
|
||||
typeVariableModel(row) and
|
||||
row.splitAt(";", 0) = name and
|
||||
row.splitAt(";", 1) = path
|
||||
)
|
||||
any(DeprecationAdapter a).typeVariableModel(name, path)
|
||||
or
|
||||
Extensions::typeVariableModel(name, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if CSV rows involving `type` might be relevant for the analysis of this database.
|
||||
* Holds if rows involving `type` might be relevant for the analysis of this database.
|
||||
*/
|
||||
predicate isRelevantType(string type) {
|
||||
(
|
||||
sourceModel(type, _, _) or
|
||||
sinkModel(type, _, _) or
|
||||
summaryModel(type, _, _, _, _) or
|
||||
sourceModel(type, _, _, _) or
|
||||
sinkModel(type, _, _, _) or
|
||||
summaryModel(type, _, _, _, _, _) or
|
||||
typeModel(_, type, _)
|
||||
) and
|
||||
(
|
||||
@@ -313,26 +378,26 @@ predicate isRelevantType(string type) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `type,path` is used in some CSV row.
|
||||
* Holds if `type,path` is used in some row.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate isRelevantFullPath(string type, string path) {
|
||||
isRelevantType(type) and
|
||||
(
|
||||
sourceModel(type, path, _) or
|
||||
sinkModel(type, path, _) or
|
||||
summaryModel(type, path, _, _, _) or
|
||||
sourceModel(type, path, _, _) or
|
||||
sinkModel(type, path, _, _) or
|
||||
summaryModel(type, path, _, _, _, _) or
|
||||
typeModel(_, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/** A string from a CSV row that should be parsed as an access path. */
|
||||
/** A string from a row that should be parsed as an access path. */
|
||||
private predicate accessPathRange(string s) {
|
||||
isRelevantFullPath(_, s)
|
||||
or
|
||||
exists(string type | isRelevantType(type) |
|
||||
summaryModel(type, _, s, _, _) or
|
||||
summaryModel(type, _, _, s, _)
|
||||
summaryModel(type, _, s, _, _, _) or
|
||||
summaryModel(type, _, _, s, _, _)
|
||||
)
|
||||
or
|
||||
typeVariableModel(_, s)
|
||||
@@ -435,7 +500,7 @@ private API::Node getNodeFromType(string type) {
|
||||
* Gets the API node identified by the first `n` tokens of `path` in the given `(type, path)` tuple.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromPath(string type, AccessPath path, int n) {
|
||||
API::Node getNodeFromPath(string type, AccessPath path, int n) {
|
||||
isRelevantFullPath(type, path) and
|
||||
(
|
||||
n = 0 and
|
||||
@@ -543,7 +608,7 @@ private API::Node getNodeFromPath(string type, AccessPath path) {
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate typeStepModel(string type, AccessPath basePath, AccessPath output) {
|
||||
summaryModel(type, basePath, "", output, "type")
|
||||
summaryModel(type, basePath, "", output, "type", _)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
@@ -618,36 +683,36 @@ module ModelOutput {
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* Holds if a CSV source model contributed `source` with the given `kind`.
|
||||
* Holds if a source model contributed `source` with the given `kind`.
|
||||
*/
|
||||
cached
|
||||
API::Node getASourceNode(string kind) {
|
||||
API::Node getASourceNode(string kind, string model) {
|
||||
exists(string type, string path |
|
||||
sourceModel(type, path, kind) and
|
||||
sourceModel(type, path, kind, model) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a CSV sink model contributed `sink` with the given `kind`.
|
||||
* Holds if a sink model contributed `sink` with the given `kind`.
|
||||
*/
|
||||
cached
|
||||
API::Node getASinkNode(string kind) {
|
||||
API::Node getASinkNode(string kind, string model) {
|
||||
exists(string type, string path |
|
||||
sinkModel(type, path, kind) and
|
||||
sinkModel(type, path, kind, model) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a relevant CSV summary exists for these parameters.
|
||||
* Holds if a relevant summary exists for these parameters.
|
||||
*/
|
||||
cached
|
||||
predicate relevantSummaryModel(
|
||||
string type, string path, string input, string output, string kind
|
||||
string type, string path, string input, string output, string kind, string model
|
||||
) {
|
||||
isRelevantType(type) and
|
||||
summaryModel(type, path, input, output, kind)
|
||||
summaryModel(type, path, input, output, kind, model)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -655,7 +720,7 @@ module ModelOutput {
|
||||
*/
|
||||
cached
|
||||
predicate resolvedSummaryBase(string type, string path, Specific::InvokeNode baseNode) {
|
||||
summaryModel(type, path, _, _, _) and
|
||||
summaryModel(type, path, _, _, _, _) and
|
||||
baseNode = getInvocationFromPath(type, path)
|
||||
}
|
||||
|
||||
@@ -664,13 +729,13 @@ module ModelOutput {
|
||||
*/
|
||||
cached
|
||||
predicate resolvedSummaryRefBase(string type, string path, API::Node baseNode) {
|
||||
summaryModel(type, path, _, _, _) and
|
||||
summaryModel(type, path, _, _, _, _) and
|
||||
baseNode = getNodeFromPath(type, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is seen as an instance of `type` due to a type definition
|
||||
* contributed by a CSV model.
|
||||
* contributed by a model.
|
||||
*/
|
||||
cached
|
||||
API::Node getATypeNode(string type) { result = getNodeFromType(type) }
|
||||
@@ -680,12 +745,22 @@ module ModelOutput {
|
||||
import Specific::ModelOutputSpecific
|
||||
private import codeql.mad.ModelValidation as SharedModelVal
|
||||
|
||||
/**
|
||||
* Holds if a CSV source model contributed `source` with the given `kind`.
|
||||
*/
|
||||
API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if a CSV sink model contributed `sink` with the given `kind`.
|
||||
*/
|
||||
API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) }
|
||||
|
||||
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
|
||||
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind) }
|
||||
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }
|
||||
|
||||
predicate sinkKind(string kind) { sinkModel(_, _, kind) }
|
||||
predicate sinkKind(string kind) { sinkModel(_, _, kind, _) }
|
||||
|
||||
predicate sourceKind(string kind) { sourceModel(_, _, kind) }
|
||||
predicate sourceKind(string kind) { sourceModel(_, _, kind, _) }
|
||||
}
|
||||
|
||||
private module KindVal = SharedModelVal::KindValidation<KindValConfig>;
|
||||
@@ -694,25 +769,6 @@ module ModelOutput {
|
||||
* Gets an error message relating to an invalid CSV row in a model.
|
||||
*/
|
||||
string getAWarning() {
|
||||
// Check number of columns
|
||||
exists(string row, string kind, int expectedArity, int actualArity |
|
||||
any(SourceModelCsv csv).row(row) and kind = "source" and expectedArity = 3
|
||||
or
|
||||
any(SinkModelCsv csv).row(row) and kind = "sink" and expectedArity = 3
|
||||
or
|
||||
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 5
|
||||
or
|
||||
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 3
|
||||
or
|
||||
any(TypeVariableModelCsv csv).row(row) and kind = "type-variable" and expectedArity = 2
|
||||
|
|
||||
actualArity = count(row.indexOf(";")) + 1 and
|
||||
actualArity != expectedArity and
|
||||
result =
|
||||
"CSV " + kind + " row should have " + expectedArity + " columns but has " + actualArity +
|
||||
": " + row
|
||||
)
|
||||
or
|
||||
// Check names and arguments of access path tokens
|
||||
exists(AccessPath path, AccessPathToken token |
|
||||
(isRelevantFullPath(_, path) or typeVariableModel(_, path)) and
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Contains an extension of `GraphExport` that relies on API graph specific functionality.
|
||||
*/
|
||||
|
||||
private import ApiGraphModels as Shared
|
||||
private import codeql.mad.dynamic.GraphExport
|
||||
private import ApiGraphModelsSpecific as Specific
|
||||
|
||||
private module API = Specific::API;
|
||||
|
||||
private import Shared
|
||||
|
||||
/**
|
||||
* Holds if some proper prefix of `(type, path)` evaluated to `node`, where `remainingPath`
|
||||
* is bound to the suffix of `path` that was not evaluated yet.
|
||||
*
|
||||
* See concrete examples in `TypeGraphExport`.
|
||||
*/
|
||||
bindingset[type, path]
|
||||
private predicate partiallyEvaluatedModel(
|
||||
string type, AccessPath path, API::Node node, string remainingPath
|
||||
) {
|
||||
exists(int n |
|
||||
getNodeFromPath(type, path, n) = node and
|
||||
n > 0 and
|
||||
// Note that `n < path.getNumToken()` is implied by the use of strictconcat()
|
||||
remainingPath =
|
||||
strictconcat(int k | k = [n .. path.getNumToken() - 1] | path.getToken(k), "." order by k)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `type` and all types leading to `type` should be re-exported.
|
||||
*/
|
||||
signature predicate shouldContainTypeSig(string type);
|
||||
|
||||
/**
|
||||
* Wrapper around `GraphExport` that also exports information about re-exported types.
|
||||
*
|
||||
* ### JavaScript example 1
|
||||
* For example, suppose `shouldContainType("foo")` holds, and the following is the entry point for a package `bar`:
|
||||
* ```js
|
||||
* // bar.js
|
||||
* module.exports.xxx = require('foo');
|
||||
* ```
|
||||
* then this would generate the following type model:
|
||||
* ```
|
||||
* foo; bar; Member[xxx]
|
||||
* ```
|
||||
*
|
||||
* ### JavaScript example 2
|
||||
* For a more complex case, suppose the following type model exists:
|
||||
* ```
|
||||
* foo.XYZ; foo; Member[x].Member[y].Member[z]
|
||||
* ```
|
||||
* And the package exports something that matches a prefix of the access path above:
|
||||
* ```js
|
||||
* module.exports.blah = require('foo').x.y;
|
||||
* ```
|
||||
* This would result in the following type model:
|
||||
* ```
|
||||
* foo.XYZ; bar; Member[blah].Member[z]
|
||||
* ```
|
||||
* Notice that the access path `Member[blah].Member[z]` consists of an access path generated from the API
|
||||
* graph, with pieces of the access path from the original type model appended to it.
|
||||
*/
|
||||
module TypeGraphExport<
|
||||
GraphExportSig<Specific::Location, API::Node> S, shouldContainTypeSig/1 shouldContainType>
|
||||
{
|
||||
/** Like `shouldContainType` but includes types that lead to `type` via type models. */
|
||||
private predicate shouldContainTypeEx(string type) {
|
||||
shouldContainType(type)
|
||||
or
|
||||
exists(string prevType |
|
||||
shouldContainType(prevType) and
|
||||
Shared::typeModel(prevType, type, _)
|
||||
)
|
||||
}
|
||||
|
||||
private module Config implements GraphExportSig<Specific::Location, API::Node> {
|
||||
import S
|
||||
|
||||
predicate shouldContain(API::Node node) {
|
||||
S::shouldContain(node)
|
||||
or
|
||||
exists(string type1 | shouldContainTypeEx(type1) |
|
||||
ModelOutput::getATypeNode(type1).getAValueReachableFromSource() = node.asSink()
|
||||
or
|
||||
exists(string type2, string path |
|
||||
Shared::typeModel(type1, type2, path) and
|
||||
getNodeFromPath(type2, path, _).getAValueReachableFromSource() = node.asSink()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module ExportedGraph = GraphExport<Specific::Location, API::Node, Config>;
|
||||
|
||||
import ExportedGraph
|
||||
|
||||
/**
|
||||
* Holds if `type1, type2, path` should be emitted as a type model, that is `(type2, path)` leads to an instance of `type1`.
|
||||
*/
|
||||
predicate typeModel(string type1, string type2, string path) {
|
||||
ExportedGraph::typeModel(type1, type2, path)
|
||||
or
|
||||
shouldContainTypeEx(type1) and
|
||||
exists(API::Node node |
|
||||
// A relevant type is exported directly
|
||||
Specific::sourceFlowsToSink(ModelOutput::getATypeNode(type1), node) and
|
||||
ExportedGraph::pathToNode(type2, path, node)
|
||||
or
|
||||
// Something that leads to a relevant type, but didn't finish its access path, is exported
|
||||
exists(string midType, string midPath, string remainingPath, string prefix, API::Node source |
|
||||
Shared::typeModel(type1, midType, midPath) and
|
||||
partiallyEvaluatedModel(midType, midPath, source, remainingPath) and
|
||||
Specific::sourceFlowsToSink(source, node) and
|
||||
ExportedGraph::pathToNode(type2, prefix, node) and
|
||||
path = join(prefix, remainingPath)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,15 @@
|
||||
*
|
||||
* The kind `remote` represents a general remote flow source.
|
||||
*/
|
||||
extensible predicate sourceModel(string type, string path, string kind);
|
||||
extensible predicate sourceModel(
|
||||
string type, string path, string kind, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if the value at `(type, path)` should be seen as a sink
|
||||
* of the given `kind`.
|
||||
*/
|
||||
extensible predicate sinkModel(string type, string path, string kind);
|
||||
extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId);
|
||||
|
||||
/**
|
||||
* Holds if in calls to `(type, path)`, the value referred to by `input`
|
||||
@@ -23,7 +25,9 @@ extensible predicate sinkModel(string type, string path, string kind);
|
||||
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
|
||||
* respectively.
|
||||
*/
|
||||
extensible predicate summaryModel(string type, string path, string input, string output, string kind);
|
||||
extensible predicate summaryModel(
|
||||
string type, string path, string input, string output, string kind, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if calls to `(type, path)` should be considered neutral. The meaning of this depends on the `kind`.
|
||||
|
||||
@@ -27,11 +27,18 @@ module API = JS::API;
|
||||
|
||||
import JS::DataFlow as DataFlow
|
||||
|
||||
class Location = JS::Location;
|
||||
|
||||
/**
|
||||
* Holds if `rawType` represents the JavaScript type `qualifiedName` from the given NPM `package`.
|
||||
*
|
||||
* Type names have form `package.type` or just `package` if referring to the package export
|
||||
* object. If `package` contains a `.` character it must be enclosed in single quotes, such as `'package'.type`.
|
||||
*
|
||||
* A type name of form `(package)` may also be used when refering to the package export object.
|
||||
* We allow this syntax as an alternative to the above, so models generated based on `EndpointNaming` look more consistent.
|
||||
* However, access paths are deliberately not parsed here, as we can not handle aliasing at this stage.
|
||||
* The model generator must explicitly generate the step between `(package)` and `(package).foo`, for example.
|
||||
*/
|
||||
bindingset[rawType]
|
||||
predicate parseTypeString(string rawType, string package, string qualifiedName) {
|
||||
@@ -40,6 +47,9 @@ predicate parseTypeString(string rawType, string package, string qualifiedName)
|
||||
package = rawType.regexpCapture(regexp, 1).regexpReplaceAll("^'|'$", "") and
|
||||
qualifiedName = rawType.regexpCapture(regexp, 2).regexpReplaceAll("^\\.", "")
|
||||
)
|
||||
or
|
||||
package = rawType.regexpCapture("[(]([^)]+)[)]", 1) and
|
||||
qualifiedName = ""
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,7 +269,7 @@ predicate invocationMatchesExtraCallSiteFilter(API::InvokeNode invoke, AccessPat
|
||||
pragma[nomagic]
|
||||
private predicate relevantInputOutputPath(API::InvokeNode base, AccessPath inputOrOutput) {
|
||||
exists(string type, string input, string output, string path |
|
||||
ModelOutput::relevantSummaryModel(type, path, input, output, _) and
|
||||
ModelOutput::relevantSummaryModel(type, path, input, output, _, _) and
|
||||
ModelOutput::resolvedSummaryBase(type, path, base) and
|
||||
inputOrOutput = [input, output]
|
||||
)
|
||||
@@ -291,7 +301,7 @@ private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPat
|
||||
*/
|
||||
predicate summaryStep(API::Node pred, API::Node succ, string kind) {
|
||||
exists(string type, string path, API::InvokeNode base, AccessPath input, AccessPath output |
|
||||
ModelOutput::relevantSummaryModel(type, path, input, output, kind) and
|
||||
ModelOutput::relevantSummaryModel(type, path, input, output, kind, _) and
|
||||
ModelOutput::resolvedSummaryBase(type, path, base) and
|
||||
pred = getNodeFromInputOutputPath(base, input) and
|
||||
succ = getNodeFromInputOutputPath(base, output)
|
||||
@@ -355,3 +365,54 @@ module ModelOutputSpecific {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the edge `pred -> succ` labelled with `path` exists in the API graph.
|
||||
*/
|
||||
bindingset[pred]
|
||||
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
|
||||
exists(string name | succ = pred.getMember(name) and path = "Member[" + name + "]")
|
||||
or
|
||||
succ = pred.getUnknownMember() and path = "AnyMember"
|
||||
or
|
||||
succ = pred.getInstance() and path = "Instance"
|
||||
or
|
||||
succ = pred.getReturn() and path = "ReturnValue"
|
||||
or
|
||||
exists(int n | succ = pred.getParameter(n) |
|
||||
if pred instanceof API::Use then path = "Argument[" + n + "]" else path = "Parameter[" + n + "]"
|
||||
)
|
||||
or
|
||||
succ = pred.getPromised() and path = "Awaited"
|
||||
or
|
||||
exists(DataFlow::ClassNode cls |
|
||||
pred = API::Internal::getClassInstance(cls.getADirectSubClass()) and
|
||||
succ = API::Internal::getClassInstance(cls) and
|
||||
path = ""
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the value of `source` is exposed at `sink`.
|
||||
*/
|
||||
bindingset[source]
|
||||
predicate sourceFlowsToSink(API::Node source, API::Node sink) {
|
||||
source.getAValueReachableFromSource() = sink.asSink()
|
||||
or
|
||||
// Handle the case of an upstream class being the base class of an exposed own class
|
||||
//
|
||||
// class Foo extends external.BaseClass {}
|
||||
//
|
||||
// Here we want to ensure that `Instance(Foo)` is seen as subtype of `Instance(external.BaseClass)`.
|
||||
//
|
||||
// Although we have a dedicated sink node for `Instance(Foo)` we don't have dedicate source node for `Instance(external.BaseClass)`.
|
||||
//
|
||||
// However, there is always an `Instance` edge from the base class expression (`external.BaseClass`)
|
||||
// to the receiver node in subclass constructor (the implicit constructor of `Foo`), which always exists.
|
||||
// So we use the constructor receiver as the representative for `Instance(external.BaseClass)`.
|
||||
// (This will get simplified when migrating to Ruby-style API graphs, as both sides will have explicit API nodes).
|
||||
exists(DataFlow::ClassNode cls |
|
||||
source.asSource() = cls.getConstructor().getReceiver() and
|
||||
sink = API::Internal::getClassInstance(cls)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
extensions:
|
||||
# Contribute empty data sets to avoid errors about an undefined extensionals
|
||||
# Make sure that the extensible model predicates have at least one definition
|
||||
# to avoid errors about undefined extensionals.
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: sourceModel
|
||||
Reference in New Issue
Block a user