Skip to content

Commit 6991f54

Browse files
committed
Ruby: Add alert provenance plumbing.
1 parent 82e6fbb commit 6991f54

File tree

9 files changed

+167
-96
lines changed

9 files changed

+167
-96
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
@@ -244,10 +244,11 @@ module LocalFlow {
244244
}
245245

246246
predicate flowSummaryLocalStep(
247-
FlowSummaryNode nodeFrom, FlowSummaryNode nodeTo, FlowSummaryImpl::Public::SummarizedCallable c
247+
FlowSummaryNode nodeFrom, FlowSummaryNode nodeTo, FlowSummaryImpl::Public::SummarizedCallable c,
248+
string model
248249
) {
249250
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.getSummaryNode(),
250-
nodeTo.getSummaryNode(), true) and
251+
nodeTo.getSummaryNode(), true, model) and
251252
c = nodeFrom.getSummarizedCallable()
252253
}
253254

@@ -271,7 +272,7 @@ module LocalFlow {
271272
node1 =
272273
unique(FlowSummaryNode n1 |
273274
FlowSummaryImpl::Private::Steps::summaryLocalStep(n1.getSummaryNode(),
274-
node2.(FlowSummaryNode).getSummaryNode(), true)
275+
node2.(FlowSummaryNode).getSummaryNode(), true, _)
275276
)
276277
}
277278
}
@@ -606,25 +607,28 @@ private module Cached {
606607
* data flow.
607608
*/
608609
cached
609-
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
610-
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
611-
or
612-
exists(SsaImpl::DefinitionExt def |
613-
// captured variables are handled by the shared `VariableCapture` library
614-
not def instanceof VariableCapture::CapturedSsaDefinitionExt
615-
|
616-
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
610+
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
611+
(
612+
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
617613
or
618-
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
619-
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
614+
exists(SsaImpl::DefinitionExt def |
615+
// captured variables are handled by the shared `VariableCapture` library
616+
not def instanceof VariableCapture::CapturedSsaDefinitionExt
617+
|
618+
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
619+
or
620+
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
621+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
622+
or
623+
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
624+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
625+
)
620626
or
621-
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
622-
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
623-
)
627+
VariableCapture::valueStep(nodeFrom, nodeTo)
628+
) and
629+
model = ""
624630
or
625-
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _)
626-
or
627-
VariableCapture::valueStep(nodeFrom, nodeTo)
631+
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _, model)
628632
}
629633

630634
/** This is the local flow predicate that is exposed. */
@@ -656,7 +660,8 @@ private module Cached {
656660
or
657661
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo)
658662
or
659-
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, any(LibraryCallableToIncludeInTypeTracking c))
663+
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, any(LibraryCallableToIncludeInTypeTracking c),
664+
_)
660665
}
661666

662667
/** Holds if `n` wraps an SSA definition without ingoing flow. */
@@ -752,7 +757,7 @@ private module Cached {
752757
// external model data. This, unfortunately, does not included any field names used
753758
// in models defined in QL code.
754759
exists(string input, string output |
755-
ModelOutput::relevantSummaryModel(_, _, input, output, _)
760+
ModelOutput::relevantSummaryModel(_, _, input, output, _, _)
756761
|
757762
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
758763
)
@@ -2241,6 +2246,14 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
22412246
/** Extra data-flow steps needed for lambda flow analysis. */
22422247
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
22432248

2249+
predicate knownSourceModel(Node source, string model) {
2250+
source = ModelOutput::getASourceNode(_, model).asSource()
2251+
}
2252+
2253+
predicate knownSinkModel(Node sink, string model) {
2254+
sink = ModelOutput::getASinkNode(_, model).asSink()
2255+
}
2256+
22442257
/**
22452258
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
22462259
* 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)