Merge branch 'main' into js/graph-export

This commit is contained in:
Asger F
2024-04-16 20:23:33 +02:00
1236 changed files with 37405 additions and 42510 deletions

View File

@@ -26,7 +26,6 @@ private import codeql.ruby.frameworks.XmlParsing
private import codeql.ruby.frameworks.ActionDispatch
private import codeql.ruby.frameworks.PosixSpawn
private import codeql.ruby.frameworks.StringFormatters
private import codeql.ruby.frameworks.Json
private import codeql.ruby.frameworks.Erb
private import codeql.ruby.frameworks.Slim
private import codeql.ruby.frameworks.Sinatra

View File

@@ -560,6 +560,10 @@ private predicate isArrayExpr(Expr e, ArrayLiteralCfgNode arr) {
// Note(hmac): I don't think this is necessary, as `getSource` will not return
// results if the source is a phi node.
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isArrayConstant(n, arr))
or
// if `e` is an array, then `e.freeze` is also an array
e.(MethodCall).getMethodName() = "freeze" and
isArrayExpr(e.(MethodCall).getReceiver(), arr)
}
private class TokenConstantAccess extends ConstantAccess, TTokenConstantAccess {

View File

@@ -32,9 +32,22 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari
* DEPRECATED: Use `propagatesFlow` instead.
*/
deprecated predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
this.propagatesFlow(input, output, preservesValue)
this.propagatesFlow(input, output, preservesValue, _)
}
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
) {
this.propagatesFlow(input, output, preservesValue) and model = ""
}
/**
* Holds if data may flow from `input` to `output` through this callable.
*
* `preservesValue` indicates whether this is a value-preserving step or a taint-step.
*/
predicate propagatesFlow(string input, string output, boolean preservesValue) { none() }
/**
* Gets the synthesized parameter that results from an input specification
* that starts with `Argument[s]` for this library callable.
@@ -100,7 +113,9 @@ private module LibraryCallbackSummaries {
libraryCallHasLambdaArg(result.getAControlFlowNode(), _)
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
) {
(
input = "Argument[block]" and
output = "Argument[block].Parameter[lambda-self]"
@@ -111,7 +126,8 @@ private module LibraryCallbackSummaries {
output = "Argument[" + i + "].Parameter[lambda-self]"
)
) and
preservesValue = true
preservesValue = true and
model = "heuristic-callback"
}
}
}

View File

@@ -263,9 +263,10 @@ deprecated private module Config implements FullStateConfigSig {
predicate isBarrierOut(Node node, FlowState state) { none() }
predicate isAdditionalFlowStep(Node node1, Node node2) {
predicate isAdditionalFlowStep(Node node1, Node node2, string model) {
singleConfiguration() and
any(Configuration config).isAdditionalFlowStep(node1, node2)
any(Configuration config).isAdditionalFlowStep(node1, node2) and
model = ""
}
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {

View File

@@ -263,9 +263,10 @@ deprecated private module Config implements FullStateConfigSig {
predicate isBarrierOut(Node node, FlowState state) { none() }
predicate isAdditionalFlowStep(Node node1, Node node2) {
predicate isAdditionalFlowStep(Node node1, Node node2, string model) {
singleConfiguration() and
any(Configuration config).isAdditionalFlowStep(node1, node2)
any(Configuration config).isAdditionalFlowStep(node1, node2) and
model = ""
}
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {

View File

@@ -244,10 +244,11 @@ module LocalFlow {
}
predicate flowSummaryLocalStep(
FlowSummaryNode nodeFrom, FlowSummaryNode nodeTo, FlowSummaryImpl::Public::SummarizedCallable c
FlowSummaryNode nodeFrom, FlowSummaryNode nodeTo, FlowSummaryImpl::Public::SummarizedCallable c,
string model
) {
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.getSummaryNode(),
nodeTo.getSummaryNode(), true) and
nodeTo.getSummaryNode(), true, model) and
c = nodeFrom.getSummarizedCallable()
}
@@ -271,7 +272,7 @@ module LocalFlow {
node1 =
unique(FlowSummaryNode n1 |
FlowSummaryImpl::Private::Steps::summaryLocalStep(n1.getSummaryNode(),
node2.(FlowSummaryNode).getSummaryNode(), true)
node2.(FlowSummaryNode).getSummaryNode(), true, _)
)
}
}
@@ -606,25 +607,28 @@ private module Cached {
* data flow.
*/
cached
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
exists(SsaImpl::DefinitionExt def |
// captured variables are handled by the shared `VariableCapture` library
not def instanceof VariableCapture::CapturedSsaDefinitionExt
|
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
(
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
exists(SsaImpl::DefinitionExt def |
// captured variables are handled by the shared `VariableCapture` library
not def instanceof VariableCapture::CapturedSsaDefinitionExt
|
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
or
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
or
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
)
or
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
)
VariableCapture::valueStep(nodeFrom, nodeTo)
) and
model = ""
or
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _)
or
VariableCapture::valueStep(nodeFrom, nodeTo)
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _, model)
}
/** This is the local flow predicate that is exposed. */
@@ -656,7 +660,8 @@ private module Cached {
or
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo)
or
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, any(LibraryCallableToIncludeInTypeTracking c))
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, any(LibraryCallableToIncludeInTypeTracking c),
_)
}
/** Holds if `n` wraps an SSA definition without ingoing flow. */
@@ -752,7 +757,7 @@ private module Cached {
// external model data. This, unfortunately, does not included any field names used
// in models defined in QL code.
exists(string input, string output |
ModelOutput::relevantSummaryModel(_, _, input, output, _)
ModelOutput::relevantSummaryModel(_, _, input, output, _, _)
|
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
)
@@ -2241,6 +2246,14 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
predicate knownSourceModel(Node source, string model) {
source = ModelOutput::getASourceNode(_, model).asSource()
}
predicate knownSinkModel(Node sink, string model) {
sink = ModelOutput::getASinkNode(_, model).asSink()
}
/**
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
* side-effect, resulting in a summary from `p` to itself.

View File

@@ -77,38 +77,41 @@ private module Cached {
* in all global taint flow configurations.
*/
cached
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// value of `case` expression into variables in patterns
exists(
CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprCfgNode value,
CfgNodes::ExprNodes::InClauseCfgNode clause, Ssa::Definition def
|
nodeFrom.asExpr() = value and
value = case.getValue() and
clause = case.getBranch(_) and
def = nodeTo.(SsaDefinitionExtNode).getDefinitionExt() and
def.getControlFlowNode() = variablesInPattern(clause.getPattern()) and
not LocalFlow::ssaDefAssigns(def, value)
)
or
// operation involving `nodeFrom`
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and
op.getAnOperand() = nodeFrom.asExpr() and
not op.getExpr() =
any(Expr e |
// included in normal data-flow
e instanceof AssignExpr or
e instanceof BinaryLogicalOperation or
// has flow summary
e instanceof SplatExpr
)
)
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) {
(
// value of `case` expression into variables in patterns
exists(
CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprCfgNode value,
CfgNodes::ExprNodes::InClauseCfgNode clause, Ssa::Definition def
|
nodeFrom.asExpr() = value and
value = case.getValue() and
clause = case.getBranch(_) and
def = nodeTo.(SsaDefinitionExtNode).getDefinitionExt() and
def.getControlFlowNode() = variablesInPattern(clause.getPattern()) and
not LocalFlow::ssaDefAssigns(def, value)
)
or
// operation involving `nodeFrom`
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and
op.getAnOperand() = nodeFrom.asExpr() and
not op.getExpr() =
any(Expr e |
// included in normal data-flow
e instanceof AssignExpr or
e instanceof BinaryLogicalOperation or
// has flow summary
e instanceof SplatExpr
)
)
) and
model = ""
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
nodeTo.(FlowSummaryNode).getSummaryNode(), false)
nodeTo.(FlowSummaryNode).getSummaryNode(), false, model)
or
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo)
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo) and model = "AdditionalTaintStep"
or
// Although flow through collections is modeled precisely using stores/reads, we still
// allow flow out of a _tainted_ collection. This is needed in order to support taint-
@@ -119,7 +122,8 @@ private module Cached {
c.isKnownOrUnknownElement(_)
or
c.isAnyElement()
)
) and
model = ""
}
cached
@@ -136,7 +140,7 @@ private module Cached {
cached
predicate localTaintStepCached(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
DataFlow::localFlowStep(nodeFrom, nodeTo) or
defaultAdditionalTaintStep(nodeFrom, nodeTo) or
defaultAdditionalTaintStep(nodeFrom, nodeTo, _) or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
summaryThroughStepTaint(nodeFrom, nodeTo, _)

View File

@@ -127,7 +127,7 @@ abstract deprecated class Configuration extends DataFlow::Configuration {
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
this.isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
defaultAdditionalTaintStep(node1, node2, _)
}
/**

View File

@@ -792,3 +792,36 @@ class ActiveRecordScopeCallTarget extends AdditionalCallTarget {
)
}
}
/** Sinks for the mass assignment query. */
private module MassAssignmentSinks {
private import codeql.ruby.security.MassAssignmentCustomizations
pragma[nomagic]
private predicate massAssignmentCall(DataFlow::CallNode call, string name) {
call = activeRecordBaseClass().getAMethodCall(name)
or
call instanceof ActiveRecordInstanceMethodCall and
call.getMethodName() = name
}
/** A call to a method that sets attributes of an database record using a hash. */
private class MassAssignmentSink extends MassAssignment::Sink {
MassAssignmentSink() {
exists(DataFlow::CallNode call, string name | massAssignmentCall(call, name) |
name =
[
"build", "create", "create!", "create_with", "create_or_find_by", "create_or_find_by!",
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "insert", "insert!",
"insert_all", "insert_all!", "instantiate", "new", "update", "update!", "upsert",
"upsert_all"
] and
this = call.getArgument(0)
or
// These methods have an optional first id parameter.
name = ["update", "update!"] and
this = call.getArgument(1)
)
}
}
}

View File

@@ -0,0 +1,22 @@
extensions:
- addsTo:
pack: codeql/ruby-all
extensible: summaryModel
data:
- ['ActiveStorage::Filename!', 'Method[new]', 'Argument[0]', 'ReturnValue', 'taint']
- ['ActiveStorage::Filename', 'Method[sanitized]', 'Argument[self]', 'ReturnValue', 'taint']
- addsTo:
pack: codeql/ruby-all
extensible: typeModel
data:
# ActiveStorage::Blob.compose(blobs : [Blob]) : Blob
- ['ActiveStorage::Blob', 'ActiveStorage::Blob!', 'Method[compose].ReturnValue']
# ActiveStorage::Blob.create_and_upload! : Blob
- ['ActiveStorage::Blob', 'ActiveStorage::Blob!', 'Method[create_and_upload!].ReturnValue']
# ActiveStorage::Blob.create_before_direct_upload! : Blob
- ['ActiveStorage::Blob', 'ActiveStorage::Blob!', 'Method[create_before_direct_upload!].ReturnValue']
# ActiveStorage::Blob.find_signed(!) : Blob
- ['ActiveStorage::Blob', 'ActiveStorage::Blob!', 'Method[find_signed,find_signed!].ReturnValue']
# gives error: Invalid name 'Element' in access path
# - ['ActiveStorage::Blob', 'ActiveStorage::Blob!', 'Method[compose].Argument[0].Element[any]']

View File

@@ -26,39 +26,6 @@ module ActiveStorage {
}
}
/** Taint related to `ActiveStorage::Filename`. */
private class FilenameSummaries extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
"ActiveStorage::Filename!;Method[new];Argument[0];ReturnValue;taint",
"ActiveStorage::Filename;Method[sanitized];Argument[self];ReturnValue;taint",
]
}
}
/**
* `Blob` is an instance of `ActiveStorage::Blob`.
*/
private class BlobTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1;type1;package2;type2;path
row =
[
// ActiveStorage::Blob.create_and_upload! : Blob
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[create_and_upload!].ReturnValue",
// ActiveStorage::Blob.create_before_direct_upload! : Blob
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[create_before_direct_upload!].ReturnValue",
// ActiveStorage::Blob.compose(blobs : [Blob]) : Blob
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[compose].ReturnValue",
// gives error: Invalid name 'Element' in access path
// "ActiveStorage::Blob;ActiveStorage::Blob!;Method[compose].Argument[0].Element[any]",
// ActiveStorage::Blob.find_signed(!) : Blob
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[find_signed,find_signed!].ReturnValue",
]
}
}
private class BlobInstance extends DataFlow::Node {
BlobInstance() {
this = ModelOutput::getATypeNode("ActiveStorage::Blob").getAValueReachableFromSource()

View File

@@ -0,0 +1,30 @@
extensions:
- addsTo:
pack: codeql/ruby-all
extensible: summaryModel
data:
# `ActiveSupport::SafeBuffer` wraps a string, providing HTML-safe methods
# for concatenation.
# It is possible to insert tainted data into `SafeBuffer` that won't get
# sanitized, and this taint is then propagated via most of the methods.
#
# TODO: SafeBuffer also reponds to all String methods.
# Can we model this without repeating all the existing summaries we have
# for String?
# SafeBuffer.new(x) does not sanitize x
- ['ActionView::SafeBuffer!', 'Method[new]', 'Argument[0]', 'ReturnValue', 'taint']
# These methods preserve taint in self
- ['ActionView::SafeBuffer', 'Method[concat,insert,prepend,to_s,to_param]', 'Argument[self]', 'ReturnValue', 'taint']
# SafeBuffer#safe_concat(x) does not sanitize x
- ['ActionView::SafeBuffer', 'Method[safe_concat]', 'Argument[0]', 'Argument[self]', 'taint']
- ['ActionView::SafeBuffer', 'Method[safe_concat]', 'Argument[0]', 'ReturnValue', 'taint']
- ['ActiveSupport::JSON!', 'Method[decode,load]', 'Argument[0]', 'ReturnValue', 'taint']
- ['ActiveSupport::JSON!', 'Method[encode,dump]', 'Argument[0]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[existence]', 'Argument[self]', 'ReturnValue', 'taint']
- addsTo:
pack: codeql/ruby-all
extensible: typeModel
data:
- ['Pathname', 'Pathname', 'Method[existence].ReturnValue']

View File

@@ -478,60 +478,4 @@ module ActiveSupport {
}
}
}
/**
* Type summaries for extensions to the `Pathname` module.
*/
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// type1;type2;path
// Pathname#existence : Pathname
row = "Pathname;Pathname;Method[existence].ReturnValue"
}
}
/** Taint flow summaries for extensions to the `Pathname` module. */
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
// Pathname#existence
row = "Pathname;Method[existence];Argument[self];ReturnValue;taint"
}
}
/**
* `ActiveSupport::SafeBuffer` wraps a string, providing HTML-safe methods
* for concatenation.
* It is possible to insert tainted data into `SafeBuffer` that won't get
* sanitized, and this taint is then propagated via most of the methods.
*/
private class SafeBufferSummary extends ModelInput::SummaryModelCsv {
// TODO: SafeBuffer also reponds to all String methods.
// Can we model this without repeating all the existing summaries we have
// for String?
override predicate row(string row) {
row =
[
// SafeBuffer.new(x) does not sanitize x
"ActionView::SafeBuffer!;Method[new];Argument[0];ReturnValue;taint",
// SafeBuffer#safe_concat(x) does not sanitize x
"ActionView::SafeBuffer;Method[safe_concat];Argument[0];ReturnValue;taint",
"ActionView::SafeBuffer;Method[safe_concat];Argument[0];Argument[self];taint",
// These methods preserve taint in self
"ActionView::SafeBuffer;Method[concat,insert,prepend,to_s,to_param];Argument[self];ReturnValue;taint",
]
}
}
/** `ActiveSupport::JSON` */
module Json {
private class JsonSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
"ActiveSupport::JSON!;Method[encode,dump];Argument[0];ReturnValue;taint",
"ActiveSupport::JSON!;Method[decode,load];Argument[0];ReturnValue;taint",
]
}
}
}
}

