mirror of
https://github.com/github/codeql.git
synced 2026-02-12 05:01:06 +01:00
python: add machinery for MaD barriers
and reinstate previously removed barrier now as a MaD row
This commit is contained in:
@@ -927,6 +927,85 @@ 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. */
|
||||
ExprNode getAnExternalBarrierNode(string kind) {
|
||||
result = ParameterizedBarrierGuard<string, guardCheck/4>::getABarrierNode(kind)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a run-time module or class.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)" }
|
||||
}
|
||||
|
||||
@@ -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,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).asSource()
|
||||
or
|
||||
node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind)
|
||||
}
|
||||
|
||||
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
|
||||
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -116,6 +116,6 @@ module CodeInjection {
|
||||
}
|
||||
|
||||
private class ExternalCodeInjectionSink extends Sink {
|
||||
ExternalCodeInjectionSink() { this = ModelOutput::getASinkNode("code-injection").asSink() }
|
||||
ExternalCodeInjectionSink() { ModelOutput::sinkNode(this, "code-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,6 @@ module CommandInjection {
|
||||
}
|
||||
|
||||
private class ExternalCommandInjectionSink extends Sink {
|
||||
ExternalCommandInjectionSink() {
|
||||
this = ModelOutput::getASinkNode("command-injection").asSink()
|
||||
}
|
||||
ExternalCommandInjectionSink() { ModelOutput::sinkNode(this, "command-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,6 @@ module PathInjection {
|
||||
{ }
|
||||
|
||||
private class ExternalPathInjectionSink extends Sink {
|
||||
ExternalPathInjectionSink() { this = ModelOutput::getASinkNode("path-injection").asSink() }
|
||||
ExternalPathInjectionSink() { ModelOutput::sinkNode(this, "path-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ module UrlRedirect {
|
||||
}
|
||||
|
||||
private class ExternalUrlRedirectSink extends Sink {
|
||||
ExternalUrlRedirectSink() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
|
||||
ExternalUrlRedirectSink() { ModelOutput::sinkNode(this, "url-redirection") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user