Merge pull request #21004 from yoff/python/mad-barriers

Python: MaD barriers
This commit is contained in:
yoff
2026-01-26 13:11:21 +01:00
committed by GitHub
60 changed files with 652 additions and 127 deletions

View File

@@ -36,6 +36,41 @@ module MakeBarrierGuard<BarrierGuardSig BaseGuard> {
}
}
/**
* 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.
*
* This only provides external barrier nodes defined as guards. To get all externally defined barrer nodes,
* use `ModelOutput::barrierNode(node, kind)`.
*
* INTERNAL: Do not use.
*/
DataFlow::Node getAnExternalBarrierNode(string kind) {
result = MakeStateBarrierGuard<string, BarrierGuard>::getABarrierNode(kind)
}
}
deprecated private module DeprecationWrapper {
signature class LabeledBarrierGuardSig extends DataFlow::Node {
/**

View File

@@ -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) }
}

View File

@@ -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() {

View File

@@ -8,3 +8,9 @@ extensions:
- ['global', 'Member[process].Member[stdin].Member[on,addListener].WithStringArgument[0=data].Argument[1].Parameter[0]', 'stdin']
- ['readline', 'Member[createInterface].ReturnValue.Member[question].Argument[1].Parameter[0]', 'stdin']
- ['readline', 'Member[createInterface].ReturnValue.Member[on,addListener].WithStringArgument[0=line].Argument[1].Parameter[0]', 'stdin']
- addsTo:
pack: codeql/javascript-all
extensible: barrierModel
data:
- ['global', 'Member[encodeURIComponent,encodeURI].ReturnValue', 'request-forgery']

View File

@@ -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") }
}
/**

View File

@@ -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]"

View File

@@ -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)
)
@@ -747,6 +771,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.
*/
@@ -789,15 +839,50 @@ 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`.
*
* INTERNAL: Do not use.
*/
API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) }
/**
* Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`.
*
* INTERNAL: Do not use.
*/
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).asSource()
or
node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind)
}
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }

View File

@@ -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

View File

@@ -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

View File

@@ -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") }
}
/**

View File

@@ -268,6 +268,6 @@ module ClientSideUrlRedirect {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "url-redirection") }
}
}

View File

@@ -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") }
}
}

View File

@@ -56,6 +56,6 @@ module CommandInjection {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("command-injection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "command-injection") }
}
}

View File

@@ -419,6 +419,6 @@ module DomBasedXss {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("html-injection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "html-injection") }
}
}

View File

@@ -101,13 +101,7 @@ module IncompleteHtmlAttributeSanitization {
}
}
/**
* An encoder for potentially malicious characters, as a sanitizer
* for incomplete HTML sanitization vulnerabilities.
*/
class EncodingSanitizer extends Sanitizer {
EncodingSanitizer() {
this = DataFlow::globalVarRef(["encodeURIComponent", "encodeURI"]).getACall()
}
private class SanitizerFromModel extends Sanitizer {
SanitizerFromModel() { ModelOutput::barrierNode(this, "request-forgery") }
}
}

View File

@@ -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") }
}

View File

@@ -145,6 +145,6 @@ module ReflectedXss {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("html-injection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "html-injection") }
}
}

View File

@@ -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 }

View File

@@ -64,6 +64,6 @@ module ServerSideUrlRedirect {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "url-redirection") }
}
}

View File

@@ -1122,6 +1122,6 @@ module TaintedPath {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("path-injection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "path-injection") }
}
}

View File

@@ -67,6 +67,6 @@ module UnsafeDeserialization {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "unsafe-deserialization") }
}
}

View File

@@ -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() }

View File

@@ -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,72 @@ signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean br
module BarrierGuard<guardChecksSig/3 guardChecks> {
/** Gets a node that is safely guarded by the given guard check. */
ExprNode getABarrierNode() {
result = ParameterizedBarrierGuard<Unit, extendedGuardChecks/4>::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<ParamSig P> {
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<ParamSig P, WithParam<P>::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.
*
* This only provides external barrier nodes defined as guards. To get all externally defined barrer nodes,
* use `ModelOutput::barrierNode(node, kind)`.
*
* INTERNAL: Do not use.
*/
ExprNode getAnExternalBarrierNode(string kind) {
result = ParameterizedBarrierGuard<string, guardCheck/4>::getABarrierNode(kind)
}
}
/**
* Algebraic datatype for tracking data content associated with values.
* Content can be collection elements or object attributes.

View File

@@ -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']

View File

@@ -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<djangoUrlHasAllowedHostAndScheme/3>::getABarrierNode()
}
override predicate sanitizes(UrlRedirect::FlowState state) {
// sanitize all flow states
any()
}
}
// ---------------------------------------------------------------------------
// Templates
// ---------------------------------------------------------------------------

View File

@@ -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]"

View File

@@ -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)
)
@@ -747,6 +771,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.
*/
@@ -789,15 +839,50 @@ 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`.
*
* INTERNAL: Do not use.
*/
API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) }
/**
* Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`.
*
* INTERNAL: Do not use.
*/
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).asSource()
or
node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind)
}
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }

View File

@@ -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

View File