View File

@@ -13,7 +13,6 @@ import core.Module
import core.Array
import core.Hash
import core.String
import core.Regexp
import core.IO
import core.Digest
import core.Base64

View File

@@ -0,0 +1,11 @@
extensions:
- addsTo:
pack: codeql/ruby-all
extensible: summaryModel
data:
# Not all of these methods are strictly defined in the `json` gem.
# The `JSON` namespace is heavily overloaded by other JSON parsing gems such as `oj`, `json_pure`, `multi_json` etc.
# This summary covers common methods we've seen called on `JSON` in the wild.
- ['JSON!', 'Method[generate,fast_generate,pretty_generate,dump,unparse,fast_unparse]', 'Argument[0]', 'ReturnValue', 'taint']
- ['JSON!', 'Method[parse,parse!,load,restore]', 'Argument[0]', 'ReturnValue', 'taint']

View File

@@ -1,22 +0,0 @@
/** Provides modeling for the `json` gem. */
private import codeql.ruby.frameworks.data.ModelsAsData
/** Provides modeling for the `json` gem. */
module Json {
/**
* Flow summaries for common `JSON` methods.
* Not all of these methods are strictly defined in the `json` gem.
* The `JSON` namespace is heavily overloaded by other JSON parsing gems such as `oj`, `json_pure`, `multi_json` etc.
* This summary covers common methods we've seen called on `JSON` in the wild.
*/
private class JsonSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
"JSON!;Method[parse,parse!,load,restore];Argument[0];ReturnValue;taint",
"JSON!;Method[generate,fast_generate,pretty_generate,dump,unparse,fast_unparse];Argument[0];ReturnValue;taint",
]
}
}
}

