Skip to content

Commit bf4ea02

Browse files
committed
Rust: Implement the query.
1 parent 526620c commit bf4ea02

File tree

4 files changed

+623
-56
lines changed

4 files changed

+623
-56
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Provides classes and predicates for reasoning about accesses to a pointer
3+
* after its lifetime has ended.
4+
*/
5+
6+
import rust
7+
private import codeql.rust.dataflow.DataFlow
8+
private import codeql.rust.security.AccessInvalidPointerExtensions
9+
10+
/**
11+
* Provides default sources, sinks and barriers for detecting accesses to a
12+
* pointer after its lifetime has ended, as well as extension points for
13+
* adding your own. Note that a particular `(source, sink)` pair must be
14+
* checked with `dereferenceAfterLifetime` to determine if it is a result.
15+
*/
16+
module AccessAfterLifetime {
17+
/**
18+
* A data flow source for accesses to a pointer after its lifetime has ended,
19+
* that is, creation of a pointer or reference.
20+
*/
21+
abstract class Source extends DataFlow::Node {
22+
/**
23+
* Gets the value this pointer or reference points to.
24+
*/
25+
abstract Expr getTargetValue();
26+
}
27+
28+
/**
29+
* A data flow sink for accesses to a pointer after its lifetime has ended,
30+
* that is, a dereference. We re-use the same sinks as for the accesses to
31+
* invalid pointers query.
32+
*/
33+
class Sink = AccessInvalidPointer::Sink;
34+
35+
/**
36+
* A barrier for accesses to a pointer after its lifetime has ended.
37+
*/
38+
abstract class Barrier extends DataFlow::Node { }
39+
40+
/**
41+
* Holds if the pair `(source, sink)` that represents a flow from a
42+
* pointer or reference to a dereference of that pointer or reference,
43+
* and the dereference is outside the lifetime of the target value.
44+
*/
45+
bindingset[source, sink]
46+
predicate dereferenceAfterLifetime(Source source, Sink sink) {
47+
exists(BlockExpr valueScope, BlockExpr accessScope |
48+
valueScope(source.getTargetValue(), valueScope) and
49+
accessScope = sink.asExpr().getExpr().getEnclosingBlock() and
50+
not maybeOnStack(valueScope, accessScope)
51+
)
52+
}
53+
54+
/**
55+
* Holds if `value` accesses a variable with scope `scope`.
56+
*/
57+
private predicate valueScope(Expr value, BlockExpr scope) {
58+
// variable access
59+
scope = value.(VariableAccess).getVariable().getEnclosingBlock()
60+
or
61+
// field access
62+
valueScope(value.(FieldExpr).getContainer(), scope)
63+
}
64+
65+
/**
66+
* Holds if block `a` contains block `b`, in the sense that a variable in
67+
* `a` may be on the stack during execution of `b`. This is interprocedural,
68+
* but is an overapproximation that doesn't accurately track call contexts
69+
* (for example if `f` and `g` both call `b`, then then depending on the
70+
* caller a variable in `f` or `g` may or may-not be on the stack during `b`).
71+
*/
72+
private predicate maybeOnStack(BlockExpr a, BlockExpr b) {
73+
// `b` is a child of `a`
74+
a = b.getEnclosingBlock*()
75+
or
76+
// propagate through function calls
77+
exists(CallExprBase ce |
78+
maybeOnStack(a, ce.getEnclosingBlock()) and
79+
ce.getStaticTarget() = b.getEnclosingCallable()
80+
)
81+
}
82+
83+
/**
84+
* A source that is a `RefExpr`.
85+
*/
86+
private class RefExprSource extends Source {
87+
Expr targetValue;
88+
89+
RefExprSource() { this.asExpr().getExpr().(RefExpr).getExpr() = targetValue }
90+
91+
override Expr getTargetValue() { result = targetValue }
92+
}
93+
}

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,33 @@
1313
*/
1414

1515
import rust
16+
import codeql.rust.dataflow.DataFlow
17+
import codeql.rust.dataflow.TaintTracking
18+
import codeql.rust.security.AccessAfterLifetimeExtensions
19+
import AccessAfterLifetimeFlow::PathGraph
1620

17-
from int n
18-
where none()
19-
select n
21+
/**
22+
* A data flow configuration for detecting accesses to a pointer after its
23+
* lifetime has ended.
24+
*/
25+
module AccessAfterLifetimeConfig implements DataFlow::ConfigSig {
26+
predicate isSource(DataFlow::Node node) { node instanceof AccessAfterLifetime::Source }
27+
28+
predicate isSink(DataFlow::Node node) { node instanceof AccessAfterLifetime::Sink }
29+
30+
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof AccessAfterLifetime::Barrier }
31+
}
32+
33+
module AccessAfterLifetimeFlow = TaintTracking::Global<AccessAfterLifetimeConfig>;
34+
35+
from
36+
AccessAfterLifetimeFlow::PathNode sourceNode, AccessAfterLifetimeFlow::PathNode sinkNode,
37+
Expr targetValue
38+
where
39+
// flow from a pointer or reference to the dereference
40+
AccessAfterLifetimeFlow::flowPath(sourceNode, sinkNode) and
41+
targetValue = sourceNode.getNode().(AccessAfterLifetime::Source).getTargetValue() and
42+
// check that the dereference is outside the lifetime of the target
43+
AccessAfterLifetime::dereferenceAfterLifetime(sourceNode.getNode(), sinkNode.getNode())
44+
select sinkNode.getNode(), sourceNode, sinkNode,
45+
"Access of a pointer to $@ after it's lifetime has ended.", targetValue, targetValue.toString()

0 commit comments

Comments
 (0)