Skip to content

Commit 3862ea2

Browse files
committed
Rust: Speedup AccessAfterLifetime.ql
Before ``` Pipeline standard for AccessAfterLifetimeExtensions::AccessAfterLifetime::mayEncloseOnStack/2#3cdefece#bf@61cb32j5 was evaluated in 30 iterations totaling 44856ms (delta sizes total: 241646328). 241404616 ~1% {2} r1 = SCAN `AccessAfterLifetimeExtensions::AccessAfterLifetime::mayEncloseOnStack/2#3cdefece#bf#prev_delta` OUTPUT In.1, In.0 7379161442 ~1080% {2} | JOIN WITH `_AstNode::AstNode.getEnclosingBlock/0#5c38e65a_AstNode::AstNode.getEnclosingCallable/0#5a548913_Bloc__#join_rhs` ON FIRST 1 OUTPUT Lhs.1, Rhs.1 333897324 ~40% {2} | AND NOT `AccessAfterLifetimeExtensions::AccessAfterLifetime::mayEncloseOnStack/2#3cdefece#bf#prev`(FIRST 2) 297961888 ~24% {2} | JOIN WITH `project#AccessAfterLifetimeExtensions::AccessAfterLifetime::sourceValueScope/3#d065ba16#2` ON FIRST 1 OUTPUT Lhs.0, Lhs.1 return r1 ```
1 parent 868e36f commit 3862ea2

File tree

2 files changed

+82
-29
lines changed

2 files changed

+82
-29
lines changed

rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,87 @@ module AccessAfterLifetime {
4848
}
4949

5050
/**
51-
* Holds if the pair `(source, sink)`, that represents a flow from a
52-
* pointer or reference to a dereference, has its dereference outside the
53-
* lifetime of the target variable `target`.
51+
* Holds if the pair `(source, sink)` represents a flow from a pointer or reference
52+
* to a dereference.
5453
*/
55-
bindingset[source, sink]
56-
predicate dereferenceAfterLifetime(Source source, Sink sink, Variable target) {
57-
exists(BlockExpr valueScope, BlockExpr accessScope |
58-
sourceValueScope(source, target, valueScope) and
59-
accessScope = sink.asExpr().getEnclosingBlock() and
60-
not mayEncloseOnStack(valueScope, accessScope)
61-
)
54+
signature predicate dereferenceAfterLifetimeCandSig(DataFlow::Node source, DataFlow::Node sink);
55+
56+
/** Provides logic for identifying dereferences after lifetime. */
57+
module DereferenceAfterLifetime<dereferenceAfterLifetimeCandSig/2 dereferenceAfterLifetimeCand> {
58+
private newtype TTcNode =
59+
TSource(Source s, Variable target) {
60+
dereferenceAfterLifetimeCand(s, _) and sourceValueScope(s, target, _)
61+
} or
62+
TBlockExpr(BlockExpr be) or
63+
TSink(Sink s) { dereferenceAfterLifetimeCand(_, s) }
64+
65+
private class TcNode extends TTcNode {
66+
Source asSource(Variable target) { this = TSource(result, target) }
67+
68+
BlockExpr asBlockExpr() { this = TBlockExpr(result) }
69+
70+
Sink asSink() { this = TSink(result) }
71+
72+
string toString() {
73+
result = this.asSource(_).toString()
74+
or
75+
result = this.asBlockExpr().toString()
76+
or
77+
result = this.asSink().toString()
78+
}
79+
80+
Location getLocation() {
81+
result = this.asSource(_).getLocation()
82+
or
83+
result = this.asBlockExpr().getLocation()
84+
or
85+
result = this.asSink().getLocation()
86+
}
87+
}
88+
89+
pragma[nomagic]
90+
private predicate tcStep(TcNode a, TcNode b) {
91+
// `b` is a child of `a`
92+
exists(Source source, Variable target, BlockExpr be |
93+
source = a.asSource(target) and
94+
be = b.asBlockExpr().getEnclosingBlock*() and
95+
sourceValueScope(source, target, be) and
96+
dereferenceAfterLifetimeCand(source, _)
97+
)
98+
or
99+
// propagate through function calls
100+
exists(Call call |
101+
a.asBlockExpr() = call.getEnclosingBlock() and
102+
call.getARuntimeTarget() = b.asBlockExpr().getEnclosingCallable()
103+
)
104+
or
105+
a.asBlockExpr() = b.asSink().asExpr().getEnclosingBlock()
106+
}
107+
108+
private predicate isTcSource(TcNode n) { n instanceof TSource }
109+
110+
private predicate isTcSink(TcNode n) { n instanceof TSink }
111+
112+
/**
113+
* Holds if block `a` contains block `b`, in the sense that a stack allocated variable in
114+
* `a` may still be on the stack during execution of `b`. This is interprocedural,
115+
* but is an overapproximation that doesn't accurately track call contexts
116+
* (for example if `f` and `g` both call `b`, then then depending on the
117+
* caller a variable in `f` or `g` may or may-not be on the stack during `b`).
118+
*/
119+
private predicate mayEncloseOnStack(TcNode a, TcNode b) =
120+
doublyBoundedFastTC(tcStep/2, isTcSource/1, isTcSink/1)(a, b)
121+
122+
/**
123+
* Holds if the pair `(source, sink)`, that represents a flow from a
124+
* pointer or reference to a dereference, has its dereference outside the
125+
* lifetime of the target variable `target`.
126+
*/
127+
predicate dereferenceAfterLifetime(Source source, Sink sink, Variable target) {
128+
dereferenceAfterLifetimeCand(source, sink) and
129+
sourceValueScope(source, target, _) and
130+
not mayEncloseOnStack(TSource(source, target), TSink(sink))
131+
}
62132
}
63133

64134
/**
@@ -88,24 +158,6 @@ module AccessAfterLifetime {
88158
valueScope(value.(FieldExpr).getContainer(), target, scope)
89159
}
90160

91-
/**
92-
* Holds if block `a` contains block `b`, in the sense that a stack allocated variable in
93-
* `a` may still be on the stack during execution of `b`. This is interprocedural,
94-
* but is an overapproximation that doesn't accurately track call contexts
95-
* (for example if `f` and `g` both call `b`, then then depending on the
96-
* caller a variable in `f` or `g` may or may-not be on the stack during `b`).
97-
*/
98-
private predicate mayEncloseOnStack(BlockExpr a, BlockExpr b) {
99-
// `b` is a child of `a`
100-
a = b.getEnclosingBlock*()
101-
or
102-
// propagate through function calls
103-
exists(Call call |
104-
mayEncloseOnStack(a, call.getEnclosingBlock()) and
105-
call.getARuntimeTarget() = b.getEnclosingCallable()
106-
)
107-
}
108-
109161
/**
110162
* A source that is a `RefExpr`.
111163
*/

rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ where
6161
// flow from a pointer or reference to the dereference
6262
AccessAfterLifetimeFlow::flowPath(sourceNode, sinkNode) and
6363
// check that the dereference is outside the lifetime of the target
64-
AccessAfterLifetime::dereferenceAfterLifetime(sourceNode.getNode(), sinkNode.getNode(), target)
64+
AccessAfterLifetime::DereferenceAfterLifetime<AccessAfterLifetimeFlow::flow/2>::dereferenceAfterLifetime(sourceNode
65+
.getNode(), sinkNode.getNode(), target)
6566
select sinkNode.getNode(), sourceNode, sinkNode,
6667
"Access of a pointer to $@ after its lifetime has ended.", target, target.toString()

0 commit comments

Comments
 (0)