From 8594c7a29a314cf5ac4a7691a26f345c1a37f08f Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:21:18 +0000 Subject: [PATCH 1/5] Rust: Add test for rust/access-after-lifetime-ended FP involving generic calls. --- .../CWE-825/AccessAfterLifetime.expected | 10 +++++++ .../query-tests/security/CWE-825/lifetime.rs | 30 +++++++++++++++++++ .../test/query-tests/security/CWE-825/main.rs | 3 ++ 3 files changed, 43 insertions(+) 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 c24c6a728bbf..1f5feff97659 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -22,6 +22,7 @@ | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:655:11:655:25 | &raw const str2 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:651:7:651:10 | str2 | str2 | | lifetime.rs:789:12:789:13 | p1 | lifetime.rs:781:9:781:19 | &my_local10 | lifetime.rs:789:12:789:13 | p1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:779:6:779:15 | my_local10 | my_local10 | | lifetime.rs:808:23:808:25 | ptr | lifetime.rs:798:9:798:12 | &val | lifetime.rs:808:23:808:25 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:796:6:796:8 | val | val | +| lifetime.rs:843:12:843:14 | ptr | lifetime.rs:851:12:851:23 | &local_value | lifetime.rs:843:12:843:14 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:850:6:850:16 | local_value | local_value | | main.rs:64:23:64:24 | p2 | main.rs:44:26:44:28 | &b2 | main.rs:64:23:64:24 | p2 | Access of a pointer to $@ after its lifetime has ended. | main.rs:43:13:43:14 | b2 | b2 | edges | deallocation.rs:242:6:242:7 | p1 | deallocation.rs:245:14:245:15 | p1 | provenance | | @@ -194,6 +195,10 @@ edges | lifetime.rs:798:9:798:12 | &val | lifetime.rs:798:2:798:12 | return ... | provenance | | | lifetime.rs:802:6:802:8 | ptr | lifetime.rs:808:23:808:25 | ptr | provenance | | | lifetime.rs:802:12:802:24 | get_pointer(...) | lifetime.rs:802:6:802:8 | ptr | provenance | | +| lifetime.rs:841:13:841:27 | ...: ... | lifetime.rs:843:12:843:14 | ptr | provenance | | +| lifetime.rs:851:6:851:8 | ptr | lifetime.rs:853:20:853:22 | ptr | provenance | | +| lifetime.rs:851:12:851:23 | &local_value | lifetime.rs:851:6:851:8 | ptr | provenance | | +| lifetime.rs:853:20:853:22 | ptr | lifetime.rs:841:13:841:27 | ...: ... | provenance | | | main.rs:18:9:18:10 | p1 [&ref] | main.rs:21:19:21:20 | p1 | provenance | | | main.rs:18:9:18:10 | p1 [&ref] | main.rs:29:19:29:20 | p1 | provenance | | | main.rs:18:14:18:29 | ...::as_ptr(...) [&ref] | main.rs:18:9:18:10 | p1 [&ref] | provenance | | @@ -409,6 +414,11 @@ nodes | lifetime.rs:802:6:802:8 | ptr | semmle.label | ptr | | lifetime.rs:802:12:802:24 | get_pointer(...) | semmle.label | get_pointer(...) | | lifetime.rs:808:23:808:25 | ptr | semmle.label | ptr | +| lifetime.rs:841:13:841:27 | ...: ... | semmle.label | ...: ... | +| lifetime.rs:843:12:843:14 | ptr | semmle.label | ptr | +| lifetime.rs:851:6:851:8 | ptr | semmle.label | ptr | +| lifetime.rs:851:12:851:23 | &local_value | semmle.label | &local_value | +| lifetime.rs:853:20:853:22 | ptr | semmle.label | ptr | | main.rs:18:9:18:10 | p1 [&ref] | semmle.label | p1 [&ref] | | main.rs:18:14:18:29 | ...::as_ptr(...) [&ref] | semmle.label | ...::as_ptr(...) [&ref] | | main.rs:18:26:18:28 | &b1 | semmle.label | &b1 | 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 83317aa13d1f..a918b69c4383 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -827,3 +827,33 @@ pub fn test_lifetimes_example_good() { println!(" val = {dereferenced_ptr}"); } + +// --- generic calls --- + +trait Processor { + fn process(ptr: *const i64) -> i64; +} + +struct MyProcessor { +} + +impl Processor for MyProcessor { + fn process(ptr: *const i64) -> i64 { + unsafe { + return *ptr; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=local_value + } + } +} + +fn generic_caller() -> i64 +{ + let local_value: i64 = 10; + let ptr = &local_value as *const i64; // $ Source[rust/access-after-lifetime-ended]=local_value + + return T::process(ptr); +} + +pub fn test_generic() { + let result = generic_caller::(); + println!(" result = {result}"); +} diff --git a/rust/ql/test/query-tests/security/CWE-825/main.rs b/rust/ql/test/query-tests/security/CWE-825/main.rs index d15f595e13c0..09a3d279c216 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -209,4 +209,7 @@ fn main() { println!("test_lifetimes_example_good:"); test_lifetimes_example_good(); + + println!("test_generic:"); + test_generic(); } From 32e9fdfe19f38279d4f2218f6e928f8a002db714 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:12:38 +0000 Subject: [PATCH 2/5] Rust: Fix the false positives. --- .../rust/security/AccessAfterLifetimeExtensions.qll | 8 +++++++- .../security/CWE-825/AccessAfterLifetime.expected | 1 - rust/ql/test/query-tests/security/CWE-825/lifetime.rs | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index 06438fef0c8f..e68fb2fb0fa4 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -99,11 +99,17 @@ module AccessAfterLifetime { // `b` is a child of `a` a = b.getEnclosingBlock*() or - // propagate through function calls + // propagate through function calls (static target) exists(CallExprBase ce | mayEncloseOnStack(a, ce.getEnclosingBlock()) and ce.getStaticTarget() = b.getEnclosingCallable() ) + or + // propagate through function calls (runtime target) + exists(Call c | + mayEncloseOnStack(a, c.getEnclosingBlock()) and + c.getARuntimeTarget() = b.getEnclosingCallable() + ) } /** 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 1f5feff97659..8609b4bcb6f8 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -22,7 +22,6 @@ | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:655:11:655:25 | &raw const str2 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:651:7:651:10 | str2 | str2 | | lifetime.rs:789:12:789:13 | p1 | lifetime.rs:781:9:781:19 | &my_local10 | lifetime.rs:789:12:789:13 | p1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:779:6:779:15 | my_local10 | my_local10 | | lifetime.rs:808:23:808:25 | ptr | lifetime.rs:798:9:798:12 | &val | lifetime.rs:808:23:808:25 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:796:6:796:8 | val | val | -| lifetime.rs:843:12:843:14 | ptr | lifetime.rs:851:12:851:23 | &local_value | lifetime.rs:843:12:843:14 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:850:6:850:16 | local_value | local_value | | main.rs:64:23:64:24 | p2 | main.rs:44:26:44:28 | &b2 | main.rs:64:23:64:24 | p2 | Access of a pointer to $@ after its lifetime has ended. | main.rs:43:13:43:14 | b2 | b2 | edges | deallocation.rs:242:6:242:7 | p1 | deallocation.rs:245:14:245:15 | p1 | provenance | | 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 a918b69c4383..05a099e903fb 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -840,7 +840,7 @@ struct MyProcessor { impl Processor for MyProcessor { fn process(ptr: *const i64) -> i64 { unsafe { - return *ptr; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=local_value + return *ptr; // good } } } @@ -848,7 +848,7 @@ impl Processor for MyProcessor { fn generic_caller() -> i64 { let local_value: i64 = 10; - let ptr = &local_value as *const i64; // $ Source[rust/access-after-lifetime-ended]=local_value + let ptr = &local_value as *const i64; return T::process(ptr); } From 3cdbef71f1c38982e6c74d5d973f21db48520d2c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:25:34 +0000 Subject: [PATCH 3/5] Rust: Change note. --- .../change-notes/2025-12-04-access-after-lifetime-ended.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rust/ql/src/change-notes/2025-12-04-access-after-lifetime-ended.md diff --git a/rust/ql/src/change-notes/2025-12-04-access-after-lifetime-ended.md b/rust/ql/src/change-notes/2025-12-04-access-after-lifetime-ended.md new file mode 100644 index 000000000000..1e9200eec8f1 --- /dev/null +++ b/rust/ql/src/change-notes/2025-12-04-access-after-lifetime-ended.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Fixed false positives from the `rust/access-after-lifetime-ended` query, involving calls to trait methods. From 4109848927a536b971c12f4aa417175fb632f3ea Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:55:34 +0000 Subject: [PATCH 4/5] Rust: Clean up following merge. --- .../rust/security/AccessAfterLifetimeExtensions.qll | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index 4b356aaa6514..dc99c61405b8 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -99,16 +99,10 @@ module AccessAfterLifetime { // `b` is a child of `a` a = b.getEnclosingBlock*() or - // propagate through function calls (static target) + // propagate through function calls exists(Call call | mayEncloseOnStack(a, call.getEnclosingBlock()) and - call.getStaticTarget() = b.getEnclosingCallable() - ) - or - // propagate through function calls (runtime target) - exists(Call c | - mayEncloseOnStack(a, c.getEnclosingBlock()) and - c.getARuntimeTarget() = b.getEnclosingCallable() + [call.getStaticTarget(), call.getARuntimeTarget()] = b.getEnclosingCallable() ) } From 108db7512450cfd3167265e9e370f7b4419144ad Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:19:38 +0000 Subject: [PATCH 5/5] Update rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll Co-authored-by: Simon Friis Vindum --- .../lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index dc99c61405b8..64b806331a66 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -102,7 +102,7 @@ module AccessAfterLifetime { // propagate through function calls exists(Call call | mayEncloseOnStack(a, call.getEnclosingBlock()) and - [call.getStaticTarget(), call.getARuntimeTarget()] = b.getEnclosingCallable() + call.getARuntimeTarget() = b.getEnclosingCallable() ) }