From 06c34fd0559613dc0e7f80215778b4dac4d674c3 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:47:11 +0000 Subject: [PATCH 1/5] Rust: Add test case for rust/access-after-lifetime-ended involving a pointer to a struct field. --- .../CWE-825/AccessAfterLifetime.expected | 13 +++++ .../query-tests/security/CWE-825/lifetime.rs | 47 +++++++++++++++++++ .../test/query-tests/security/CWE-825/main.rs | 3 ++ 3 files changed, 63 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 69573c738578..151a140d1ce6 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,8 @@ | 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:895:13:895:16 | ptr2 | lifetime.rs:880:3:880:23 | &raw const ... | lifetime.rs:895:13:895:16 | ptr2 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:879:25:879:28 | self | self | +| lifetime.rs:903:21:903:24 | ptr2 | lifetime.rs:880:3:880:23 | &raw const ... | lifetime.rs:903:21:903:24 | ptr2 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:879:25:879:28 | self | self | | 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 | | @@ -155,6 +157,11 @@ 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:879:45:882:5 | { ... } | lifetime.rs:892:10:892:23 | obj.get_ptr2() | provenance | | +| lifetime.rs:880:3:880:23 | &raw const ... | lifetime.rs:879:45:882:5 | { ... } | provenance | | +| lifetime.rs:892:3:892:6 | ptr2 | lifetime.rs:895:13:895:16 | ptr2 | provenance | | +| lifetime.rs:892:3:892:6 | ptr2 | lifetime.rs:903:21:903:24 | ptr2 | provenance | | +| lifetime.rs:892:10:892:23 | obj.get_ptr2() | lifetime.rs:892:3:892:6 | ptr2 | 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 | | @@ -325,6 +332,12 @@ 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:879:45:882:5 | { ... } | semmle.label | { ... } | +| lifetime.rs:880:3:880:23 | &raw const ... | semmle.label | &raw const ... | +| lifetime.rs:892:3:892:6 | ptr2 | semmle.label | ptr2 | +| lifetime.rs:892:10:892:23 | obj.get_ptr2() | semmle.label | obj.get_ptr2() | +| lifetime.rs:895:13:895:16 | ptr2 | semmle.label | ptr2 | +| lifetime.rs:903:21:903:24 | ptr2 | semmle.label | ptr2 | | 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 05a099e903fb..b0ed82f8f7a5 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -857,3 +857,50 @@ pub fn test_generic() { let result = generic_caller::(); println!(" result = {result}"); } + +// --- struct methods --- + +struct MyObjectWithGetters { + value: i64 +} + +impl MyObjectWithGetters { + pub fn new(_value: i64) -> Self { + Self { + value: _value + } + } + + pub unsafe fn get_ptr1(&self) -> *const i64 { + &raw const self.value // $ MISSING: Source[rust/access-after-lifetime-ended]=self_value + // (the returned pointer is valid as long as the containing object is) + } + + pub unsafe fn get_ptr2(self) -> *const i64 { + &raw const self.value // $ Source[rust/access-after-lifetime-ended]=self_value + // (the returned pointer is valid as long as the containing object is) + } +} + +pub fn test_struct_methods() { + let ptr1: *const i64; + let ptr2: *const i64; + + unsafe { + let obj = MyObjectWithGetters::new(1111); + ptr1 = obj.get_ptr1(); + ptr2 = obj.get_ptr2(); + + let v1 = *ptr1; + let v2 = *ptr2; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=self_value + println!(" v1 = {}", v1); + println!(" v2 = {}", v2); + } + + use_the_stack(); + + let v3 = unsafe { *ptr1 }; // $ MISSING: Alert[rust/access-after-lifetime-ended]=self_value + let v4 = unsafe { *ptr2 }; // $ Alert[rust/access-after-lifetime-ended]=self_value + println!(" v3 = {} (!)", v3); + println!(" v4 = {} (!)", v4); // corrupt in practice +} 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 d14c5d463ece..9b08df648a9c 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -215,4 +215,7 @@ fn main() { println!("test_generic:"); test_generic(); + + println!("test_struct_methods:"); + test_struct_methods(); } From 3792adf98d2726a4a190d8681a03bc22f6d88ea0 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:19:19 +0000 Subject: [PATCH 2/5] Rust: For now lets just exclude self parameters from the query sources. --- .../rust/security/AccessAfterLifetimeExtensions.qll | 3 ++- .../security/CWE-825/AccessAfterLifetime.expected | 13 ------------- .../test/query-tests/security/CWE-825/lifetime.rs | 8 ++++---- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index c404f13b5314..c0ba618a9c5e 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -57,7 +57,8 @@ module AccessAfterLifetime { // parameter exists(Callable c | var.getParameter().getEnclosingCallable() = c and - scope.getParentNode() = c + scope.getParentNode() = c and + not var.getParameter() instanceof SelfParam ) } 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 151a140d1ce6..69573c738578 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -22,8 +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:895:13:895:16 | ptr2 | lifetime.rs:880:3:880:23 | &raw const ... | lifetime.rs:895:13:895:16 | ptr2 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:879:25:879:28 | self | self | -| lifetime.rs:903:21:903:24 | ptr2 | lifetime.rs:880:3:880:23 | &raw const ... | lifetime.rs:903:21:903:24 | ptr2 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:879:25:879:28 | self | self | | 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 | | @@ -157,11 +155,6 @@ 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:879:45:882:5 | { ... } | lifetime.rs:892:10:892:23 | obj.get_ptr2() | provenance | | -| lifetime.rs:880:3:880:23 | &raw const ... | lifetime.rs:879:45:882:5 | { ... } | provenance | | -| lifetime.rs:892:3:892:6 | ptr2 | lifetime.rs:895:13:895:16 | ptr2 | provenance | | -| lifetime.rs:892:3:892:6 | ptr2 | lifetime.rs:903:21:903:24 | ptr2 | provenance | | -| lifetime.rs:892:10:892:23 | obj.get_ptr2() | lifetime.rs:892:3:892:6 | ptr2 | 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 | | @@ -332,12 +325,6 @@ 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:879:45:882:5 | { ... } | semmle.label | { ... } | -| lifetime.rs:880:3:880:23 | &raw const ... | semmle.label | &raw const ... | -| lifetime.rs:892:3:892:6 | ptr2 | semmle.label | ptr2 | -| lifetime.rs:892:10:892:23 | obj.get_ptr2() | semmle.label | obj.get_ptr2() | -| lifetime.rs:895:13:895:16 | ptr2 | semmle.label | ptr2 | -| lifetime.rs:903:21:903:24 | ptr2 | semmle.label | ptr2 | | 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 b0ed82f8f7a5..a4f870e96f74 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -877,7 +877,7 @@ impl MyObjectWithGetters { } pub unsafe fn get_ptr2(self) -> *const i64 { - &raw const self.value // $ Source[rust/access-after-lifetime-ended]=self_value + &raw const self.value // $ MISSING: Source[rust/access-after-lifetime-ended]=self_value // (the returned pointer is valid as long as the containing object is) } } @@ -891,8 +891,8 @@ pub fn test_struct_methods() { ptr1 = obj.get_ptr1(); ptr2 = obj.get_ptr2(); - let v1 = *ptr1; - let v2 = *ptr2; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=self_value + let v1 = *ptr1; // GOOD + let v2 = *ptr2; // GOOD println!(" v1 = {}", v1); println!(" v2 = {}", v2); } @@ -900,7 +900,7 @@ pub fn test_struct_methods() { use_the_stack(); let v3 = unsafe { *ptr1 }; // $ MISSING: Alert[rust/access-after-lifetime-ended]=self_value - let v4 = unsafe { *ptr2 }; // $ Alert[rust/access-after-lifetime-ended]=self_value + let v4 = unsafe { *ptr2 }; // $ MISSING: Alert[rust/access-after-lifetime-ended]=self_value println!(" v3 = {} (!)", v3); println!(" v4 = {} (!)", v4); // corrupt in practice } From bed81065b4f19fa9a2a89cef4a16e735cc19c23d Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:32:56 +0000 Subject: [PATCH 3/5] Rust: Rename the test case now that I've figured out exactly what the problem was. --- rust/ql/test/query-tests/security/CWE-825/lifetime.rs | 4 ++-- rust/ql/test/query-tests/security/CWE-825/main.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 a4f870e96f74..7c47397aed36 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -858,7 +858,7 @@ pub fn test_generic() { println!(" result = {result}"); } -// --- struct methods --- +// --- self --- struct MyObjectWithGetters { value: i64 @@ -882,7 +882,7 @@ impl MyObjectWithGetters { } } -pub fn test_struct_methods() { +pub fn test_get_self() { let ptr1: *const i64; let ptr2: *const i64; 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 9b08df648a9c..762b4ecfa36f 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -216,6 +216,6 @@ fn main() { println!("test_generic:"); test_generic(); - println!("test_struct_methods:"); - test_struct_methods(); + println!("test_get_self:"); + test_get_self(); } From d88e71a8ac3d690ce46c64ea6ceae5c60f315746 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:04:52 +0000 Subject: [PATCH 4/5] Rust: Change note. --- .../src/change-notes/2026-01-access-after-lifetime-ended.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rust/ql/src/change-notes/2026-01-access-after-lifetime-ended.md diff --git a/rust/ql/src/change-notes/2026-01-access-after-lifetime-ended.md b/rust/ql/src/change-notes/2026-01-access-after-lifetime-ended.md new file mode 100644 index 000000000000..9310c4237e95 --- /dev/null +++ b/rust/ql/src/change-notes/2026-01-access-after-lifetime-ended.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Fixed false positives from the `rust/access-after-lifetime-ended` query, involving pointers to fields of `self`. From 91ab686aed100c57483486b19da802023784c6d5 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:09:47 +0000 Subject: [PATCH 5/5] Rust: Fix the change note file name. --- ...ifetime-ended.md => 2026-01-13-access-after-lifetime-ended.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rust/ql/src/change-notes/{2026-01-access-after-lifetime-ended.md => 2026-01-13-access-after-lifetime-ended.md} (100%) diff --git a/rust/ql/src/change-notes/2026-01-access-after-lifetime-ended.md b/rust/ql/src/change-notes/2026-01-13-access-after-lifetime-ended.md similarity index 100% rename from rust/ql/src/change-notes/2026-01-access-after-lifetime-ended.md rename to rust/ql/src/change-notes/2026-01-13-access-after-lifetime-ended.md