Skip to content

Commit 6b8c216

Browse files
committed
Ruby: Add alert provenance plumbing.
1 parent 75e8afc commit 6b8c216

File tree

6 files changed

+149
-88
lines changed

6 files changed

+149
-88
lines changed

ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,22 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari
3232
* DEPRECATED: Use `propagatesFlow` instead.
3333
*/
3434
deprecated predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
35-
this.propagatesFlow(input, output, preservesValue)
35+
this.propagatesFlow(input, output, preservesValue, _)
3636
}
3737

38+
override predicate propagatesFlow(
39+
string input, string output, boolean preservesValue, string model
40+
) {
41+
this.propagatesFlow(input, output, preservesValue) and model = ""
42+
}
43+
44+
/**
45+
* Holds if data may flow from `input` to `output` through this callable.
46+
*
47+
* `preservesValue` indicates whether this is a value-preserving step or a taint-step.
48+
*/
49+
predicate propagatesFlow(string input, string output, boolean preservesValue) { none() }
50+
3851
/**
3952
* Gets the synthesized parameter that results from an input specification
4053
* that starts with `Argument[s]` for this library callable.
@@ -100,7 +113,9 @@ private module LibraryCallbackSummaries {
100113
libraryCallHasLambdaArg(result.getAControlFlowNode(), _)
101114
}
102115

103-
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
116+
override predicate propagatesFlow(
117+
string input, string output, boolean preservesValue, string model
118+
) {
104119
(
105120
input = "Argument[block]" and
106121
output = "Argument[block].Parameter[lambda-self]"
@@ -111,7 +126,8 @@ private module LibraryCallbackSummaries {
111126
output = "Argument[" + i + "].Parameter[lambda-self]"
112127
)
113128
) and
114-
preservesValue = true
129+
preservesValue = true and
130+
model = "heuristic-callback"
115131
}
116132
}
117133
}

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,11 @@ module LocalFlow {
237237
}
238238

239239
predicate flowSummaryLocalStep(
240-
FlowSummaryNode nodeFrom, FlowSummaryNode nodeTo, FlowSummaryImpl::Public::SummarizedCallable c
240+
FlowSummaryNode nodeFrom, FlowSummaryNode nodeTo, FlowSummaryImpl::Public::SummarizedCallable c,
241+
string model
241242
) {
242243
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.getSummaryNode(),
243-
nodeTo.getSummaryNode(), true) and
244+
nodeTo.getSummaryNode(), true, model) and
244245
c = nodeFrom.getSummarizedCallable()
245246
}
246247

@@ -264,7 +265,7 @@ module LocalFlow {
264265
node1 =
265266
unique(FlowSummaryNode n1 |
266267
FlowSummaryImpl::Private::Steps::summaryLocalStep(n1.getSummaryNode(),
267-
node2.(FlowSummaryNode).getSummaryNode(), true)
268+
node2.(FlowSummaryNode).getSummaryNode(), true, _)
268269
)
269270
}
270271
}
@@ -588,25 +589,28 @@ private module Cached {
588589
* data flow.
589590
*/
590591
cached
591-
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
592-
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
593-
or
594-
exists(SsaImpl::DefinitionExt def |
595-
// captured variables are handled by the shared `VariableCapture` library
596-
not def instanceof VariableCapture::CapturedSsaDefinitionExt
597-
|
598-
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
592+
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
593+
(
594+
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
599595
or
600-
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
601-
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
596+
exists(SsaImpl::DefinitionExt def |
597+
// captured variables are handled by the shared `VariableCapture` library
598+
not def instanceof VariableCapture::CapturedSsaDefinitionExt
599+
|
600+
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
601+
or
602+
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
603+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
604+
or
605+
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
606+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
607+
)
602608
or
603-
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
604-
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
605-
)
609+
VariableCapture::valueStep(nodeFrom, nodeTo)
610+
) and
611+
model = ""
606612
or
607-
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _)
608-
or
609-
VariableCapture::valueStep(nodeFrom, nodeTo)
613+
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _, model)
610614
}
611615

612616
/** This is the local flow predicate that is exposed. */
@@ -638,7 +642,8 @@ private module Cached {
638642
or
639643
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo)
640644
or
641-
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, any(LibraryCallableToIncludeInTypeTracking c))
645+
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, any(LibraryCallableToIncludeInTypeTracking c),
646+
_)
642647
}
643648

