diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/BarrierGuards.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/BarrierGuards.qll index 371fbce77a9c..b0a8518ef55e 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/BarrierGuards.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/BarrierGuards.qll @@ -36,6 +36,36 @@ module MakeBarrierGuard { } } +/** + * Provides access to barrier guards defined via models-as-data. + */ +module ExternalBarrierGuard { + private predicate guardCheck(DataFlow::Node g, Expr e, boolean branch, string kind) { + exists(API::CallNode call, API::Node parameter | + parameter = call.getAParameter() and + parameter = ModelOutput::getABarrierGuardNode(kind, branch) + | + g = call and + e = parameter.asSink().asExpr() + ) + } + + private class BarrierGuard extends DataFlow::Node { + BarrierGuard() { guardCheck(this, _, _, _) } + + predicate blocksExpr(boolean outcome, Expr e, string kind) { + guardCheck(this, e, outcome, kind) + } + } + + /** + * Gets a barrier guard node of the given `kind` defined via models-as-data. + */ + DataFlow::Node getAnExternalBarrierNode(string kind) { + result = MakeStateBarrierGuard::getABarrierNode(kind) + } +} + deprecated private module DeprecationWrapper { signature class LabeledBarrierGuardSig extends DataFlow::Node { /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll b/javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll index 35119a7d8b8f..6a70d6033a6f 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Credentials.qll @@ -29,7 +29,7 @@ module CredentialsExpr { private class CredentialsFromModel extends CredentialsNode { string kind; - CredentialsFromModel() { this = ModelOutput::getASinkNode("credentials-" + kind).asSink() } + CredentialsFromModel() { ModelOutput::sinkNode(this, "credentials-" + kind) } override string getCredentialsKind() { result = CredentialsExpr::normalizeKind(kind) } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll b/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll index aeae1afcac30..3888dede5533 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll @@ -13,7 +13,7 @@ module NoSql { } private class QueryFromModel extends Query { - QueryFromModel() { this = ModelOutput::getASinkNode("nosql-injection").asSink() } + QueryFromModel() { ModelOutput::sinkNode(this, "nosql-injection") } } } @@ -46,7 +46,7 @@ private module MongoDB { override DataFlow::Node getAQueryArgument() { result = [this.getAnArgument(), this.getOptionArgument(_, _)] and - result = ModelOutput::getASinkNode("mongodb.sink").asSink() + ModelOutput::sinkNode(result, "mongodb.sink") } override DataFlow::Node getAResult() { diff --git a/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll b/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll index 9d106251a211..e961a794b2b6 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/SQL.qll @@ -9,7 +9,7 @@ module SQL { abstract class SqlString extends DataFlow::Node { } private class SqlStringFromModel extends SqlString { - SqlStringFromModel() { this = ModelOutput::getASinkNode("sql-injection").asSink() } + SqlStringFromModel() { ModelOutput::sinkNode(this, "sql-injection") } } /** diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll index 5d65f901d22d..876191dcd296 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll @@ -30,7 +30,7 @@ import Shared::ModelOutput as ModelOutput * A remote flow source originating from a MaD source row. */ private class RemoteFlowSourceFromMaD extends RemoteFlowSource { - RemoteFlowSourceFromMaD() { this = ModelOutput::getASourceNode("remote").asSource() } + RemoteFlowSourceFromMaD() { ModelOutput::sourceNode(this, "remote") } override string getSourceType() { result = "Remote flow" } } @@ -39,9 +39,9 @@ private class RemoteFlowSourceFromMaD extends RemoteFlowSource { * A threat-model flow source originating from a data extension. */ private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Range { - ThreatModelSourceFromDataExtension() { this = ModelOutput::getASourceNode(_).asSource() } + ThreatModelSourceFromDataExtension() { ModelOutput::sourceNode(this, _) } - override string getThreatModel() { this = ModelOutput::getASourceNode(result).asSource() } + override string getThreatModel() { ModelOutput::sourceNode(this, result) } override string getSourceType() { result = "Source node (" + this.getThreatModel() + ") [from data-extension]" diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..a8ef2f4e985e 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll @@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model) ) } +/** Holds if a barrier model exists for the given parameters. */ +private predicate barrierModel(string type, string path, string kind, string model) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierModel(type, path, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + +/** Holds if a barrier guard model exists for the given parameters. */ +private predicate barrierGuardModel( + string type, string path, string branch, string kind, string model +) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierGuardModel(type, path, branch, 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, string model @@ -400,6 +420,8 @@ predicate isRelevantType(string type) { ( sourceModel(type, _, _, _) or sinkModel(type, _, _, _) or + barrierModel(type, _, _, _) or + barrierGuardModel(type, _, _, _, _) or summaryModel(type, _, _, _, _, _) or typeModel(_, type, _) ) and @@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) { ( sourceModel(type, path, _, _) or sinkModel(type, path, _, _) or + barrierModel(type, path, _, _) or + barrierGuardModel(type, path, _, _, _) or summaryModel(type, path, _, _, _, _) or typeModel(_, type, path) ) @@ -745,6 +769,32 @@ module ModelOutput { ) } + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierNode(string kind, string model) { + exists(string type, string path | + barrierModel(type, path, kind, model) and + result = getNodeFromPath(type, path) + ) + } + + /** + * Holds if a barrier model contributed `barrier` with the given `kind` for the given `branch`. + */ + cached + API::Node getABarrierGuardNode(string kind, boolean branch, string model) { + exists(string type, string path, string branch_str | + branch = true and branch_str = "true" + or + branch = false and branch_str = "false" + | + barrierGuardModel(type, path, branch_str, kind, model) and + result = getNodeFromPath(type, path) + ) + } + /** * Holds if a relevant summary exists for these parameters. */ @@ -787,15 +837,46 @@ module ModelOutput { private import codeql.mad.ModelValidation as SharedModelVal /** - * Holds if a CSV source model contributed `source` with the given `kind`. + * Holds if an external 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`. + * Holds if an external model contributed `sink` with the given `kind`. */ API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) } + /** + * Holds if an external model contributed `barrier` with the given `kind`. + */ + API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) } + + /** + * Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`. + */ + API::Node getABarrierGuardNode(string kind, boolean branch) { + result = getABarrierGuardNode(kind, branch, _) + } + + /** + * Holds if `node` is specified as a source with the given kind in an external model. + */ + predicate sourceNode(DataFlow::Node node, string kind) { node = getASourceNode(kind).asSource() } + + /** + * Holds if `node` is specified as a sink with the given kind in an external model. + */ + predicate sinkNode(DataFlow::Node node, string kind) { node = getASinkNode(kind).asSink() } + + /** + * Holds if `node` is specified as a barrier with the given kind in an external model. + */ + predicate barrierNode(DataFlow::Node node, string kind) { + node = getABarrierNode(kind).asSink() + or + node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind) + } + private module KindValConfig implements SharedModelVal::KindValidationConfigSig { predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsExtensions.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsExtensions.qll index 3f38c498f324..2a644aabb95d 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsExtensions.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsExtensions.qll @@ -20,6 +20,26 @@ extensible predicate sourceModel( */ extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId); +/** + * Holds if the value at `(type, path)` should be seen as a barrier + * of the given `kind` and `madId` is the data extension row number. + */ +extensible predicate barrierModel( + string type, string path, string kind, QlBuiltins::ExtensionId madId +); + +/** + * Holds if the value at `(type, path)` should be seen as a barrier guard + * of the given `kind` and `madId` is the data extension row number. + * `path` is assumed to lead to a parameter of a call (possibly `self`), and + * the call is guarding the parameter. + * `branch` is either `true` or `false`, indicating which branch of the guard + * is protecting the parameter. + */ +extensible predicate barrierGuardModel( + string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId +); + /** * Holds if in calls to `(type, path)`, the value referred to by `input` * can flow to the value referred to by `output` and `madId` is the data diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/empty.model.yml b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/empty.model.yml index 12f83f71e55b..6542a1194cab 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/empty.model.yml +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/empty.model.yml @@ -16,6 +16,16 @@ extensions: extensible: summaryModel data: [] + - addsTo: + pack: codeql/javascript-all + extensible: barrierModel + data: [] + + - addsTo: + pack: codeql/javascript-all + extensible: barrierGuardModel + data: [] + - addsTo: pack: codeql/javascript-all extensible: neutralModel diff --git a/javascript/ql/lib/semmle/javascript/security/CorsPermissiveConfigurationCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/CorsPermissiveConfigurationCustomizations.qll index aed0e26a6b3f..e67b9e0d38cb 100644 --- a/javascript/ql/lib/semmle/javascript/security/CorsPermissiveConfigurationCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/CorsPermissiveConfigurationCustomizations.qll @@ -66,7 +66,7 @@ module CorsPermissiveConfiguration { * The value of cors origin when initializing the application. */ class CorsOriginSink extends Sink, DataFlow::ValueNode { - CorsOriginSink() { this = ModelOutput::getASinkNode("cors-origin").asSink() } + CorsOriginSink() { ModelOutput::sinkNode(this, "cors-origin") } } /** diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll index 1b987ea2679e..a8d15d0e6989 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll @@ -268,6 +268,6 @@ module ClientSideUrlRedirect { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "url-redirection") } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll index 8fded55bc896..9a72ae4a2315 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll @@ -436,6 +436,6 @@ module CodeInjection { class JsonStringifySanitizer extends Sanitizer, JsonStringifyCall { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("code-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "code-injection") } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll index 9762c1142b99..6b17adcb773c 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll @@ -56,6 +56,6 @@ module CommandInjection { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("command-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "command-injection") } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll index 1440fb5539d7..b7639ccc3aad 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll @@ -419,6 +419,6 @@ module DomBasedXss { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("html-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "html-injection") } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll index 9f2060709059..25474297d09f 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll @@ -86,5 +86,5 @@ class JsonStringifySanitizer extends Sanitizer { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("log-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "log-injection") } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ReflectedXssCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ReflectedXssCustomizations.qll index 70b2685d90d4..82b6e99dc217 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ReflectedXssCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ReflectedXssCustomizations.qll @@ -145,6 +145,6 @@ module ReflectedXss { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("html-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "html-injection") } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll index ec46ef91c3c0..de2a1e3c6bf6 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll @@ -96,7 +96,7 @@ module RequestForgery { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("request-forgery").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "request-forgery") } override DataFlow::Node getARequest() { result = this } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectCustomizations.qll index 55815717e98c..18cfaf6f7420 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectCustomizations.qll @@ -64,6 +64,6 @@ module ServerSideUrlRedirect { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "url-redirection") } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index f863b86a3b57..541726843e6f 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -1121,6 +1121,6 @@ module TaintedPath { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("path-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "path-injection") } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll index 2e13e0ee7f9b..82f11ec80030 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll @@ -67,6 +67,6 @@ module UnsafeDeserialization { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "unsafe-deserialization") } } } diff --git a/javascript/ql/test/library-tests/frameworks/data/test.ql b/javascript/ql/test/library-tests/frameworks/data/test.ql index 6ba504e921fa..97f3c2d2d5a5 100644 --- a/javascript/ql/test/library-tests/frameworks/data/test.ql +++ b/javascript/ql/test/library-tests/frameworks/data/test.ql @@ -16,13 +16,13 @@ module TestConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source.(DataFlow::CallNode).getCalleeName() = "source" or - source = ModelOutput::getASourceNode("test-source").asSource() + ModelOutput::sourceNode(source, "test-source") } predicate isSink(DataFlow::Node sink) { sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument() or - sink = ModelOutput::getASinkNode("test-sink").asSink() + ModelOutput::sinkNode(sink, "test-sink") } } @@ -48,9 +48,7 @@ query predicate taintFlow(DataFlow::Node source, DataFlow::Node sink) { TestFlow::flow(source, sink) } -query predicate isSink(DataFlow::Node node, string kind) { - node = ModelOutput::getASinkNode(kind).asSink() -} +query predicate isSink(DataFlow::Node node, string kind) { ModelOutput::sinkNode(node, kind) } query predicate syntaxErrors(ApiGraphModels::AccessPath path) { path.hasSyntaxError() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll index ffecbcba57ac..2727ed6651d3 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll @@ -584,10 +584,6 @@ class GuardNode extends ControlFlowNode { /** * Holds if the guard `g` validates `node` upon evaluating to `branch`. - * - * The expression `e` is expected to be a syntactic part of the guard `g`. - * For example, the guard `g` might be a call `isSafe(x)` and the expression `e` - * the argument `x`. */ signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch); @@ -600,15 +596,65 @@ signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean br module BarrierGuard { /** Gets a node that is safely guarded by the given guard check. */ ExprNode getABarrierNode() { + result = ParameterizedBarrierGuard::getABarrierNode(_) + } + + private predicate extendedGuardChecks(GuardNode g, ControlFlowNode node, boolean branch, Unit u) { + guardChecks(g, node, branch) and + u = u + } +} + +bindingset[this] +private signature class ParamSig; + +private module WithParam { + signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch, P param); +} + +/** + * Provides a set of barrier nodes for a guard that validates a node. + * + * This is expected to be used in `isBarrier`/`isSanitizer` definitions + * in data flow and taint tracking. + */ +module ParameterizedBarrierGuard::guardChecksSig/4 guardChecks> { + /** Gets a node that is safely guarded by the given guard check with parameter `param`. */ + ExprNode getABarrierNode(P param) { exists(GuardNode g, EssaDefinition def, ControlFlowNode node, boolean branch | AdjacentUses::useOfDef(def, node) and - guardChecks(g, node, branch) and + guardChecks(g, node, branch, param) and AdjacentUses::useOfDef(def, result.asCfgNode()) and g.controlsBlock(result.asCfgNode().getBasicBlock(), branch) ) } } +/** + * Provides a set of barrier nodes for a guard that validates a node as described by an external predicate. + * + * This is expected to be used in `isBarrier`/`isSanitizer` definitions + * in data flow and taint tracking. + */ +module ExternalBarrierGuard { + private import semmle.python.ApiGraphs + + private predicate guardCheck(GuardNode g, ControlFlowNode node, boolean branch, string kind) { + exists(API::CallNode call, API::Node parameter | + parameter = call.getAParameter() and + parameter = ModelOutput::getABarrierGuardNode(kind, branch) + | + g = call.asCfgNode() and + node = parameter.asSink().asCfgNode() + ) + } + + /** Gets a node that is an external barrier of the given kind. */ + ExprNode getAnExternalBarrierNode(string kind) { + result = ParameterizedBarrierGuard::getABarrierNode(kind) + } +} + /** * Algebraic datatype for tracking data content associated with values. * Content can be collection elements or object attributes. diff --git a/python/ql/lib/semmle/python/frameworks/Django.model.yml b/python/ql/lib/semmle/python/frameworks/Django.model.yml new file mode 100644 index 000000000000..4e472af6c117 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Django.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/python-all + extensible: barrierGuardModel + data: + - ['django', 'Member[utils].Member[http].Member[url_has_allowed_host_and_scheme].Argument[0,url:]', "true", 'url-redirection'] diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index 4aa5776ad54b..ee0ed4a84dd0 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -2965,38 +2965,6 @@ module PrivateDjango { override predicate csrfEnabled() { decoratorName in ["csrf_protect", "requires_csrf_token"] } } - private predicate djangoUrlHasAllowedHostAndScheme( - DataFlow::GuardNode g, ControlFlowNode node, boolean branch - ) { - exists(API::CallNode call | - call = - API::moduleImport("django") - .getMember("utils") - .getMember("http") - .getMember("url_has_allowed_host_and_scheme") - .getACall() and - g = call.asCfgNode() and - node = call.getParameter(0, "url").asSink().asCfgNode() and - branch = true - ) - } - - /** - * A call to `django.utils.http.url_has_allowed_host_and_scheme`, considered as a sanitizer-guard for URL redirection. - * - * See https://docs.djangoproject.com/en/4.2/_modules/django/utils/http/ - */ - private class DjangoAllowedUrl extends UrlRedirect::Sanitizer { - DjangoAllowedUrl() { - this = DataFlow::BarrierGuard::getABarrierNode() - } - - override predicate sanitizes(UrlRedirect::FlowState state) { - // sanitize all flow states - any() - } - } - // --------------------------------------------------------------------------- // Templates // --------------------------------------------------------------------------- diff --git a/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll b/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll index 11c6b285f2aa..719b93592390 100644 --- a/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll +++ b/python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll @@ -24,9 +24,9 @@ private import semmle.python.Concepts * A threat-model flow source originating from a data extension. */ private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Range { - ThreatModelSourceFromDataExtension() { this = ModelOutput::getASourceNode(_).asSource() } + ThreatModelSourceFromDataExtension() { ModelOutput::sourceNode(this, _) } - override string getThreatModel() { this = ModelOutput::getASourceNode(result).asSource() } + override string getThreatModel() { ModelOutput::sourceNode(this, result) } override string getSourceType() { result = "Source node (" + this.getThreatModel() + ") [from data-extension]" diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..a8ef2f4e985e 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll @@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model) ) } +/** Holds if a barrier model exists for the given parameters. */ +private predicate barrierModel(string type, string path, string kind, string model) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierModel(type, path, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + +/** Holds if a barrier guard model exists for the given parameters. */ +private predicate barrierGuardModel( + string type, string path, string branch, string kind, string model +) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierGuardModel(type, path, branch, 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, string model @@ -400,6 +420,8 @@ predicate isRelevantType(string type) { ( sourceModel(type, _, _, _) or sinkModel(type, _, _, _) or + barrierModel(type, _, _, _) or + barrierGuardModel(type, _, _, _, _) or summaryModel(type, _, _, _, _, _) or typeModel(_, type, _) ) and @@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) { ( sourceModel(type, path, _, _) or sinkModel(type, path, _, _) or + barrierModel(type, path, _, _) or + barrierGuardModel(type, path, _, _, _) or summaryModel(type, path, _, _, _, _) or typeModel(_, type, path) ) @@ -745,6 +769,32 @@ module ModelOutput { ) } + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierNode(string kind, string model) { + exists(string type, string path | + barrierModel(type, path, kind, model) and + result = getNodeFromPath(type, path) + ) + } + + /** + * Holds if a barrier model contributed `barrier` with the given `kind` for the given `branch`. + */ + cached + API::Node getABarrierGuardNode(string kind, boolean branch, string model) { + exists(string type, string path, string branch_str | + branch = true and branch_str = "true" + or + branch = false and branch_str = "false" + | + barrierGuardModel(type, path, branch_str, kind, model) and + result = getNodeFromPath(type, path) + ) + } + /** * Holds if a relevant summary exists for these parameters. */ @@ -787,15 +837,46 @@ module ModelOutput { private import codeql.mad.ModelValidation as SharedModelVal /** - * Holds if a CSV source model contributed `source` with the given `kind`. + * Holds if an external 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`. + * Holds if an external model contributed `sink` with the given `kind`. */ API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) } + /** + * Holds if an external model contributed `barrier` with the given `kind`. + */ + API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) } + + /** + * Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`. + */ + API::Node getABarrierGuardNode(string kind, boolean branch) { + result = getABarrierGuardNode(kind, branch, _) + } + + /** + * Holds if `node` is specified as a source with the given kind in an external model. + */ + predicate sourceNode(DataFlow::Node node, string kind) { node = getASourceNode(kind).asSource() } + + /** + * Holds if `node` is specified as a sink with the given kind in an external model. + */ + predicate sinkNode(DataFlow::Node node, string kind) { node = getASinkNode(kind).asSink() } + + /** + * Holds if `node` is specified as a barrier with the given kind in an external model. + */ + predicate barrierNode(DataFlow::Node node, string kind) { + node = getABarrierNode(kind).asSink() + or + node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind) + } + private module KindValConfig implements SharedModelVal::KindValidationConfigSig { predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) } diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll index 3f38c498f324..2a644aabb95d 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll @@ -20,6 +20,26 @@ extensible predicate sourceModel( */ extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId); +/** + * Holds if the value at `(type, path)` should be seen as a barrier + * of the given `kind` and `madId` is the data extension row number. + */ +extensible predicate barrierModel( + string type, string path, string kind, QlBuiltins::ExtensionId madId +); + +/** + * Holds if the value at `(type, path)` should be seen as a barrier guard + * of the given `kind` and `madId` is the data extension row number. + * `path` is assumed to lead to a parameter of a call (possibly `self`), and + * the call is guarding the parameter. + * `branch` is either `true` or `false`, indicating which branch of the guard + * is protecting the parameter. + */ +extensible predicate barrierGuardModel( + string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId +); + /** * Holds if in calls to `(type, path)`, the value referred to by `input` * can flow to the value referred to by `output` and `madId` is the data diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml b/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml index ea9b9fce546b..a7529031c29f 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml +++ b/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml @@ -11,6 +11,16 @@ extensions: extensible: sinkModel data: [] + - addsTo: + pack: codeql/python-all + extensible: barrierModel + data: [] + + - addsTo: + pack: codeql/python-all + extensible: barrierGuardModel + data: [] + - addsTo: pack: codeql/python-all extensible: summaryModel diff --git a/python/ql/lib/semmle/python/security/dataflow/CodeInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/CodeInjectionCustomizations.qll index a7c2ad90a35a..5878245ed104 100644 --- a/python/ql/lib/semmle/python/security/dataflow/CodeInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/CodeInjectionCustomizations.qll @@ -50,7 +50,7 @@ module CodeInjection { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("code-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "code-injection") } } /** diff --git a/python/ql/lib/semmle/python/security/dataflow/CommandInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/CommandInjectionCustomizations.qll index 83f6ccff0a51..0bfd6494a1cd 100644 --- a/python/ql/lib/semmle/python/security/dataflow/CommandInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/CommandInjectionCustomizations.qll @@ -85,7 +85,7 @@ module CommandInjection { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("command-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "command-injection") } } /** diff --git a/python/ql/lib/semmle/python/security/dataflow/LogInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/LogInjectionCustomizations.qll index 40aa83e17443..59e52f0ab0a9 100644 --- a/python/ql/lib/semmle/python/security/dataflow/LogInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/LogInjectionCustomizations.qll @@ -78,7 +78,7 @@ module LogInjection { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("log-injection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "log-injection") } } /** diff --git a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll index 8b8e2f696739..e2399d49c0b8 100644 --- a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll @@ -88,7 +88,7 @@ module PathInjection { private import semmle.python.frameworks.data.ModelsAsData private class DataAsFileSink extends Sink { - DataAsFileSink() { this = ModelOutput::getASinkNode("path-injection").asSink() } + DataAsFileSink() { ModelOutput::sinkNode(this, "path-injection") } } /** diff --git a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll index 14db509df2f3..58e5adc86605 100644 --- a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll @@ -46,9 +46,7 @@ module ReflectedXss { * A data flow sink for "reflected cross-site scripting" vulnerabilities. */ private class SinkFromModel extends Sink { - SinkFromModel() { - this = ModelOutput::getASinkNode(["html-injection", "js-injection"]).asSink() - } + SinkFromModel() { ModelOutput::sinkNode(this, ["html-injection", "js-injection"]) } } /** diff --git a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll index b614eaeebec4..4118732e8dae 100644 --- a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll @@ -67,6 +67,6 @@ module SqlInjection { /** A sink for sql-injection from model data. */ private class DataAsSqlSink extends Sink { - DataAsSqlSink() { this = ModelOutput::getASinkNode("sql-injection").asSink() } + DataAsSqlSink() { ModelOutput::sinkNode(this, "sql-injection") } } } diff --git a/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationCustomizations.qll index d71d36279b50..074677ee1dc2 100644 --- a/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationCustomizations.qll @@ -55,7 +55,7 @@ module UnsafeDeserialization { } private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "unsafe-deserialization") } } /** diff --git a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll index f5810944f8d9..1f718e478a1e 100644 --- a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll @@ -7,6 +7,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts +private import semmle.python.ApiGraphs private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.BarrierGuards private import semmle.python.frameworks.data.ModelsAsData @@ -95,8 +96,11 @@ module UrlRedirect { } } + /** + * A sink for URL redirection defined via models-as-data. + */ private class SinkFromModel extends Sink { - SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() } + SinkFromModel() { ModelOutput::sinkNode(this, "url-redirection") } } /** @@ -156,4 +160,18 @@ module UrlRedirect { /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; + + /** + * A sanitizer defined via models-as-data with kind "url-redirection". + */ + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { + this = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode("url-redirection") + } + + override predicate sanitizes(FlowState state) { + // sanitize all flow states + any() + } + } } diff --git a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql index d08223a553bd..97b1f2fba1c9 100644 --- a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql +++ b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql @@ -83,7 +83,7 @@ class CredentialSink extends DataFlow::Node { CredentialSink() { exists(string s | s.matches("credentials-%") | // Actual sink-type will be things like `credentials-password` or `credentials-username` - this = ModelOutput::getASinkNode(s).asSink() + ModelOutput::sinkNode(this, s) ) or exists(string name | diff --git a/python/ql/test/experimental/meta/MaDTest.qll b/python/ql/test/experimental/meta/MaDTest.qll index 705785d796c7..10403eb67183 100644 --- a/python/ql/test/experimental/meta/MaDTest.qll +++ b/python/ql/test/experimental/meta/MaDTest.qll @@ -17,7 +17,7 @@ module MadSinkTest implements TestSig { predicate hasActualResult(Location location, string element, string tag, string value) { exists(location.getFile().getRelativePath()) and exists(DataFlow::Node sink, string kind | - sink = ModelOutput::getASinkNode(kind).asSink() and + ModelOutput::sinkNode(sink, kind) and location = sink.getLocation() and element = sink.toString() and value = prettyNodeForInlineTest(sink) and @@ -34,7 +34,7 @@ module MadSourceTest implements TestSig { predicate hasActualResult(Location location, string element, string tag, string value) { exists(location.getFile().getRelativePath()) and exists(DataFlow::Node source, string kind | - source = ModelOutput::getASourceNode(kind).asSource() and + ModelOutput::sourceNode(source, kind) and location = source.getLocation() and element = source.toString() and value = prettyNodeForInlineTest(source) and diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected index 0f334d45ea83..69bb8d30e8f4 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected @@ -75,7 +75,7 @@ edges | UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | provenance | | | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | provenance | | | UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | Config | -| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | MaD:69 | +| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | MaD:70 | | UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | UnsafeUnpack.py:166:37:166:42 | ControlFlowNode for member | provenance | | | UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | provenance | | | UnsafeUnpack.py:166:23:166:28 | [post] ControlFlowNode for result | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | provenance | | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected b/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected index 17c28aa1d95d..e32edeb702bb 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected @@ -1,23 +1,23 @@ edges | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:11:21:11:29 | ControlFlowNode for file_path | provenance | | | test.py:11:5:11:35 | ControlFlowNode for Attribute() | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:35 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config | | test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:12:21:12:29 | ControlFlowNode for file_path | provenance | | | test.py:12:5:12:35 | ControlFlowNode for Attribute() | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:35 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config | | test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:14:26:14:34 | ControlFlowNode for file_path | provenance | | | test.py:14:10:14:35 | ControlFlowNode for Attribute() | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:14:10:14:35 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:14:10:14:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config | | test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:18:26:18:34 | ControlFlowNode for file_path | provenance | | | test.py:18:10:18:35 | ControlFlowNode for Attribute() | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:18:10:18:35 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:18:10:18:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config | | test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:22:21:22:29 | ControlFlowNode for file_path | provenance | | | test.py:22:5:22:30 | ControlFlowNode for Attribute() | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:30 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:30 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config | | test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:24:18:24:26 | ControlFlowNode for file_path | provenance | | | test.py:24:18:24:26 | ControlFlowNode for file_path | test.py:24:5:24:52 | ControlFlowNode for Attribute() | provenance | Config | diff --git a/python/ql/test/library-tests/frameworks/data/test.ql b/python/ql/test/library-tests/frameworks/data/test.ql index 701c74f12464..c06365dc7689 100644 --- a/python/ql/test/library-tests/frameworks/data/test.ql +++ b/python/ql/test/library-tests/frameworks/data/test.ql @@ -6,11 +6,9 @@ import semmle.python.dataflow.new.DataFlow private import semmle.python.ApiGraphs module BasicTaintTrackingConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - source = ModelOutput::getASourceNode("test-source").asSource() - } + predicate isSource(DataFlow::Node source) { ModelOutput::sourceNode(source, "test-source") } - predicate isSink(DataFlow::Node sink) { sink = ModelOutput::getASinkNode("test-sink").asSink() } + predicate isSink(DataFlow::Node sink) { ModelOutput::sinkNode(sink, "test-sink") } } module TestTaintTrackingFlow = TaintTracking::Global; @@ -19,13 +17,9 @@ query predicate taintFlow(DataFlow::Node source, DataFlow::Node sink) { TestTaintTrackingFlow::flow(source, sink) } -query predicate isSink(DataFlow::Node node, string kind) { - node = ModelOutput::getASinkNode(kind).asSink() -} +query predicate isSink(DataFlow::Node node, string kind) { ModelOutput::sinkNode(node, kind) } -query predicate isSource(DataFlow::Node node, string kind) { - node = ModelOutput::getASourceNode(kind).asSource() -} +query predicate isSource(DataFlow::Node node, string kind) { ModelOutput::sourceNode(node, kind) } query predicate syntaxErrors(ApiGraphModels::AccessPath path) { path.hasSyntaxError() } diff --git a/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected b/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected index 1e4ba8b95305..d59e639d641b 100644 --- a/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected @@ -1,5 +1,5 @@ edges -| test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | provenance | Src:MaD:17 | +| test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | provenance | Src:MaD:18 | nodes | test.py:6:14:6:21 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | test.py:6:14:6:24 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected index cea505fe39db..6c5f8363c487 100644 --- a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected @@ -14,10 +14,10 @@ edges | http_test.py:5:16:5:19 | ControlFlowNode for self | http_test.py:6:45:6:53 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | http_test.py:6:9:6:19 | ControlFlowNode for parsed_path | http_test.py:7:40:7:56 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | http_test.py:6:9:6:19 | ControlFlowNode for parsed_path | provenance | | -| http_test.py:6:45:6:53 | ControlFlowNode for Attribute | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | provenance | MaD:77 | +| http_test.py:6:45:6:53 | ControlFlowNode for Attribute | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | provenance | MaD:78 | | http_test.py:7:9:7:14 | ControlFlowNode for params | http_test.py:8:23:8:28 | ControlFlowNode for params | provenance | | | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | http_test.py:7:9:7:14 | ControlFlowNode for params | provenance | | -| http_test.py:7:40:7:56 | ControlFlowNode for Attribute | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | provenance | MaD:76 | +| http_test.py:7:40:7:56 | ControlFlowNode for Attribute | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | provenance | MaD:77 | | http_test.py:8:9:8:19 | ControlFlowNode for input_value | http_test.py:12:40:12:50 | ControlFlowNode for input_value | provenance | | | http_test.py:8:23:8:28 | ControlFlowNode for params | http_test.py:8:23:8:47 | ControlFlowNode for Attribute() | provenance | dict.get | | http_test.py:8:23:8:47 | ControlFlowNode for Attribute() | http_test.py:8:9:8:19 | ControlFlowNode for input_value | provenance | | diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll index 3be4fdbcfe89..10d5a6621518 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -927,6 +927,85 @@ module BarrierGuard { } } +bindingset[this] +private signature class ParamSig; + +private module WithParam { + /** + * Holds if the guard `g` validates the expression `e` upon evaluating to `branch`. + * + * The expression `e` is expected to be a syntactic part of the guard `g`. + * For example, the guard `g` might be a call `isSafe(x)` and the expression `e` + * the argument `x`. + */ + signature predicate guardChecksSig(CfgNodes::AstCfgNode g, CfgNode e, boolean branch, P param); +} + +/** + * Provides a set of barrier nodes for a guard that validates a node. + * + * This is expected to be used in `isBarrier`/`isSanitizer` definitions + * in data flow and taint tracking. + */ +module ParameterizedBarrierGuard::guardChecksSig/4 guardChecks> { + private import codeql.ruby.controlflow.internal.Guards + + /** + * Gets an implicit entry definition for a captured variable that + * may be guarded, because a call to the capturing callable is guarded. + * + * This is restricted to calls where the variable is captured inside a + * block. + */ + pragma[nomagic] + private Ssa::CapturedEntryDefinition getAMaybeGuardedCapturedDef(P param) { + exists( + CfgNodes::ExprCfgNode g, boolean branch, CfgNodes::ExprCfgNode testedNode, + Ssa::Definition def, CfgNodes::ExprNodes::CallCfgNode call + | + def.getARead() = testedNode and + guardChecks(g, testedNode, branch, param) and + guardControlsBlock(g, call.getBasicBlock(), branch) and + result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock() and + sameSourceVariable(def, result) + ) + } + + /** Gets a node that is safely guarded by the given guard check. */ + Node getABarrierNode(P param) { + SsaFlow::asNode(result) = + SsaImpl::DataFlowIntegration::ParameterizedBarrierGuard::getABarrierNode(param) + or + result.asExpr() = getAMaybeGuardedCapturedDef(param).getARead() + } +} + +/** + * Provides a set of barrier nodes for a guard that validates a node as described by an external predicate. + * + * This is expected to be used in `isBarrier`/`isSanitizer` definitions + * in data flow and taint tracking. + */ +module ExternalBarrierGuard { + private import codeql.ruby.frameworks.data.ModelsAsData + + private predicate guardCheck(CfgNodes::AstCfgNode g, CfgNode e, boolean branch, string kind) { + // (GuardNode g, ControlFlowNode node, boolean branch, string kind) { + exists(API::Node call, API::Node parameter | + parameter.asSink() = call.asCall().getArgument(_) and + parameter = ModelOutput::getABarrierGuardNode(kind, branch) + | + g = call.asCall().asExpr() and + e = parameter.asSink().asExpr() + ) + } + + /** Gets a node that is an external barrier of the given kind. */ + ExprNode getAnExternalBarrierNode(string kind) { + result = ParameterizedBarrierGuard::getABarrierNode(kind) + } +} + /** * A representation of a run-time module or class. * diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll index 029d4c530601..1856d03c1190 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll @@ -403,6 +403,33 @@ private module Cached { predicate getABarrierNode = getABarrierNodeImpl/0; } + + bindingset[this] + private signature class ParamSig; + + private module WithParam { + signature predicate guardChecksSig( + Cfg::CfgNodes::AstCfgNode g, Cfg::CfgNode e, boolean branch, P param + ); + } + + overlay[global] + cached // nothing is actually cached + module ParameterizedBarrierGuard::guardChecksSig/4 guardChecks> { + private predicate guardChecksAdjTypes( + DataFlowIntegrationInput::Guard g, DataFlowIntegrationInput::Expr e, + DataFlowIntegrationInput::GuardValue branch, P param + ) { + guardChecks(g, e, branch, param) + } + + private Node getABarrierNodeImpl(P param) { + result = + DataFlowIntegrationImpl::BarrierGuardWithState::getABarrierNode(param) + } + + predicate getABarrierNode = getABarrierNodeImpl/1; + } } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll index 4d57191dc1ed..a8f72895b0b5 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll @@ -27,7 +27,7 @@ private import codeql.ruby.dataflow.FlowSummary * A remote flow source originating from a CSV source row. */ private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range { - RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").asSource() } + RemoteFlowSourceFromCsv() { ModelOutput::sourceNode(this, "remote") } override string getSourceType() { result = "Remote flow (from model)" } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..a8ef2f4e985e 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll @@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model) ) } +/** Holds if a barrier model exists for the given parameters. */ +private predicate barrierModel(string type, string path, string kind, string model) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierModel(type, path, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + +/** Holds if a barrier guard model exists for the given parameters. */ +private predicate barrierGuardModel( + string type, string path, string branch, string kind, string model +) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierGuardModel(type, path, branch, 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, string model @@ -400,6 +420,8 @@ predicate isRelevantType(string type) { ( sourceModel(type, _, _, _) or sinkModel(type, _, _, _) or + barrierModel(type, _, _, _) or + barrierGuardModel(type, _, _, _, _) or summaryModel(type, _, _, _, _, _) or typeModel(_, type, _) ) and @@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) { ( sourceModel(type, path, _, _) or sinkModel(type, path, _, _) or + barrierModel(type, path, _, _) or + barrierGuardModel(type, path, _, _, _) or summaryModel(type, path, _, _, _, _) or typeModel(_, type, path) ) @@ -745,6 +769,32 @@ module ModelOutput { ) } + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierNode(string kind, string model) { + exists(string type, string path | + barrierModel(type, path, kind, model) and + result = getNodeFromPath(type, path) + ) + } + + /** + * Holds if a barrier model contributed `barrier` with the given `kind` for the given `branch`. + */ + cached + API::Node getABarrierGuardNode(string kind, boolean branch, string model) { + exists(string type, string path, string branch_str | + branch = true and branch_str = "true" + or + branch = false and branch_str = "false" + | + barrierGuardModel(type, path, branch_str, kind, model) and + result = getNodeFromPath(type, path) + ) + } + /** * Holds if a relevant summary exists for these parameters. */ @@ -787,15 +837,46 @@ module ModelOutput { private import codeql.mad.ModelValidation as SharedModelVal /** - * Holds if a CSV source model contributed `source` with the given `kind`. + * Holds if an external 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`. + * Holds if an external model contributed `sink` with the given `kind`. */ API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) } + /** + * Holds if an external model contributed `barrier` with the given `kind`. + */ + API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) } + + /** + * Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`. + */ + API::Node getABarrierGuardNode(string kind, boolean branch) { + result = getABarrierGuardNode(kind, branch, _) + } + + /** + * Holds if `node` is specified as a source with the given kind in an external model. + */ + predicate sourceNode(DataFlow::Node node, string kind) { node = getASourceNode(kind).asSource() } + + /** + * Holds if `node` is specified as a sink with the given kind in an external model. + */ + predicate sinkNode(DataFlow::Node node, string kind) { node = getASinkNode(kind).asSink() } + + /** + * Holds if `node` is specified as a barrier with the given kind in an external model. + */ + predicate barrierNode(DataFlow::Node node, string kind) { + node = getABarrierNode(kind).asSink() + or + node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind) + } + private module KindValConfig implements SharedModelVal::KindValidationConfigSig { predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll index 3f38c498f324..2a644aabb95d 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll @@ -20,6 +20,26 @@ extensible predicate sourceModel( */ extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId); +/** + * Holds if the value at `(type, path)` should be seen as a barrier + * of the given `kind` and `madId` is the data extension row number. + */ +extensible predicate barrierModel( + string type, string path, string kind, QlBuiltins::ExtensionId madId +); + +/** + * Holds if the value at `(type, path)` should be seen as a barrier guard + * of the given `kind` and `madId` is the data extension row number. + * `path` is assumed to lead to a parameter of a call (possibly `self`), and + * the call is guarding the parameter. + * `branch` is either `true` or `false`, indicating which branch of the guard + * is protecting the parameter. + */ +extensible predicate barrierGuardModel( + string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId +); + /** * Holds if in calls to `(type, path)`, the value referred to by `input` * can flow to the value referred to by `output` and `madId` is the data diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/empty.model.yml b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/empty.model.yml index b887eed7c1c5..ec68e8fcb380 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/empty.model.yml +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/empty.model.yml @@ -16,6 +16,16 @@ extensions: extensible: summaryModel data: [] + - addsTo: + pack: codeql/ruby-all + extensible: barrierModel + data: [] + + - addsTo: + pack: codeql/ruby-all + extensible: barrierGuardModel + data: [] + - addsTo: pack: codeql/ruby-all extensible: neutralModel diff --git a/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll index fe08d83d6fb0..ca79a079a107 100644 --- a/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll @@ -116,6 +116,6 @@ module CodeInjection { } private class ExternalCodeInjectionSink extends Sink { - ExternalCodeInjectionSink() { this = ModelOutput::getASinkNode("code-injection").asSink() } + ExternalCodeInjectionSink() { ModelOutput::sinkNode(this, "code-injection") } } } diff --git a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll index 542049c88755..f36b72ae6b79 100644 --- a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll @@ -55,8 +55,6 @@ module CommandInjection { } private class ExternalCommandInjectionSink extends Sink { - ExternalCommandInjectionSink() { - this = ModelOutput::getASinkNode("command-injection").asSink() - } + ExternalCommandInjectionSink() { ModelOutput::sinkNode(this, "command-injection") } } } diff --git a/ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll b/ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll index 487dc31d4f0b..8111932c7df4 100644 --- a/ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll @@ -38,7 +38,7 @@ class LoggingSink extends Sink { } private class ExternalLogInjectionSink extends Sink { - ExternalLogInjectionSink() { this = ModelOutput::getASinkNode("log-injection").asSink() } + ExternalLogInjectionSink() { ModelOutput::sinkNode(this, "log-injection") } } /** diff --git a/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll index aa8c183db8bb..8a8b916f6275 100644 --- a/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll @@ -55,6 +55,6 @@ module PathInjection { { } private class ExternalPathInjectionSink extends Sink { - ExternalPathInjectionSink() { this = ModelOutput::getASinkNode("path-injection").asSink() } + ExternalPathInjectionSink() { ModelOutput::sinkNode(this, "path-injection") } } } diff --git a/ruby/ql/lib/codeql/ruby/security/ServerSideRequestForgeryCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/ServerSideRequestForgeryCustomizations.qll index 9fd20d9bc9d7..509900a12e15 100644 --- a/ruby/ql/lib/codeql/ruby/security/ServerSideRequestForgeryCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/ServerSideRequestForgeryCustomizations.qll @@ -44,6 +44,6 @@ module ServerSideRequestForgery { class StringInterpolationAsSanitizer extends PrefixedStringInterpolation, Sanitizer { } private class ExternalRequestForgerySink extends Sink { - ExternalRequestForgerySink() { this = ModelOutput::getASinkNode("request-forgery").asSink() } + ExternalRequestForgerySink() { ModelOutput::sinkNode(this, "request-forgery") } } } diff --git a/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll index 722730ae1b15..1bf14dc3b280 100644 --- a/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll @@ -59,6 +59,6 @@ module SqlInjection { private class SqlSanitizationAsSanitizer extends Sanitizer, SqlSanitization { } private class ExternalSqlInjectionSink extends Sink { - ExternalSqlInjectionSink() { this = ModelOutput::getASinkNode("sql-injection").asSink() } + ExternalSqlInjectionSink() { ModelOutput::sinkNode(this, "sql-injection") } } } diff --git a/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll index bad046f921f6..4e02b3181e35 100644 --- a/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll @@ -75,7 +75,7 @@ module UrlRedirect { } private class ExternalUrlRedirectSink extends Sink { - ExternalUrlRedirectSink() { this = ModelOutput::getASinkNode("url-redirection").asSink() } + ExternalUrlRedirectSink() { ModelOutput::sinkNode(this, "url-redirection") } } /** diff --git a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql index 7b370496f0a6..36ef1e560760 100644 --- a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql +++ b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql @@ -80,7 +80,7 @@ module CustomConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node sink) { DefaultFlowConfig::isSink(sink) or - sink = ModelOutput::getASinkNode("test-sink").asSink() + ModelOutput::sinkNode(sink, "test-sink") } }