Skip to content

Commit d2eb436

Browse files
committed
Ruby: Add alert provenance plumbing.
1 parent da3cfbc commit d2eb436

File tree

7 files changed

+151
-90
lines changed

7 files changed

+151
-90
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
}
@@ -596,25 +597,28 @@ private module Cached {
596597
* data flow.
597598
*/
598599
cached
599-
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
600-
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
601-
or
602-
exists(SsaImpl::DefinitionExt def |
603-
// captured variables are handled by the shared `VariableCapture` library
604-
not def instanceof VariableCapture::CapturedSsaDefinitionExt
605-
|
606-
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
600+
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
601+
(
602+
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
607603
or
608-
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
609-
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
604+
exists(SsaImpl::DefinitionExt def |
605+
// captured variables are handled by the shared `VariableCapture` library
606+
not def instanceof VariableCapture::CapturedSsaDefinitionExt
607+
|
608+
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)
609+
or
610+
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
611+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
612+
or
613+
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
614+
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
615+
)
610616
or
611-
LocalFlow::localFlowSsaInputFromRead(def, nodeFrom, nodeTo) and
612-
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
613-
)
617+
VariableCapture::valueStep(nodeFrom, nodeTo)
618+
) and
619+
model = ""
614620
or
615-
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _)
616-
or
617-
VariableCapture::valueStep(nodeFrom, nodeTo)
621+
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _, model)
618622
}
619623

620624
/** This is the local flow predicate that is exposed. */
@@ -646,7 +650,8 @@ private module Cached {
646650
or
647651
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo)
648652
or
649-
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, any(LibraryCallableToIncludeInTypeTracking c))
653+
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, any(LibraryCallableToIncludeInTypeTracking c),
654+
_)
650655
}
651656

652657
/** Holds if `n` wraps an SSA definition without ingoing flow. */
@@ -742,7 +747,7 @@ private module Cached {
742747
// external model data. This, unfortunately, does not included any field names used
743748
// in models defined in QL code.
744749
exists(string input, string output |
745-
ModelOutput::relevantSummaryModel(_, _, input, output, _)
750+
ModelOutput::relevantSummaryModel(_, _, input, output, _, _)
746751
|
747752
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
748753
)
@@ -2178,6 +2183,14 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
21782183
/** Extra data-flow steps needed for lambda flow analysis. */
21792184
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
21802185

2186+
predicate knownSourceModel(Node source, string model) {
2187+
source = ModelOutput::getASourceNode(_, model).asSource()
2188+
}
2189+
2190+
predicate knownSinkModel(Node sink, string model) {
2191+
sink = ModelOutput::getASinkNode(_, model).asSink()
2192+
}
2193+
21812194
/**
21822195
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
21832196
* 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)