644649
/** Holds if `n` wraps an SSA definition without ingoing flow. */
@@ -734,7 +739,7 @@ private module Cached {
734739
// external model data. This, unfortunately, does not included any field names used
735740
// in models defined in QL code.
736741
exists(string input, string output |
737-
ModelOutput::relevantSummaryModel(_, _, input, output, _)
742+
ModelOutput::relevantSummaryModel(_, _, input, output, _, _)
738743
|
739744
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
740745
)
@@ -2158,6 +2163,14 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
21582163
/** Extra data-flow steps needed for lambda flow analysis. */
21592164
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
21602165

2166+
predicate knownSourceModel(Node source, string model) {
2167+
source = ModelOutput::getASourceNode(_, model).asSource()
2168+
}
2169+
2170+
predicate knownSinkModel(Node sink, string model) {
2171+
sink = ModelOutput::getASinkNode(_, model).asSink()
2172+
}
2173+
21612174
/**
21622175
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
21632176
* side-effect, resulting in a summary from `p` to itself.

ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPrivate.qll

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -77,38 +77,41 @@ private module Cached {
7777
* in all global taint flow configurations.
7878
*/
7979
cached
80-
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
81-
// value of `case` expression into variables in patterns
82-
exists(
83-
CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprCfgNode value,
84-
CfgNodes::ExprNodes::InClauseCfgNode clause, Ssa::Definition def
85-
|
86-
nodeFrom.asExpr() = value and
87-
value = case.getValue() and
88-
clause = case.getBranch(_) and
89-
def = nodeTo.(SsaDefinitionExtNode).getDefinitionExt() and
90-
def.getControlFlowNode() = variablesInPattern(clause.getPattern()) and
91-
not LocalFlow::ssaDefAssigns(def, value)
92-
)
93-
or
94-
// operation involving `nodeFrom`
95-
exists(CfgNodes::ExprNodes::OperationCfgNode op |
96-
op = nodeTo.asExpr() and
97-
op.getAnOperand() = nodeFrom.asExpr() and
98-
not op.getExpr() =
99-
any(Expr e |
100-
// included in normal data-flow
101-
e instanceof AssignExpr or
102-
e instanceof BinaryLogicalOperation or
103-
// has flow summary
104-
e instanceof SplatExpr
105-
)
106-
)
80+
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) {
81+
(
82+
// value of `case` expression into variables in patterns
83+
exists(
84+
CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprCfgNode value,
85+
CfgNodes::ExprNodes::InClauseCfgNode clause, Ssa::Definition def
86+
|
87+
nodeFrom.asExpr() = value and
88+
value = case.getValue() and
89+
clause = case.getBranch(_) and
90+
def = nodeTo.(SsaDefinitionExtNode).getDefinitionExt() and
91+
def.getControlFlowNode() = variablesInPattern(clause.getPattern()) and
92+
not LocalFlow::ssaDefAssigns(def, value)
93+
)
94+
or
95+
// operation involving `nodeFrom`
96+
exists(CfgNodes::ExprNodes::OperationCfgNode op |
97+
op = nodeTo.asExpr() and
98+
op.getAnOperand() = nodeFrom.asExpr() and
99+
not op.getExpr() =
100+
any(Expr e |
101+
// included in normal data-flow
102+
e instanceof AssignExpr or
103+
e instanceof BinaryLogicalOperation or
104+
// has flow summary
105+
e instanceof SplatExpr
106+
)
107+
)
108+
) and
109+
model = ""
107110
or
108111
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
109-
nodeTo.(FlowSummaryNode).getSummaryNode(), false)
112+
nodeTo.(FlowSummaryNode).getSummaryNode(), false, model)
110113
or
111-
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo)
114+
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo) and model = "AdditionalTaintStep"
112115
or
113116
// Although flow through collections is modeled precisely using stores/reads, we still
114117
// allow flow out of a _tainted_ collection. This is needed in order to support taint-
@@ -119,7 +122,8 @@ private module Cached {
119122
c.isKnownOrUnknownElement(_)
120123
or
121124
c.isAnyElement()
122-
)
125+
) and
126+
model = ""
123127
}
124128

125129
cached
@@ -136,7 +140,7 @@ private module Cached {
136140
cached
137141
predicate localTaintStepCached(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
138142
DataFlow::localFlowStep(nodeFrom, nodeTo) or
139-
defaultAdditionalTaintStep(nodeFrom, nodeTo) or
143+
defaultAdditionalTaintStep(nodeFrom, nodeTo, _) or
140144
// Simple flow through library code is included in the exposed local
141145
// step relation, even though flow is technically inter-procedural
142146
summaryThroughStepTaint(nodeFrom, nodeTo, _)

ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ private class SummarizedCallableFromModel extends SummarizedCallable {
3737
string path;
3838

3939
SummarizedCallableFromModel() {
40-
ModelOutput::relevantSummaryModel(type, path, _, _, _) and
40+
ModelOutput::relevantSummaryModel(type, path, _, _, _, _) and
4141
this = type + ";" + path
4242
}
4343

@@ -48,8 +48,10 @@ private class SummarizedCallableFromModel extends SummarizedCallable {
4848
)
4949
}
5050

51-
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
52-
exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind) |
51+
override predicate propagatesFlow(
52+
string input, string output, boolean preservesValue, string model
53+
) {
54+
exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind, model) |
5355
kind = "value" and
5456
preservesValue = true
5557
or

0 commit comments

Comments
 (0)