Skip to content

Commit e75f7dd

Browse files
authored
Merge pull request github#15540 from hvitved/variable-capture-overwrite
2 parents 566351a + 37d7741 commit e75f7dd

File tree

9 files changed

+82
-18
lines changed

9 files changed

+82
-18
lines changed

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ private predicate captureReadStep(Node node1, CapturedVariableContent c, Node no
178178
CaptureFlow::readStep(asClosureNode(node1), c.getVariable(), asClosureNode(node2))
179179
}
180180

181+
private predicate captureClearsContent(Node node, CapturedVariableContent c) {
182+
CaptureFlow::clearsContent(asClosureNode(node), c.getVariable())
183+
}
184+
181185
predicate captureValueStep(Node node1, Node node2) {
182186
CaptureFlow::localFlowStep(asClosureNode(node1), asClosureNode(node2))
183187
}
@@ -311,6 +315,8 @@ predicate clearsContent(Node n, ContentSet c) {
311315
)
312316
or
313317
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
318+
or
319+
captureClearsContent(n, c)
314320
}
315321

316322
/**

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,8 @@ predicate clearsContent(Node n, ContentSet c) {
979979
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
980980
or
981981
dictSplatParameterNodeClearStep(n, c)
982+
or
983+
VariableCapture::clearsContent(n, c)
982984
}
983985

984986
/**

python/ql/lib/semmle/python/dataflow/new/internal/VariableCapture.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ predicate readStep(Node nodeFrom, CapturedVariableContent c, Node nodeTo) {
144144
Flow::readStep(asClosureNode(nodeFrom), c.getVariable(), asClosureNode(nodeTo))
145145
}
146146

147+
predicate clearsContent(Node node, CapturedVariableContent c) {
148+
Flow::clearsContent(asClosureNode(node), c.getVariable())
149+
}
150+
147151
predicate valueStep(Node nodeFrom, Node nodeTo) {
148152
Flow::localFlowStep(asClosureNode(nodeFrom), asClosureNode(nodeTo))
149153
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,10 @@ module VariableCapture {
453453
Flow::localFlowStep(asClosureNode(node1), asClosureNode(node2))
454454
}
455455

456+
predicate clearsContent(Node node, Content::CapturedVariableContent c) {
457+
Flow::clearsContent(asClosureNode(node), c.getVariable())
458+
}
459+
456460
class CapturedSsaDefinitionExt extends SsaImpl::DefinitionExt {
457461
CapturedSsaDefinitionExt() { this.getSourceVariable() instanceof CapturedVariable }
458462
}
@@ -1930,6 +1934,8 @@ predicate clearsContent(Node n, ContentSet c) {
19301934
c.isKnownOrUnknownElement(TKnownElementContent(cv)) and
19311935
cv.isSymbol(name)
19321936
)
1937+
or
1938+
VariableCapture::clearsContent(n, any(Content::CapturedVariableContent v | c.isSingleton(v)))
19331939
}
19341940

19351941
/**

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

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -143,23 +143,23 @@ private predicate hasCapturedRead(Variable v, Cfg::CfgScope scope) {
143143
}
144144

145145
/**
146-
* Holds if `v` is written inside basic block `bb`, which is in the immediate
147-
* outer scope of `scope`.
146+
* Holds if `v` is written inside basic block `bb` at index `i`, which is in
147+
* the immediate outer scope of `scope`.
148148
*/
149149
pragma[noinline]
150-
private predicate variableWriteInOuterScope(Cfg::BasicBlock bb, LocalVariable v, Cfg::CfgScope scope) {
151-
SsaInput::variableWrite(bb, _, v, _) and
150+
private predicate variableWriteInOuterScope(
151+
Cfg::BasicBlock bb, int i, LocalVariable v, Cfg::CfgScope scope
152+
) {
153+
SsaInput::variableWrite(bb, i, v, _) and
152154
scope.getOuterCfgScope() = bb.getScope()
153155
}
154156

155157
pragma[noinline]
156-
private predicate proceedsVariableWriteWithCapturedRead(
157-
Cfg::BasicBlock bb, LocalVariable v, Cfg::CfgScope scope
158+
private predicate hasVariableWriteWithCapturedRead(
159+
Cfg::BasicBlock bb, int i, LocalVariable v, Cfg::CfgScope scope
158160
) {
159161
hasCapturedRead(v, scope) and
160-
variableWriteInOuterScope(bb, v, scope)
161-
or
162-
proceedsVariableWriteWithCapturedRead(bb.getAPredecessor(), v, scope)
162+
variableWriteInOuterScope(bb, i, v, scope)
163163
}
164164

165165
/**
@@ -168,7 +168,10 @@ private predicate proceedsVariableWriteWithCapturedRead(
168168
*/
169169
private predicate capturedCallRead(CallCfgNode call, Cfg::BasicBlock bb, int i, LocalVariable v) {
170170
exists(Cfg::CfgScope scope |
171-
proceedsVariableWriteWithCapturedRead(bb, v, scope) and
171+
(
172+
hasVariableWriteWithCapturedRead(bb, any(int j | j < i), v, scope) or
173+
hasVariableWriteWithCapturedRead(bb.getAPredecessor+(), _, v, scope)
174+
) and
172175
call = bb.getNode(i)
173176
|
174177
// If the read happens inside a block, we restrict to the call that
@@ -199,23 +202,23 @@ private predicate hasCapturedWrite(Variable v, Cfg::CfgScope scope) {
199202
}
200203

201204
/**
202-
* Holds if `v` is read inside basic block `bb`, which is in the immediate
203-
* outer scope of `scope`.
205+
* Holds if `v` is read inside basic block `bb` at index `i`, which is in the
206+
* immediate outer scope of `scope`.
204207
*/
205208
pragma[noinline]
206209
private predicate variableReadActualInOuterScope(
207-
Cfg::BasicBlock bb, LocalVariable v, Cfg::CfgScope scope
210+
Cfg::BasicBlock bb, int i, LocalVariable v, Cfg::CfgScope scope
208211
) {
209-
variableReadActual(bb, _, v) and
212+
variableReadActual(bb, i, v) and
210213
bb.getScope() = scope.getOuterCfgScope()
211214
}
212215

213216
pragma[noinline]
214217
private predicate hasVariableReadWithCapturedWrite(
215-
Cfg::BasicBlock bb, LocalVariable v, Cfg::CfgScope scope
218+
Cfg::BasicBlock bb, int i, LocalVariable v, Cfg::CfgScope scope
216219
) {
217220
hasCapturedWrite(v, scope) and
218-
variableReadActualInOuterScope(bb, v, scope)
221+
variableReadActualInOuterScope(bb, i, v, scope)
219222
}
220223

221224
pragma[noinline]
@@ -324,7 +327,11 @@ private module Cached {
324327
cached
325328
predicate capturedCallWrite(CallCfgNode call, Cfg::BasicBlock bb, int i, LocalVariable v) {
326329
exists(Cfg::CfgScope scope |
327-
hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and
330+
(
331+
hasVariableReadWithCapturedWrite(bb, any(int j | j > i), v, scope)
332+
or
333+
hasVariableReadWithCapturedWrite(bb.getASuccessor+(), _, v, scope)
334+
) and
328335
call = bb.getNode(i)
329336
|
330337
// If the write happens inside a block, we restrict to the call that

ruby/ql/test/library-tests/dataflow/global/Flow.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ edges
114114
| captured_variables.rb:187:18:187:19 | self [@x] | captured_variables.rb:187:18:187:19 | @x | provenance | |
115115
| captured_variables.rb:193:1:193:1 | [post] c [@x] | captured_variables.rb:194:1:194:1 | c [@x] | provenance | |
116116
| captured_variables.rb:194:1:194:1 | c [@x] | captured_variables.rb:185:5:189:7 | self in baz [@x] | provenance | |
117+
| captured_variables.rb:197:9:197:17 | call to taint | captured_variables.rb:199:10:199:10 | x | provenance | |
118+
| captured_variables.rb:206:13:206:21 | call to taint | captured_variables.rb:208:14:208:14 | x | provenance | |
117119
| instance_variables.rb:10:19:10:19 | x | instance_variables.rb:11:18:11:18 | x | provenance | |
118120
| instance_variables.rb:11:18:11:18 | x | instance_variables.rb:11:9:11:14 | [post] self [@field] | provenance | |
119121
| instance_variables.rb:13:5:15:7 | self in get_field [@field] | instance_variables.rb:14:16:14:21 | self [@field] | provenance | |
@@ -368,6 +370,10 @@ nodes
368370
| captured_variables.rb:187:18:187:19 | self [@x] | semmle.label | self [@x] |
369371
| captured_variables.rb:193:1:193:1 | [post] c [@x] | semmle.label | [post] c [@x] |
370372
| captured_variables.rb:194:1:194:1 | c [@x] | semmle.label | c [@x] |
373+
| captured_variables.rb:197:9:197:17 | call to taint | semmle.label | call to taint |
374+
| captured_variables.rb:199:10:199:10 | x | semmle.label | x |
375+
| captured_variables.rb:206:13:206:21 | call to taint | semmle.label | call to taint |
376+
| captured_variables.rb:208:14:208:14 | x | semmle.label | x |
371377
| instance_variables.rb:10:19:10:19 | x | semmle.label | x |
372378
| instance_variables.rb:11:9:11:14 | [post] self [@field] | semmle.label | [post] self [@field] |
373379
| instance_variables.rb:11:18:11:18 | x | semmle.label | x |
@@ -575,6 +581,8 @@ subpaths
575581
| captured_variables.rb:154:14:154:15 | @x | captured_variables.rb:147:10:147:18 | call to taint | captured_variables.rb:154:14:154:15 | @x | $@ | captured_variables.rb:147:10:147:18 | call to taint | call to taint |
576582
| captured_variables.rb:169:18:169:19 | @x | captured_variables.rb:160:14:160:22 | call to taint | captured_variables.rb:169:18:169:19 | @x | $@ | captured_variables.rb:160:14:160:22 | call to taint | call to taint |
577583
| captured_variables.rb:187:18:187:19 | @x | captured_variables.rb:178:14:178:22 | call to taint | captured_variables.rb:187:18:187:19 | @x | $@ | captured_variables.rb:178:14:178:22 | call to taint | call to taint |
584+
| captured_variables.rb:199:10:199:10 | x | captured_variables.rb:197:9:197:17 | call to taint | captured_variables.rb:199:10:199:10 | x | $@ | captured_variables.rb:197:9:197:17 | call to taint | call to taint |
585+
| captured_variables.rb:208:14:208:14 | x | captured_variables.rb:206:13:206:21 | call to taint | captured_variables.rb:208:14:208:14 | x | $@ | captured_variables.rb:206:13:206:21 | call to taint | call to taint |
578586
| instance_variables.rb:20:10:20:13 | @foo | instance_variables.rb:19:12:19:21 | call to taint | instance_variables.rb:20:10:20:13 | @foo | $@ | instance_variables.rb:19:12:19:21 | call to taint | call to taint |
579587
| instance_variables.rb:36:10:36:33 | call to get_field | instance_variables.rb:36:14:36:22 | call to taint | instance_variables.rb:36:10:36:33 | call to get_field | $@ | instance_variables.rb:36:14:36:22 | call to taint | call to taint |
580588
| instance_variables.rb:39:6:39:33 | call to get_field | instance_variables.rb:39:14:39:22 | call to taint | instance_variables.rb:39:6:39:33 | call to get_field | $@ | instance_variables.rb:39:14:39:22 | call to taint | call to taint |

ruby/ql/test/library-tests/dataflow/global/captured_variables.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,25 @@ def baz
192192
c = CaptureInstanceSelf2.new
193193
c.foo
194194
c.baz
195+
196+
class CaptureOverwrite
197+
x = taint(16)
198+
199+
sink(x) # $ hasValueFlow=16
200+
201+
x = nil
202+
203+
sink(x)
204+
205+
fn = -> {
206+
x = taint(17)
207+
208+
sink(x) # $ hasValueFlow=17
209+
210+
x = nil
211+
212+
sink(x)
213+
}
214+
215+
fn.call()
216+
end

ruby/ql/test/library-tests/variables/ssa.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ definition
9090
| scopes.rb:2:9:6:3 | <captured entry> self | scopes.rb:1:1:49:4 | self |
9191
| scopes.rb:4:4:4:4 | a | scopes.rb:4:4:4:4 | a |
9292
| scopes.rb:7:1:7:1 | a | scopes.rb:7:1:7:1 | a |
93-
| scopes.rb:9:1:18:3 | <captured exit> a | scopes.rb:7:1:7:1 | a |
9493
| scopes.rb:9:9:18:3 | <captured entry> a | scopes.rb:7:1:7:1 | a |
9594
| scopes.rb:9:9:18:3 | <captured entry> self | scopes.rb:1:1:49:4 | self |
9695
| scopes.rb:11:4:11:4 | a | scopes.rb:7:1:7:1 | a |

shared/dataflow/codeql/dataflow/VariableCapture.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ signature module OutputSig<LocationSig Location, InputSig<Location> I> {
232232

233233
/** Holds if this-to-this summaries are expected for `c`. */
234234
predicate heuristicAllowInstanceParameterReturnInSelf(I::Callable c);
235+
236+
/** Holds if captured variable `v` is cleared at `node`. */
237+
predicate clearsContent(ClosureNode node, I::CapturedVariable v);
235238
}
236239

237240
/**
@@ -959,4 +962,11 @@ module Flow<LocationSig Location, InputSig<Location> Input> implements OutputSig
959962
)
960963
)
961964
}
965+
966+
predicate clearsContent(ClosureNode node, CapturedVariable v) {
967+
exists(BasicBlock bb, int i |
968+
captureWrite(v, bb, i, false, _) and
969+
node = TSynthThisQualifier(bb, i, false)
970+
)
971+
}
962972
}

0 commit comments

Comments
 (0)