@@ -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

View File

@@ -50,7 +50,7 @@ module CodeInjection {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("code-injection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "code-injection") }
}
/**

View File

@@ -85,7 +85,7 @@ module CommandInjection {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("command-injection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "command-injection") }
}
/**

View File

@@ -78,7 +78,7 @@ module LogInjection {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("log-injection").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "log-injection") }
}
/**

View File

@@ -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") }
}
/**

View File

@@ -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"]) }
}
/**

View File

@@ -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") }
}
}

View File

@@ -55,7 +55,7 @@ module UnsafeDeserialization {
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() }
SinkFromModel() { ModelOutput::sinkNode(this, "unsafe-deserialization") }
}
/**

View File

@@ -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,16 @@ 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() { ModelOutput::barrierNode(this, "url-redirection") }
override predicate sanitizes(FlowState state) {
// sanitize all flow states
any()
}
}
}

View File

@@ -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 |

View File

@@ -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

View File

@@ -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 | |

View File

@@ -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 |

View File

@@ -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<BasicTaintTrackingConfig>;
@@ -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() }

View File

@@ -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 |

View File

@@ -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 | |

View File

@@ -927,6 +927,92 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
}
}
bindingset[this]
private signature class ParamSig;
private module WithParam<ParamSig P> {
/**
* 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<ParamSig P, WithParam<P>::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<P, guardChecks/4>::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.
*
* This only provides external barrier nodes defined as guards. To get all externally defined barrer nodes,
* use `ModelOutput::barrierNode(node, kind)`.
*
* INTERNAL: Do not use.
*/
ExprNode getAnExternalBarrierNode(string kind) {
result = ParameterizedBarrierGuard<string, guardCheck/4>::getABarrierNode(kind)
}
}
/**
* A representation of a run-time module or class.
*

View File

@@ -403,6 +403,33 @@ private module Cached {
predicate getABarrierNode = getABarrierNodeImpl/0;
}
bindingset[this]
private signature class ParamSig;
private module WithParam<ParamSig P> {
signature predicate guardChecksSig(
Cfg::CfgNodes::AstCfgNode g, Cfg::CfgNode e, boolean branch, P param
);
}
overlay[global]
cached // nothing is actually cached
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::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<P, guardChecksAdjTypes/4>::getABarrierNode(param)
}
predicate getABarrierNode = getABarrierNodeImpl/1;
}
}
}

View File

@@ -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)" }
}

View File

@@ -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)
)
@@ -747,6 +771,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.
*/
@@ -789,15 +839,50 @@ 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`.
*
* INTERNAL: Do not use.
*/
API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) }
/**
* Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`.
*
* INTERNAL: Do not use.
*/
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).asSource()
or
node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind)
}
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,6 @@
extensions:
- addsTo:
pack: codeql/ruby-all
extensible: barrierModel
data:
- ['Regexp!', 'Method[escape,quote].ReturnValue', 'regexp-injection']

View File

@@ -116,6 +116,6 @@ module CodeInjection {
}
private class ExternalCodeInjectionSink extends Sink {
ExternalCodeInjectionSink() { this = ModelOutput::getASinkNode("code-injection").asSink() }
ExternalCodeInjectionSink() { ModelOutput::sinkNode(this, "code-injection") }
}
}

View File

@@ -55,8 +55,6 @@ module CommandInjection {
}
private class ExternalCommandInjectionSink extends Sink {
ExternalCommandInjectionSink() {
this = ModelOutput::getASinkNode("command-injection").asSink()
}
ExternalCommandInjectionSink() { ModelOutput::sinkNode(this, "command-injection") }
}
}

View File

@@ -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") }
}
/**

View File

@@ -55,6 +55,6 @@ module PathInjection {
{ }
private class ExternalPathInjectionSink extends Sink {
ExternalPathInjectionSink() { this = ModelOutput::getASinkNode("path-injection").asSink() }
ExternalPathInjectionSink() { ModelOutput::sinkNode(this, "path-injection") }
}
}

View File

@@ -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") }
}
}

View File

@@ -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") }
}
}

View File

@@ -75,7 +75,7 @@ module UrlRedirect {
}
private class ExternalUrlRedirectSink extends Sink {
ExternalUrlRedirectSink() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
ExternalUrlRedirectSink() { ModelOutput::sinkNode(this, "url-redirection") }
}
/**

View File

@@ -10,6 +10,7 @@ private import codeql.ruby.Frameworks
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.dataflow.BarrierGuards
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.data.internal.ApiGraphModels
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -69,13 +70,7 @@ module RegExpInjection {
StringConstArrayInclusionCallBarrier
{ }
/**
* A call to `Regexp.escape` (or its alias, `Regexp.quote`), considered as a
* sanitizer.
*/
class RegexpEscapeSanitization extends Sanitizer {
RegexpEscapeSanitization() {
this = API::getTopLevelMember("Regexp").getAMethodCall(["escape", "quote"])
}
private class ExternalRegexpInjectionSanitizer extends Sanitizer {
ExternalRegexpInjectionSanitizer() { ModelOutput::barrierNode(this, "regexp-injection") }
}
}

View File

@@ -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")
}
}