View File

@@ -0,0 +1,10 @@
extensions:
- addsTo:
pack: codeql/ruby-all
extensible: typeModel
data:
- ['Mime::Type', 'Mime!', 'Method[fetch].ReturnValue']
- ['Mime::Type', 'Mime::Type!', 'Method[lookup].ReturnValue']
- ['Mime::Type', 'Mime::Type!', 'Method[lookup_by_extension].ReturnValue']
- ['Mime::Type', 'Mime::Type!', 'Method[register].ReturnValue']
- ['Mime::Type', 'Mime::Type!', 'Method[register_alias].ReturnValue']

View File

@@ -9,31 +9,6 @@ private import codeql.ruby.frameworks.data.ModelsAsData
* Models MIME type handling using the `ActionDispatch` library, which is part of Rails.
*/
module Mime {
/**
* Type summaries for the `Mime::Type` class, i.e. method calls that produce new
* `Mime::Type` instances.
*/
private class MimeTypeTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// type1;type2;path
row =
[
// Mime[type] : Mime::Type (omitted)
// Method names with brackets like [] cannot be represented in MaD.
// Mime.fetch(type) : Mime::Type
"Mime::Type;Mime!;Method[fetch].ReturnValue",
// Mime::Type.lookup(str) : Mime::Type
"Mime::Type;Mime::Type!;Method[lookup].ReturnValue",
// Mime::Type.lookup_by_extension(str) : Mime::Type
"Mime::Type;Mime::Type!;Method[lookup_by_extension].ReturnValue",
// Mime::Type.register(str) : Mime::Type
"Mime::Type;Mime::Type!;Method[register].ReturnValue",
// Mime::Type.register_alias(str) : Mime::Type
"Mime::Type;Mime::Type!;Method[register_alias].ReturnValue",
]
}
}
/**
* An argument to `Mime::Type#match?`, which is converted to a RegExp via
* `Regexp.new`.

View File

@@ -0,0 +1,7 @@
extensions:
- addsTo:
pack: codeql/ruby-all
extensible: summaryModel
data:
- ['Regexp!', 'Method[escape,quote]', 'Argument[0]', 'ReturnValue', 'taint']

View File

@@ -1,19 +0,0 @@
/**
* Provides modeling for the `Regexp` class.
*/
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.frameworks.data.ModelsAsData
/**
* Provides modeling for the `Regexp` class.
*/
module Regexp {
/** A flow summary for `Regexp.escape` and its alias, `Regexp.quote`. */
class RegexpEscapeSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row = "Regexp!;Method[escape,quote];Argument[0];ReturnValue;taint"
}
}
}

