From 18d2f586b35971cdf4a8313a2ec923c0bcab0fef Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 4 Mar 2026 09:08:05 +0100 Subject: [PATCH] Rust: Update `AccessAfterLifetime` query to use `FeatureEscapesSourceCallContextOrEqualSourceSinkCallContext` --- .../security/CWE-825/AccessAfterLifetime.ql | 59 +++++-------------- .../CWE-825/AccessAfterLifetime.expected | 34 +---------- .../query-tests/security/CWE-825/lifetime.rs | 2 +- 3 files changed, 18 insertions(+), 77 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql index 2f299167893..b27251c397c 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -50,6 +50,10 @@ module AccessAfterLifetimeConfig implements DataFlow::ConfigSig { result = [target.getLocation(), source.getLocation()] ) } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureEscapesSourceCallContextOrEqualSourceSinkCallContext + } } module AccessAfterLifetimeFlow = TaintTracking::Global; @@ -64,53 +68,22 @@ predicate sinkBlock(Sink s, BlockExpr be) { be = s.asExpr().getEnclosingBlock() } -private predicate tcStep(BlockExpr a, BlockExpr b) { - // propagate through function calls - exists(Call call | - a = call.getEnclosingBlock() and - call.getARuntimeTarget() = b.getEnclosingCallable() - ) -} - -private predicate isTcSource(BlockExpr be) { sourceBlock(_, _, be) } - -private predicate isTcSink(BlockExpr be) { sinkBlock(_, be) } - -/** - * Holds if block `a` contains block `b`, in the sense that a stack allocated variable in - * `a` may still be on the stack during execution of `b`. This is interprocedural, - * but is an overapproximation that doesn't accurately track call contexts - * (for example if `f` and `g` both call `b`, then depending on the - * caller a variable in `f` or `g` may or may-not be on the stack during `b`). - */ -private predicate mayEncloseOnStack(BlockExpr a, BlockExpr b) = - doublyBoundedFastTC(tcStep/2, isTcSource/1, isTcSink/1)(a, b) - -/** - * Holds if the pair `(source, sink)`, that represents a flow from a - * pointer or reference to a dereference, has its dereference outside the - * lifetime of the target variable `target`. - */ -predicate dereferenceAfterLifetime(Source source, Sink sink, Variable target) { - AccessAfterLifetimeFlow::flow(source, sink) and - sourceValueScope(source, target, _) and - not exists(BlockExpr beSource, BlockExpr beSink | - sourceBlock(source, target, beSource) and - sinkBlock(sink, beSink) - | - beSource = beSink - or - mayEncloseOnStack(beSource, beSink) - ) -} - from AccessAfterLifetimeFlow::PathNode sourceNode, AccessAfterLifetimeFlow::PathNode sinkNode, - Variable target + Source source, Sink sink, Variable target where // flow from a pointer or reference to the dereference AccessAfterLifetimeFlow::flowPath(sourceNode, sinkNode) and - // check that the dereference is outside the lifetime of the target - dereferenceAfterLifetime(sourceNode.getNode(), sinkNode.getNode(), target) + source = sourceNode.getNode() and + sink = sinkNode.getNode() and + sourceValueScope(source, target, _) and + // check that the dereference is outside the lifetime of the target, when the source + // and the sink are in the same callable + // (`FeatureEscapesSourceCallContextOrEqualSourceSinkCallContext` handles the case when + // they are not) + not exists(BlockExpr be | + sourceBlock(source, target, be) and + sinkBlock(sink, be) + ) select sinkNode.getNode(), sourceNode, sinkNode, "Access of a pointer to $@ after its lifetime has ended.", target, target.toString() diff --git a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected index 92e11e895cd..a301fad6560 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -9,6 +9,7 @@ | lifetime.rs:76:4:76:5 | p2 | lifetime.rs:27:9:27:22 | &mut my_local2 | lifetime.rs:76:4:76:5 | p2 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:25:10:25:18 | my_local2 | my_local2 | | lifetime.rs:77:4:77:5 | p4 | lifetime.rs:39:9:39:26 | &raw mut my_local4 | lifetime.rs:77:4:77:5 | p4 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:37:10:37:18 | my_local4 | my_local4 | | lifetime.rs:172:13:172:15 | ptr | lifetime.rs:187:12:187:21 | &my_local1 | lifetime.rs:172:13:172:15 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:186:6:186:14 | my_local1 | my_local1 | +| lifetime.rs:180:13:180:15 | ptr | lifetime.rs:187:12:187:21 | &my_local1 | lifetime.rs:180:13:180:15 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:186:6:186:14 | my_local1 | my_local1 | | lifetime.rs:255:14:255:17 | prev | lifetime.rs:251:10:251:19 | &my_local2 | lifetime.rs:255:14:255:17 | prev | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:242:7:242:15 | my_local2 | my_local2 | | lifetime.rs:310:31:310:32 | e1 | lifetime.rs:272:30:272:32 | &e1 | lifetime.rs:310:31:310:32 | e1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:271:6:271:7 | e1 | e1 | | lifetime.rs:314:23:314:24 | p2 | lifetime.rs:279:28:279:30 | &v2 | lifetime.rs:314:23:314:24 | p2 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:278:6:278:7 | v2 | v2 | @@ -55,39 +56,23 @@ edges | lifetime.rs:59:11:59:36 | get_local_field_dangling(...) | lifetime.rs:59:6:59:7 | p6 | provenance | | | lifetime.rs:63:3:63:4 | p7 | lifetime.rs:75:13:75:14 | p7 | provenance | | | lifetime.rs:63:8:63:27 | &raw const my_local7 | lifetime.rs:63:3:63:4 | p7 | provenance | | -| lifetime.rs:91:17:91:30 | ...: ... | lifetime.rs:101:14:101:15 | p1 | provenance | | -| lifetime.rs:91:33:91:44 | ...: ... | lifetime.rs:102:14:102:15 | p2 | provenance | | -| lifetime.rs:91:33:91:44 | ...: ... | lifetime.rs:110:5:110:6 | p2 | provenance | | | lifetime.rs:94:2:94:3 | p3 | lifetime.rs:103:14:103:15 | p3 | provenance | | | lifetime.rs:94:7:94:16 | &my_local1 | lifetime.rs:94:2:94:3 | p3 | provenance | | -| lifetime.rs:119:15:119:24 | &my_local3 | lifetime.rs:91:17:91:30 | ...: ... | provenance | | -| lifetime.rs:119:27:119:44 | &mut my_local_mut4 | lifetime.rs:91:33:91:44 | ...: ... | provenance | | -| lifetime.rs:161:17:161:31 | ...: ... | lifetime.rs:164:13:164:15 | ptr | provenance | | | lifetime.rs:169:17:169:31 | ...: ... | lifetime.rs:172:13:172:15 | ptr | provenance | | | lifetime.rs:177:17:177:31 | ...: ... | lifetime.rs:180:13:180:15 | ptr | provenance | | -| lifetime.rs:187:6:187:8 | ptr | lifetime.rs:189:15:189:17 | ptr | provenance | | -| lifetime.rs:187:6:187:8 | ptr | lifetime.rs:190:15:190:17 | ptr | provenance | | | lifetime.rs:187:6:187:8 | ptr | lifetime.rs:192:2:192:11 | return ptr | provenance | | | lifetime.rs:187:12:187:21 | &my_local1 | lifetime.rs:187:6:187:8 | ptr | provenance | | -| lifetime.rs:189:15:189:17 | ptr | lifetime.rs:161:17:161:31 | ...: ... | provenance | | -| lifetime.rs:190:15:190:17 | ptr | lifetime.rs:177:17:177:31 | ...: ... | provenance | | | lifetime.rs:192:2:192:11 | return ptr | lifetime.rs:196:12:196:36 | access_and_get_dangling(...) | provenance | | | lifetime.rs:196:6:196:8 | ptr | lifetime.rs:200:15:200:17 | ptr | provenance | | | lifetime.rs:196:6:196:8 | ptr | lifetime.rs:201:15:201:17 | ptr | provenance | | | lifetime.rs:196:12:196:36 | access_and_get_dangling(...) | lifetime.rs:196:6:196:8 | ptr | provenance | | | lifetime.rs:200:15:200:17 | ptr | lifetime.rs:169:17:169:31 | ...: ... | provenance | | | lifetime.rs:201:15:201:17 | ptr | lifetime.rs:177:17:177:31 | ...: ... | provenance | | -| lifetime.rs:206:19:206:36 | ...: ... | lifetime.rs:216:16:216:21 | ptr_up | provenance | | -| lifetime.rs:208:6:208:13 | ptr_ours | lifetime.rs:211:33:211:40 | ptr_ours | provenance | | | lifetime.rs:208:6:208:13 | ptr_ours | lifetime.rs:225:2:225:16 | return ptr_ours | provenance | | | lifetime.rs:208:17:208:29 | &my_local_rec | lifetime.rs:208:6:208:13 | ptr_ours | provenance | | | lifetime.rs:211:7:211:14 | ptr_down | lifetime.rs:218:18:218:25 | ptr_down | provenance | | | lifetime.rs:211:18:211:52 | access_ptr_rec(...) | lifetime.rs:211:7:211:14 | ptr_down | provenance | | -| lifetime.rs:211:33:211:40 | ptr_ours | lifetime.rs:206:19:206:36 | ...: ... | provenance | | | lifetime.rs:225:2:225:16 | return ptr_ours | lifetime.rs:211:18:211:52 | access_ptr_rec(...) | provenance | | -| lifetime.rs:230:6:230:14 | ptr_start | lifetime.rs:232:21:232:29 | ptr_start | provenance | | -| lifetime.rs:230:18:230:31 | &my_local_rec2 | lifetime.rs:230:6:230:14 | ptr_start | provenance | | -| lifetime.rs:232:21:232:29 | ptr_start | lifetime.rs:206:19:206:36 | ...: ... | provenance | | | lifetime.rs:239:6:239:13 | mut prev | lifetime.rs:247:15:247:18 | prev | provenance | | | lifetime.rs:239:6:239:13 | mut prev | lifetime.rs:255:14:255:17 | prev | provenance | | | lifetime.rs:239:34:239:43 | &my_local1 | lifetime.rs:239:6:239:13 | mut prev | provenance | | @@ -215,43 +200,26 @@ nodes | lifetime.rs:75:13:75:14 | p7 | semmle.label | p7 | | lifetime.rs:76:4:76:5 | p2 | semmle.label | p2 | | lifetime.rs:77:4:77:5 | p4 | semmle.label | p4 | -| lifetime.rs:91:17:91:30 | ...: ... | semmle.label | ...: ... | -| lifetime.rs:91:33:91:44 | ...: ... | semmle.label | ...: ... | | lifetime.rs:94:2:94:3 | p3 | semmle.label | p3 | | lifetime.rs:94:7:94:16 | &my_local1 | semmle.label | &my_local1 | -| lifetime.rs:101:14:101:15 | p1 | semmle.label | p1 | -| lifetime.rs:102:14:102:15 | p2 | semmle.label | p2 | | lifetime.rs:103:14:103:15 | p3 | semmle.label | p3 | -| lifetime.rs:110:5:110:6 | p2 | semmle.label | p2 | -| lifetime.rs:119:15:119:24 | &my_local3 | semmle.label | &my_local3 | -| lifetime.rs:119:27:119:44 | &mut my_local_mut4 | semmle.label | &mut my_local_mut4 | -| lifetime.rs:161:17:161:31 | ...: ... | semmle.label | ...: ... | -| lifetime.rs:164:13:164:15 | ptr | semmle.label | ptr | | lifetime.rs:169:17:169:31 | ...: ... | semmle.label | ...: ... | | lifetime.rs:172:13:172:15 | ptr | semmle.label | ptr | | lifetime.rs:177:17:177:31 | ...: ... | semmle.label | ...: ... | | lifetime.rs:180:13:180:15 | ptr | semmle.label | ptr | | lifetime.rs:187:6:187:8 | ptr | semmle.label | ptr | | lifetime.rs:187:12:187:21 | &my_local1 | semmle.label | &my_local1 | -| lifetime.rs:189:15:189:17 | ptr | semmle.label | ptr | -| lifetime.rs:190:15:190:17 | ptr | semmle.label | ptr | | lifetime.rs:192:2:192:11 | return ptr | semmle.label | return ptr | | lifetime.rs:196:6:196:8 | ptr | semmle.label | ptr | | lifetime.rs:196:12:196:36 | access_and_get_dangling(...) | semmle.label | access_and_get_dangling(...) | | lifetime.rs:200:15:200:17 | ptr | semmle.label | ptr | | lifetime.rs:201:15:201:17 | ptr | semmle.label | ptr | -| lifetime.rs:206:19:206:36 | ...: ... | semmle.label | ...: ... | | lifetime.rs:208:6:208:13 | ptr_ours | semmle.label | ptr_ours | | lifetime.rs:208:17:208:29 | &my_local_rec | semmle.label | &my_local_rec | | lifetime.rs:211:7:211:14 | ptr_down | semmle.label | ptr_down | | lifetime.rs:211:18:211:52 | access_ptr_rec(...) | semmle.label | access_ptr_rec(...) | -| lifetime.rs:211:33:211:40 | ptr_ours | semmle.label | ptr_ours | -| lifetime.rs:216:16:216:21 | ptr_up | semmle.label | ptr_up | | lifetime.rs:218:18:218:25 | ptr_down | semmle.label | ptr_down | | lifetime.rs:225:2:225:16 | return ptr_ours | semmle.label | return ptr_ours | -| lifetime.rs:230:6:230:14 | ptr_start | semmle.label | ptr_start | -| lifetime.rs:230:18:230:31 | &my_local_rec2 | semmle.label | &my_local_rec2 | -| lifetime.rs:232:21:232:29 | ptr_start | semmle.label | ptr_start | | lifetime.rs:239:6:239:13 | mut prev | semmle.label | mut prev | | lifetime.rs:239:34:239:43 | &my_local1 | semmle.label | &my_local1 | | lifetime.rs:247:15:247:18 | prev | semmle.label | prev | diff --git a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs index 05a099e903f..51042b56ecc 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -177,7 +177,7 @@ fn access_ptr_2(ptr: *const i64) { fn access_ptr_3(ptr: *const i64) { // called from contexts with `ptr` safe and dangling unsafe { - let v3 = *ptr; // $ MISSING: Alert + let v3 = *ptr; // $ Alert[rust/access-after-lifetime-ended]=local1 println!(" v3 = {v3} (!)"); // corrupt in practice (in one context) } }