View File

@@ -37,7 +37,7 @@ private class SummarizedCallableFromModel extends SummarizedCallable {
string path;
SummarizedCallableFromModel() {
ModelOutput::relevantSummaryModel(type, path, _, _, _) and
ModelOutput::relevantSummaryModel(type, path, _, _, _, _) and
this = type + ";" + path
}
@@ -48,8 +48,10 @@ private class SummarizedCallableFromModel extends SummarizedCallable {
)
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind) |
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
) {
exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind, model) |
kind = "value" and
preservesValue = true
or

View File

@@ -1,17 +1,17 @@
/**
* INTERNAL use only. This is an experimental API subject to change without notice.
*
* Provides classes and predicates for dealing with flow models specified in CSV format.
* Provides classes and predicates for dealing with flow models specified in extensible predicates.
*
* The CSV specification has the following columns:
* The extensible predicates have the following columns:
* - Sources:
* `type; path; kind`
* `type, path, kind`
* - Sinks:
* `type; path; kind`
* `type, path, kind`
* - Summaries:
* `type; path; input; output; kind`
* `type, path, input, output, kind`
* - Types:
* `type1; type2; path`
* `type1, type2, path`
*
* The interpretation of a row is similar to API-graphs with a left-to-right
* reading.
@@ -76,11 +76,13 @@ private import codeql.dataflow.internal.AccessPathSyntax
/** Module containing hooks for providing input data to be interpreted as a model. */
module ModelInput {
/**
* DEPRECATED: Use the extensible predicate `sourceModel` instead.
*
* A unit class for adding additional source model rows.
*
* Extend this class to add additional source definitions.
*/
class SourceModelCsv extends Unit {
deprecated class SourceModelCsv extends Unit {
/**
* Holds if `row` specifies a source definition.
*
@@ -93,15 +95,17 @@ module ModelInput {
*
* The kind `remote` represents a general remote flow source.
*/
abstract predicate row(string row);
abstract deprecated predicate row(string row);
}
/**
* A unit class for adding additional sink model rows.
*
* Extend this class to add additional sink definitions.
*
* DEPRECATED: Use the extensible predicate `sinkModel` instead.
*/
class SinkModelCsv extends Unit {
deprecated class SinkModelCsv extends Unit {
/**
* Holds if `row` specifies a sink definition.
*
@@ -112,15 +116,17 @@ module ModelInput {
* indicates that the value at `(type, path)` should be seen as a sink
* of the given `kind`.
*/
abstract predicate row(string row);
abstract deprecated predicate row(string row);
}
/**
* A unit class for adding additional summary model rows.
*
* Extend this class to add additional flow summary definitions.
*
* DEPRECATED: Use the extensible predicate `summaryModel` instead.
*/
class SummaryModelCsv extends Unit {
deprecated class SummaryModelCsv extends Unit {
/**
* Holds if `row` specifies a summary definition.
*
@@ -134,15 +140,18 @@ module ModelInput {
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
* respectively.
*/
abstract predicate row(string row);
abstract deprecated predicate row(string row);
}
/**
* A unit class for adding additional type model rows.
*
* Extend this class to add additional type definitions.
*
* DEPRECATED: Use the extensible predicate `typeModel` or the class
* `TypeModel` instead.
*/
class TypeModelCsv extends Unit {
deprecated class TypeModelCsv extends Unit {
/**
* Holds if `row` specifies a type definition.
*
@@ -152,7 +161,7 @@ module ModelInput {
* ```
* indicates that `(type2, path)` should be seen as an instance of `type1`.
*/
abstract predicate row(string row);
abstract deprecated predicate row(string row);
}
/**
@@ -186,8 +195,10 @@ module ModelInput {
/**
* A unit class for adding additional type variable model rows.
*
* DEPRECATED: Use the extensible predicate `typeVariableModel` instead.
*/
class TypeVariableModelCsv extends Unit {
deprecated class TypeVariableModelCsv extends Unit {
/**
* Holds if `row` specifies a path through a type variable.
*
@@ -197,7 +208,7 @@ module ModelInput {
* ```
* means `path` can be substituted for a token `TypeVar[name]`.
*/
abstract predicate row(string row);
abstract deprecated predicate row(string row);
}
}
@@ -216,57 +227,121 @@ abstract class TestAllModels extends Unit { }
* does not preserve empty trailing substrings.
*/
bindingset[result]
private string inversePad(string s) { s = result + ";dummy" }
deprecated private string inversePad(string s) { s = result + ";dummy" }
private predicate sourceModel(string row) { any(SourceModelCsv s).row(inversePad(row)) }
deprecated private predicate sourceModel(string row) { any(SourceModelCsv s).row(inversePad(row)) }
private predicate sinkModel(string row) { any(SinkModelCsv s).row(inversePad(row)) }
deprecated private predicate sinkModel(string row) { any(SinkModelCsv s).row(inversePad(row)) }
private predicate summaryModel(string row) { any(SummaryModelCsv s).row(inversePad(row)) }
deprecated private predicate summaryModel(string row) {
any(SummaryModelCsv s).row(inversePad(row))
}
private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
deprecated private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
private predicate typeVariableModel(string row) { any(TypeVariableModelCsv s).row(inversePad(row)) }
deprecated private predicate typeVariableModel(string row) {
any(TypeVariableModelCsv s).row(inversePad(row))
}
private class DeprecationAdapter extends Unit {
abstract predicate sourceModel(string type, string path, string kind);
abstract predicate sinkModel(string type, string path, string kind);
abstract predicate summaryModel(string type, string path, string input, string output, string kind);
abstract predicate typeModel(string type1, string type2, string path);
abstract predicate typeVariableModel(string name, string path);
}
private class DeprecationAdapterImpl extends DeprecationAdapter {
deprecated override predicate sourceModel(string type, string path, string kind) {
exists(string row |
sourceModel(row) and
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = kind
)
}
deprecated override predicate sinkModel(string type, string path, string kind) {
exists(string row |
sinkModel(row) and
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = kind
)
}
deprecated override predicate summaryModel(
string type, string path, string input, string output, string kind
) {
exists(string row |
summaryModel(row) and
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = input and
row.splitAt(";", 3) = output and
row.splitAt(";", 4) = kind
)
}
deprecated override predicate typeModel(string type1, string type2, string path) {
exists(string row |
typeModel(row) and
row.splitAt(";", 0) = type1 and
row.splitAt(";", 1) = type2 and
row.splitAt(";", 2) = path
)
}
deprecated override predicate typeVariableModel(string name, string path) {
exists(string row |
typeVariableModel(row) and
row.splitAt(";", 0) = name and
row.splitAt(";", 1) = path
)
}
}
/** Holds if a source model exists for the given parameters. */
predicate sourceModel(string type, string path, string kind) {
exists(string row |
sourceModel(row) and
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = kind
)
predicate sourceModel(string type, string path, string kind, string model) {
any(DeprecationAdapter a).sourceModel(type, path, kind) and
model = "SourceModelCsv"
or
Extensions::sourceModel(type, path, kind)
exists(QlBuiltins::ExtensionId madId |
Extensions::sourceModel(type, path, kind, madId) and
model = "MaD:" + madId.toString()
)
}
/** Holds if a sink model exists for the given parameters. */
private predicate sinkModel(string type, string path, string kind) {
exists(string row |
sinkModel(row) and
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = kind
)
private predicate sinkModel(string type, string path, string kind, string model) {
any(DeprecationAdapter a).sinkModel(type, path, kind) and
model = "SinkModelCsv"
or
Extensions::sinkModel(type, path, kind)
exists(QlBuiltins::ExtensionId madId |
Extensions::sinkModel(type, path, kind, madId) and
model = "MaD:" + madId.toString()
)
}
/** Holds if a summary model `row` exists for the given parameters. */
private predicate summaryModel(string type, string path, string input, string output, string kind) {
exists(string row |
summaryModel(row) and
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = input and
row.splitAt(";", 3) = output and
row.splitAt(";", 4) = kind
)
private predicate summaryModel(
string type, string path, string input, string output, string kind, string model
) {
any(DeprecationAdapter a).summaryModel(type, path, input, output, kind) and
model = "SummaryModelCsv"
or
Extensions::summaryModel(type, path, input, output, kind)
exists(QlBuiltins::ExtensionId madId |
Extensions::summaryModel(type, path, input, output, kind, madId) and
model = "MaD:" + madId.toString()
)
}
/** Holds if a type model exists for the given parameters. */
<<<<<<< HEAD
predicate typeModel(string type1, string type2, string path) {
exists(string row |
typeModel(row) and
@@ -274,29 +349,29 @@ predicate typeModel(string type1, string type2, string path) {
row.splitAt(";", 1) = type2 and
row.splitAt(";", 2) = path
)
=======
private predicate typeModel(string type1, string type2, string path) {
any(DeprecationAdapter a).typeModel(type1, type2, path)
>>>>>>> main
or
Extensions::typeModel(type1, type2, path)
}
/** Holds if a type variable model exists for the given parameters. */
private predicate typeVariableModel(string name, string path) {
exists(string row |
typeVariableModel(row) and
row.splitAt(";", 0) = name and
row.splitAt(";", 1) = path
)
any(DeprecationAdapter a).typeVariableModel(name, path)
or
Extensions::typeVariableModel(name, path)
}
/**
* Holds if CSV rows involving `type` might be relevant for the analysis of this database.
* Holds if rows involving `type` might be relevant for the analysis of this database.
*/
predicate isRelevantType(string type) {
(
sourceModel(type, _, _) or
sinkModel(type, _, _) or
summaryModel(type, _, _, _, _) or
sourceModel(type, _, _, _) or
sinkModel(type, _, _, _) or
summaryModel(type, _, _, _, _, _) or
typeModel(_, type, _)
) and
(
@@ -313,26 +388,26 @@ predicate isRelevantType(string type) {
}
/**
* Holds if `type,path` is used in some CSV row.
* Holds if `type,path` is used in some row.
*/
pragma[nomagic]
predicate isRelevantFullPath(string type, string path) {
isRelevantType(type) and
(
sourceModel(type, path, _) or
sinkModel(type, path, _) or
summaryModel(type, path, _, _, _) or
sourceModel(type, path, _, _) or
sinkModel(type, path, _, _) or
summaryModel(type, path, _, _, _, _) or
typeModel(_, type, path)
)
}
/** A string from a CSV row that should be parsed as an access path. */
/** A string from a row that should be parsed as an access path. */
private predicate accessPathRange(string s) {
isRelevantFullPath(_, s)
or
exists(string type | isRelevantType(type) |
summaryModel(type, _, s, _, _) or
summaryModel(type, _, _, s, _)
summaryModel(type, _, s, _, _, _) or
summaryModel(type, _, _, s, _, _)
)
or
typeVariableModel(_, s)
@@ -543,7 +618,7 @@ private API::Node getNodeFromPath(string type, AccessPath path) {
pragma[nomagic]
private predicate typeStepModel(string type, AccessPath basePath, AccessPath output) {
summaryModel(type, basePath, "", output, "type")
summaryModel(type, basePath, "", output, "type", _)
}
pragma[nomagic]
@@ -618,36 +693,36 @@ module ModelOutput {
cached
private module Cached {
/**
* Holds if a CSV source model contributed `source` with the given `kind`.
* Holds if a source model contributed `source` with the given `kind`.
*/
cached
API::Node getASourceNode(string kind) {
API::Node getASourceNode(string kind, string model) {
exists(string type, string path |
sourceModel(type, path, kind) and
sourceModel(type, path, kind, model) and
result = getNodeFromPath(type, path)
)
}
/**
* Holds if a CSV sink model contributed `sink` with the given `kind`.
* Holds if a sink model contributed `sink` with the given `kind`.
*/
cached
API::Node getASinkNode(string kind) {
API::Node getASinkNode(string kind, string model) {
exists(string type, string path |
sinkModel(type, path, kind) and
sinkModel(type, path, kind, model) and
result = getNodeFromPath(type, path)
)
}
/**
* Holds if a relevant CSV summary exists for these parameters.
* Holds if a relevant summary exists for these parameters.
*/
cached
predicate relevantSummaryModel(
string type, string path, string input, string output, string kind
string type, string path, string input, string output, string kind, string model
) {
isRelevantType(type) and
summaryModel(type, path, input, output, kind)
summaryModel(type, path, input, output, kind, model)
}
/**
@@ -655,7 +730,7 @@ module ModelOutput {
*/
cached
predicate resolvedSummaryBase(string type, string path, Specific::InvokeNode baseNode) {
summaryModel(type, path, _, _, _) and
summaryModel(type, path, _, _, _, _) and
baseNode = getInvocationFromPath(type, path)
}
@@ -664,13 +739,13 @@ module ModelOutput {
*/
cached
predicate resolvedSummaryRefBase(string type, string path, API::Node baseNode) {
summaryModel(type, path, _, _, _) and
summaryModel(type, path, _, _, _, _) and
baseNode = getNodeFromPath(type, path)
}
/**
* Holds if `node` is seen as an instance of `type` due to a type definition
* contributed by a CSV model.
* contributed by a model.
*/
cached
API::Node getATypeNode(string type) { result = getNodeFromType(type) }
@@ -680,12 +755,22 @@ module ModelOutput {
import Specific::ModelOutputSpecific
private import codeql.mad.ModelValidation as SharedModelVal
/**
* Holds if a CSV source model contributed `source` with the given `kind`.
*/
API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) }
/**
* Holds if a CSV sink model contributed `sink` with the given `kind`.
*/
API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) }
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind) }
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }
predicate sinkKind(string kind) { sinkModel(_, _, kind) }
predicate sinkKind(string kind) { sinkModel(_, _, kind, _) }
predicate sourceKind(string kind) { sourceModel(_, _, kind) }
predicate sourceKind(string kind) { sourceModel(_, _, kind, _) }
}
private module KindVal = SharedModelVal::KindValidation<KindValConfig>;
@@ -694,25 +779,6 @@ module ModelOutput {
* Gets an error message relating to an invalid CSV row in a model.
*/
string getAWarning() {
// Check number of columns
exists(string row, string kind, int expectedArity, int actualArity |
any(SourceModelCsv csv).row(row) and kind = "source" and expectedArity = 3
or
any(SinkModelCsv csv).row(row) and kind = "sink" and expectedArity = 3
or
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 5
or
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 3
or
any(TypeVariableModelCsv csv).row(row) and kind = "type-variable" and expectedArity = 2
|
actualArity = count(row.indexOf(";")) + 1 and
actualArity != expectedArity and
result =
"CSV " + kind + " row should have " + expectedArity + " columns but has " + actualArity +
": " + row
)
or
// Check names and arguments of access path tokens
exists(AccessPath path, AccessPathToken token |
(isRelevantFullPath(_, path) or typeVariableModel(_, path)) and

View File

@@ -8,13 +8,15 @@
*
* The kind `remote` represents a general remote flow source.
*/
extensible predicate sourceModel(string type, string path, string kind);
extensible predicate sourceModel(
string type, string path, string kind, QlBuiltins::ExtensionId madId
);
/**
* Holds if the value at `(type, path)` should be seen as a sink
* of the given `kind`.
*/
extensible predicate sinkModel(string type, string path, string kind);
extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId);
/**
* Holds if in calls to `(type, path)`, the value referred to by `input`
@@ -23,7 +25,9 @@ extensible predicate sinkModel(string type, string path, string kind);
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
* respectively.
*/
extensible predicate summaryModel(string type, string path, string input, string output, string kind);
extensible predicate summaryModel(
string type, string path, string input, string output, string kind, QlBuiltins::ExtensionId madId
);
/**
* Holds if calls to `(type, path)` should be considered neutral. The meaning of this depends on the `kind`.

View File

@@ -0,0 +1,34 @@
extensions:
- addsTo:
pack: codeql/ruby-all
extensible: summaryModel
data:
- ['Pathname!', 'Method[new]', 'Argument[0]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[basename]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[cleanpath]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[dirname]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[each_filename]', 'Argument[self]', 'Argument[block].Parameter[0]', 'taint']
- ['Pathname', 'Method[expand_path]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[join]', 'Argument[self,any]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[parent]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[realpath]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[relative_path_from]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[sub]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[sub_ext]', 'Argument[self]', 'ReturnValue', 'taint']
- ['Pathname', 'Method[to_path]', 'Argument[self]', 'ReturnValue', 'taint']
- addsTo:
pack: codeql/ruby-all
extensible: typeModel
data:
- ['Pathname', 'Pathname', 'Method[+].ReturnValue']
- ['Pathname', 'Pathname', 'Method[/].ReturnValue']
- ['Pathname', 'Pathname', 'Method[basename].ReturnValue']
- ['Pathname', 'Pathname', 'Method[cleanpath].ReturnValue']
- ['Pathname', 'Pathname', 'Method[expand_path].ReturnValue']
- ['Pathname', 'Pathname', 'Method[join].ReturnValue']
- ['Pathname', 'Pathname', 'Method[realpath].ReturnValue']
- ['Pathname', 'Pathname', 'Method[relative_path_from].ReturnValue']
- ['Pathname', 'Pathname', 'Method[sub].ReturnValue']
- ['Pathname', 'Pathname', 'Method[sub_ext].ReturnValue']
- ['Pathname', 'Pathname', 'Method[to_path].ReturnValue']

View File

@@ -116,74 +116,4 @@ module Pathname {
override DataFlow::Node getAPermissionNode() { result = permissionArg }
}
/**
* Type summaries for the `Pathname` class, i.e. method calls that produce new
* `Pathname` instances.
*/
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// type1;type2;path
row =
[
// Pathname#+(path) : Pathname
"Pathname;Pathname;Method[+].ReturnValue",
// Pathname#/(path) : Pathname
"Pathname;Pathname;Method[/].ReturnValue",
// Pathname#basename(path) : Pathname
"Pathname;Pathname;Method[basename].ReturnValue",
// Pathname#cleanpath(path) : Pathname
"Pathname;Pathname;Method[cleanpath].ReturnValue",
// Pathname#expand_path(path) : Pathname
"Pathname;Pathname;Method[expand_path].ReturnValue",
// Pathname#join(path) : Pathname
"Pathname;Pathname;Method[join].ReturnValue",
// Pathname#realpath(path) : Pathname
"Pathname;Pathname;Method[realpath].ReturnValue",
// Pathname#relative_path_from(path) : Pathname
"Pathname;Pathname;Method[relative_path_from].ReturnValue",
// Pathname#sub(path) : Pathname
"Pathname;Pathname;Method[sub].ReturnValue",
// Pathname#sub_ext(path) : Pathname
"Pathname;Pathname;Method[sub_ext].ReturnValue",
// Pathname#to_path(path) : Pathname
"Pathname;Pathname;Method[to_path].ReturnValue",
]
}
}
/** Taint flow summaries for the `Pathname` class. */
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
// Pathname.new(path)
"Pathname!;Method[new];Argument[0];ReturnValue;taint",
// Pathname#dirname
"Pathname;Method[dirname];Argument[self];ReturnValue;taint",
// Pathname#each_filename
"Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
// Pathname#expand_path
"Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
// Pathname#join
"Pathname;Method[join];Argument[self,any];ReturnValue;taint",
// Pathname#parent
"Pathname;Method[parent];Argument[self];ReturnValue;taint",
// Pathname#realpath
"Pathname;Method[realpath];Argument[self];ReturnValue;taint",
// Pathname#relative_path_from
"Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
// Pathname#to_path
"Pathname;Method[to_path];Argument[self];ReturnValue;taint",
// Pathname#basename
"Pathname;Method[basename];Argument[self];ReturnValue;taint",
// Pathname#cleanpath
"Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
// Pathname#sub
"Pathname;Method[sub];Argument[self];ReturnValue;taint",
// Pathname#sub_ext
"Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
]
}
}
}

View File

@@ -16,7 +16,7 @@ module CodeInjection {
module FlowState {
/**
* Flow state used for normal tainted data, where an attacker might only control a substring.
* DEPRECATED: Use `Full()`
* DEPRECATED: Use `SubString()`
*/
deprecated DataFlow::FlowState substring() { result = "substring" }

View File

@@ -38,7 +38,7 @@ deprecated class Configuration extends DataFlow::Configuration {
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
DataFlow::FlowState stateTo
) {
defaultAdditionalTaintStep(nodeFrom, nodeTo) and
defaultAdditionalTaintStep(nodeFrom, nodeTo, _) and
// This is a taint step, so the flow state becomes `taint`.
stateFrom = [FlowState::data(), FlowState::taint()] and
stateTo = FlowState::taint()
@@ -57,7 +57,7 @@ private module Config implements DataFlow::StateConfigSig {
predicate isAdditionalFlowStep(
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
) {
defaultAdditionalTaintStep(nodeFrom, nodeTo) and
defaultAdditionalTaintStep(nodeFrom, nodeTo, _) and // TODO: propagate provenance
// This is a taint step, so the flow state becomes `taint`.
(
stateFrom = FlowState::Taint()

View File

@@ -0,0 +1,98 @@
/**
* Provides default sources, sinks, sanitizers, and flow steps for
* detecting insecure mass assignment, as well as extension points for adding your own.
*/
private import codeql.ruby.AST
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
private import codeql.ruby.dataflow.RemoteFlowSources
/**
* Provides default sources, sinks, sanitizers, and flow steps for
* detecting insecure mass assignment, as well as extension points for adding your own.
*/
module MassAssignment {
/**
* A data flow source for user input used for mass assignment.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for user input used for mass assignment.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for insecure mass assignment.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A call that permits arbitrary parameters to be used for mass assignment.
*/
abstract class MassPermit extends DataFlow::Node {
/** Gets the argument for the parameters to be permitted. */
abstract DataFlow::Node getParamsArgument();
/** Gets the result node of the permitted parameters. */
abstract DataFlow::Node getPermittedParamsResult();
}
private class RemoteSource extends Source instanceof RemoteFlowSource { }
/** A call to `permit!`, which permits each key of its receiver. */
private class PermitBangCall extends MassPermit instanceof DataFlow::CallNode {
PermitBangCall() { this.(DataFlow::CallNode).getMethodName() = "permit!" }
override DataFlow::Node getParamsArgument() { result = this.(DataFlow::CallNode).getReceiver() }
override DataFlow::Node getPermittedParamsResult() {
result = this
or
result.(DataFlow::PostUpdateNode).getPreUpdateNode() = this.getParamsArgument()
}
}
/** Holds if `h` is an empty hash or contains an empty hash at one if its (possibly nested) values. */
private predicate hasEmptyHash(ExprCfgNode e) {
e instanceof ExprNodes::HashLiteralCfgNode and
not exists(e.(ExprNodes::HashLiteralCfgNode).getAKeyValuePair())
or
hasEmptyHash(e.(ExprNodes::HashLiteralCfgNode).getAKeyValuePair().getValue())
or
hasEmptyHash(e.(ExprNodes::PairCfgNode).getValue())
or
hasEmptyHash(e.(ExprNodes::ArrayLiteralCfgNode).getAnArgument())
}
/** A call to `permit` that fully specifies the permitted parameters. */
private class PermitCallSanitizer extends Sanitizer, DataFlow::CallNode {
PermitCallSanitizer() {
this.getMethodName() = "permit" and
not hasEmptyHash(this.getArgument(_).getExprNode())
}
}
/** A call to `permit` that uses an empty hash, which allows arbitrary keys to be specified. */
private class PermitCallMassPermit extends MassPermit instanceof DataFlow::CallNode {
PermitCallMassPermit() {
this.(DataFlow::CallNode).getMethodName() = "permit" and
hasEmptyHash(this.(DataFlow::CallNode).getArgument(_).getExprNode())
}
override DataFlow::Node getParamsArgument() { result = this.(DataFlow::CallNode).getReceiver() }
override DataFlow::Node getPermittedParamsResult() { result = this }
}
/** A call to `to_unsafe_h`, which allows arbitrary parameter. */
private class ToUnsafeHashCall extends MassPermit instanceof DataFlow::CallNode {
ToUnsafeHashCall() { this.(DataFlow::CallNode).getMethodName() = "to_unsafe_h" }
override DataFlow::Node getParamsArgument() { result = this.(DataFlow::CallNode).getReceiver() }
override DataFlow::Node getPermittedParamsResult() { result = this }
}
}

View File

@@ -0,0 +1,65 @@
/**
* Provides a taint tracking configuration for reasoning about insecure mass assignment.
*/
private import codeql.ruby.AST
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
private import codeql.ruby.dataflow.RemoteFlowSources
private import MassAssignmentCustomizations
private module FlowState {
private newtype TState =
TUnpermitted() or
TPermitted()
/** A flow state used to distinguish whether arbitrary user parameters have been permitted to be used for mass assignment. */
class State extends TState {
string toString() {
this = TUnpermitted() and result = "unpermitted"
or
this = TPermitted() and result = "permitted"
}
}
/** A flow state used for user parameters for which arbitrary parameters have not been permitted to use for mass assignment. */
class Unpermitted extends State, TUnpermitted { }
/** A flow state used for user parameters for which arbitrary parameters have been permitted to use for mass assignment. */
class Permitted extends State, TPermitted { }
}
/** A flow configuration for reasoning about insecure mass assignment. */
private module Config implements DataFlow::StateConfigSig {
class FlowState = FlowState::State;
predicate isSource(DataFlow::Node node, FlowState state) {
node instanceof MassAssignment::Source and
state instanceof FlowState::Unpermitted
}
predicate isSink(DataFlow::Node node, FlowState state) {
node instanceof MassAssignment::Sink and
state instanceof FlowState::Permitted
}
predicate isBarrierIn(DataFlow::Node node, FlowState state) { isSource(node, state) }
predicate isBarrierOut(DataFlow::Node node, FlowState state) { isSink(node, state) }
predicate isBarrier(DataFlow::Node node) { node instanceof MassAssignment::Sanitizer }
predicate isAdditionalFlowStep(
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
) {
exists(MassAssignment::MassPermit permit |
node1 = permit.getParamsArgument() and
state1 instanceof FlowState::Unpermitted and
node2 = permit.getPermittedParamsResult() and
state2 instanceof FlowState::Permitted
)
}
}
/** Taint tracking for reasoning about user input used for mass assignment. */
module MassAssignmentFlow = TaintTracking::GlobalWithState<Config>;

View File

@@ -46,12 +46,6 @@ private module UnsafeCodeConstructionConfig implements DataFlow::ConfigSig {
// override to require the path doesn't have unmatched return steps
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet set) {
// allow implicit reads of array elements
isSink(node) and
set.isElementOfTypeOrUnknown("int")
}
}
/**

View File

@@ -49,12 +49,6 @@ private module UnsafeShellCommandConstructionConfig implements DataFlow::ConfigS
// override to require the path doesn't have unmatched return steps
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet set) {
// allow implicit reads of array elements
isSink(node) and
set.isElementOfTypeOrUnknown("int")
}
}
/**

View File

@@ -83,6 +83,12 @@ module UrlRedirect {
*/
class StringConstCompareAsSanitizer extends Sanitizer, StringConstCompareBarrier { }
/**
* A string concatenation against a constant list, considered as a sanitizer-guard.
*/
class StringConstArrayInclusionAsSanitizer extends Sanitizer, StringConstArrayInclusionCallBarrier
{ }
/**
* Some methods will propagate taint to their return values.
* Here we cover a few common ones related to `ActionController::Parameters`.

View File

@@ -217,7 +217,15 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
predicate return = FlowSummaryImpl::Private::SummaryComponent::return/0;
// Callables
class SummarizedCallable = FlowSummaryImpl::Private::SummarizedCallableImpl;
class SummarizedCallable instanceof FlowSummaryImpl::Private::SummarizedCallableImpl {
string toString() { result = super.toString() }
predicate propagatesFlow(
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
) {
super.propagatesFlow(input, output, preservesValue, _)
}
}
// Relating nodes to summaries
Node argumentOf(Node call, SummaryComponent arg, boolean isPostUpdate) {