From da4fbfb4496492e830626217ab809176004e3992 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 30 May 2025 14:59:19 +0100 Subject: [PATCH 01/34] Rust: Placeholder new query. --- .../security/CWE-825/AccessAfterLifetime.ql | 19 +++++++++++++++++++ .../CWE-825/AccessAfterLifetime.expected | 10 ++++++++++ .../CWE-825/AccessAfterLifetime.qlref | 4 ++++ 3 files changed, 33 insertions(+) create mode 100644 rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql create mode 100644 rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected create mode 100644 rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.qlref diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql new file mode 100644 index 000000000000..b2530e93fe06 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -0,0 +1,19 @@ +/** + * @name Access of a pointer after its lifetime has ended + * @description Dereferencing a pointer after the lifetime of its target has ended + * causes undefined behavior and may result in memory corruption. + * @kind path-problem + * @problem.severity error + * @security-severity TODO + * @precision high + * @id rust/access-after-lifetime-ended + * @tags reliability + * security + * external/cwe/cwe-825 + */ + +import rust + +from int n +where none() +select n diff --git a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected new file mode 100644 index 000000000000..302f403e7cf2 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -0,0 +1,10 @@ +#select +testFailures +| deallocation.rs:20:36:20:54 | //... | Missing result: Source=dealloc | +| deallocation.rs:70:47:70:71 | //... | Missing result: Source=dealloc_array | +| deallocation.rs:112:44:112:59 | //... | Missing result: Source=free | +| deallocation.rs:123:45:123:64 | //... | Missing result: Source=dangling | +| deallocation.rs:124:47:124:70 | //... | Missing result: Source=dangling_mut | +| deallocation.rs:125:41:125:56 | //... | Missing result: Source=null | +| deallocation.rs:176:32:176:56 | //... | Missing result: Source=drop_in_place | +| deallocation.rs:242:33:242:57 | //... | Missing result: Source=drop_in_place | diff --git a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.qlref b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.qlref new file mode 100644 index 000000000000..d9249badc000 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.qlref @@ -0,0 +1,4 @@ +query: queries/security/CWE-825/AccessAfterLifetime.ql +postprocess: + - utils/test/PrettyPrintModels.ql + - utils/test/InlineExpectationsTestQuery.ql From 8e8374b9bcb55cce3fba289d1e22c605e8cac60e Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 30 May 2025 15:28:11 +0100 Subject: [PATCH 02/34] Rust: Label source annotations in the test properly. --- .../CWE-825/AccessAfterLifetime.expected | 10 --------- .../security/CWE-825/deallocation.rs | 22 +++++++++---------- 2 files changed, 11 insertions(+), 21 deletions(-) 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 302f403e7cf2..e69de29bb2d1 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -1,10 +0,0 @@ -#select -testFailures -| deallocation.rs:20:36:20:54 | //... | Missing result: Source=dealloc | -| deallocation.rs:70:47:70:71 | //... | Missing result: Source=dealloc_array | -| deallocation.rs:112:44:112:59 | //... | Missing result: Source=free | -| deallocation.rs:123:45:123:64 | //... | Missing result: Source=dangling | -| deallocation.rs:124:47:124:70 | //... | Missing result: Source=dangling_mut | -| deallocation.rs:125:41:125:56 | //... | Missing result: Source=null | -| deallocation.rs:176:32:176:56 | //... | Missing result: Source=drop_in_place | -| deallocation.rs:242:33:242:57 | //... | Missing result: Source=drop_in_place | diff --git a/rust/ql/test/query-tests/security/CWE-825/deallocation.rs b/rust/ql/test/query-tests/security/CWE-825/deallocation.rs index 361f938e02c4..6024ccd48717 100644 --- a/rust/ql/test/query-tests/security/CWE-825/deallocation.rs +++ b/rust/ql/test/query-tests/security/CWE-825/deallocation.rs @@ -17,7 +17,7 @@ pub fn test_alloc(mode: i32) { println!(" v3 = {v3}"); println!(" v4 = {v4}"); - std::alloc::dealloc(m1, layout); // $ Source=dealloc + std::alloc::dealloc(m1, layout); // $ Source[rust/access-invalid-pointer]=dealloc // (m1, m2 are now dangling) match mode { @@ -67,7 +67,7 @@ pub fn test_alloc_array(mode: i32) { println!(" v1 = {v1}"); println!(" v2 = {v2}"); - std::alloc::dealloc(m2 as *mut u8, layout); // $ Source=dealloc_array + std::alloc::dealloc(m2 as *mut u8, layout); // $ Source[rust/access-invalid-pointer]=dealloc_array // m1, m2 are now dangling match mode { @@ -109,7 +109,7 @@ pub fn test_libc() { let v1 = *my_ptr; // GOOD println!(" v1 = {v1}"); - libc::free(my_ptr as *mut libc::c_void); // $ Source=free + libc::free(my_ptr as *mut libc::c_void); // $ Source[rust/access-invalid-pointer]=free // (my_ptr is now dangling) let v2 = *my_ptr; // $ Alert[rust/access-invalid-pointer]=free @@ -120,9 +120,9 @@ pub fn test_libc() { // --- std::ptr --- pub fn test_ptr_invalid(mode: i32) { - let p1: *const i64 = std::ptr::dangling(); // $ Source=dangling - let p2: *mut i64 = std::ptr::dangling_mut(); // $ Source=dangling_mut - let p3: *const i64 = std::ptr::null(); // $ Source=null + let p1: *const i64 = std::ptr::dangling(); // $ Source[rust/access-invalid-pointer]=dangling + let p2: *mut i64 = std::ptr::dangling_mut(); // $ Source[rust/access-invalid-pointer]=dangling_mut + let p3: *const i64 = std::ptr::null(); // $ Source[rust/access-invalid-pointer]=null if mode == 120 { unsafe { @@ -173,7 +173,7 @@ pub fn test_ptr_drop(mode: i32) { println!(" v1 = {v1}"); println!(" v2 = {v2}"); - std::ptr::drop_in_place(p1); // $ Source=drop_in_place + std::ptr::drop_in_place(p1); // $ Source[rust/access-invalid-pointer]=drop_in_place // explicitly destructs the pointed-to `m2` if mode == 1 { @@ -212,7 +212,7 @@ impl Drop for MyDropBuffer { unsafe { _ = *self.ptr; - drop(*self.ptr); // $ MISSING: Source=drop + drop(*self.ptr); // $ MISSING: Source[rust/access-invalid-pointer]=drop _ = *self.ptr; // $ MISSING: Alert[rust/access-invalid-pointer]=drop std::alloc::dealloc(self.ptr, layout); } @@ -239,7 +239,7 @@ fn test_qhelp_example_good(ptr: *mut String) { fn test_qhelp_example_bad(ptr: *mut String) { unsafe { - std::ptr::drop_in_place(ptr); // $ Source=drop_in_place + std::ptr::drop_in_place(ptr); // $ Source[rust/access-invalid-pointer]=drop_in_place } // ... @@ -280,7 +280,7 @@ pub fn test_vec_reserve() { println!(" v1 = {}", v1); } - vec1.reserve(1000); // $ MISSING: Source=reserve + vec1.reserve(1000); // $ MISSING: Source[rust/access-invalid-pointer]=reserve // (may invalidate the pointer) unsafe { @@ -300,7 +300,7 @@ pub fn test_vec_reserve() { } for _i in 0..1000 { - vec2.push(0); // $ MISSING: Source=push + vec2.push(0); // $ MISSING: Source[rust/access-invalid-pointer]=push // (may invalidate the pointer) } From 43cb98ad157423aed5d73090ed6a8dad01089d9d Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 30 May 2025 16:12:31 +0100 Subject: [PATCH 03/34] Rust: Fix some warnings in the existing test. --- rust/ql/test/query-tests/security/CWE-825/deallocation.rs | 4 ++-- rust/ql/test/query-tests/security/CWE-825/lifetime.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/ql/test/query-tests/security/CWE-825/deallocation.rs b/rust/ql/test/query-tests/security/CWE-825/deallocation.rs index 6024ccd48717..89ef0470e99d 100644 --- a/rust/ql/test/query-tests/security/CWE-825/deallocation.rs +++ b/rust/ql/test/query-tests/security/CWE-825/deallocation.rs @@ -29,8 +29,8 @@ pub fn test_alloc(mode: i32) { println!(" v6 = {v6} (!)"); // corrupt in practice // test repeat reads (we don't want lots of very similar results for the same dealloc) - let v5b = *m1; - let v5c = *m1; + let _v5b = *m1; + let _v5c = *m1; }, 100 => { // more reads 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 d7fd8204993a..3d1cb78b20d0 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -96,7 +96,7 @@ fn use_pointers(p1: *const i64, p2: *mut i64, mode: i32) { use_the_stack(); unsafe { - if (mode == 0) { + if mode == 0 { // reads let v1 = *p1; // GOOD let v2 = *p2; // GOOD @@ -105,7 +105,7 @@ fn use_pointers(p1: *const i64, p2: *mut i64, mode: i32) { println!(" v2 = {v2}"); println!(" v3 = {v3}"); } - if (mode == 200) { + if mode == 200 { // writes *p2 = 2; // GOOD } @@ -142,14 +142,14 @@ pub fn test_static(mode: i32) { use_the_stack(); unsafe { - if (mode == 0) { + if mode == 0 { // reads let v1 = *p1; // GOOD let v2 = *p2; // GOOD println!(" v1 = {v1}"); println!(" v2 = {v2}"); } - if (mode == 210) { + if mode == 210 { // writes *p2 = 3; // GOOD } From ae19ecc674b16e7636a38d632ef7e99d2c21d4b4 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 30 May 2025 16:04:35 +0100 Subject: [PATCH 04/34] Rust: Add test cases involving lifetimes + closures and async blocks. --- .../query-tests/security/CWE-825/lifetime.rs | 90 +++++++++++++++++++ .../test/query-tests/security/CWE-825/main.rs | 6 ++ .../query-tests/security/CWE-825/options.yml | 1 + 3 files changed, 97 insertions(+) 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 3d1cb78b20d0..d98c7ef8e9d7 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -441,3 +441,93 @@ pub fn test_rc() { // note: simialar things are likely possible with Ref, RefMut, RefCell, // Vec and others. } + +// --- closures --- + +fn get_closure(p3: *const i64, p4: *const i64) -> impl FnOnce() { + let my_local1: i64 = 1; + let my_local2: i64 = 2; + let p1: *const i64 = &my_local1; + + return move || { // captures `my_local2`, `p1`, `p3`, `p4` by value (due to `move`) + let p2: *const i64 = &my_local2; + + unsafe { + let v1 = *p1; // $ MISSING: Alert + let v2 = *p2; // GOOD + let v3 = *p3; // GOOD + let v4 = *p4; // $ MISSING: Alert + println!(" v1 = {v1} (!)"); // corrupt in practice + println!(" v2 = {v2}"); + println!(" v3 = {v3}"); + println!(" v4 = {v4} (!)"); + } + }; +} // (`my_local1` goes out of scope, thus `p1` is dangling) + +fn with_closure(ptr: *const i64, closure: fn(*const i64, *const i64)) { + let my_local5: i64 = 5; + + closure(ptr, + &my_local5); +} + +pub fn test_closures() { + let closure; + let my_local3: i64 = 3; + { + let my_local4: i64 = 4; + closure = get_closure( &my_local3, + &my_local4); + } // (`my_local4` goes out of scope, so `p4` is dangling) + + use_the_stack(); + + closure(); + + with_closure(&my_local3, |p1, p2| { + unsafe { + let v5 = *p1; // GOOD + let v6 = *p2; // GOOD + println!(" v5 = {v5}"); + println!(" v6 = {v6}"); + } + }); +} + +// --- async --- + +fn get_async_closure(p3: *const i64, p4: *const i64) -> impl std::future::Future { + let my_local1: i64 = 1; + let my_local2: i64 = 2; + let p1: *const i64 = &my_local1; + + return async move { // captures `my_local2`, `p1`, `p3`, `p4` by value (due to `move`) + let p2: *const i64 = &my_local2; + + unsafe { + let v1 = *p1; // $ MISSING: Alert + let v2 = *p2; // GOOD + let v3 = *p3; // GOOD + let v4 = *p4; // $ MISSING: Alert + println!(" v1 = {v1} (!)"); // corrupt in practice + println!(" v2 = {v2}"); + println!(" v3 = {v3}"); + println!(" v4 = {v4} (!)"); + } + }; +} // (`my_local1` goes out of scope, thus `p1` is dangling) + +pub fn test_async() { + let async_closure; + let my_local3: i64 = 3; + { + let my_local4: i64 = 4; + async_closure = get_async_closure(&my_local3, + &my_local4); + } // (`my_local4` goes out of scope, so `p4` is dangling) + + use_the_stack(); + + futures::executor::block_on(async_closure); +} 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 ec135011f705..81f6161f70ce 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -165,4 +165,10 @@ fn main() { println!("test_rc:"); test_rc(); + + println!("test_closures:"); + test_closures(); + + println!("test_async:"); + test_async(); } diff --git a/rust/ql/test/query-tests/security/CWE-825/options.yml b/rust/ql/test/query-tests/security/CWE-825/options.yml index 95a17a53b431..90a51f61a43e 100644 --- a/rust/ql/test/query-tests/security/CWE-825/options.yml +++ b/rust/ql/test/query-tests/security/CWE-825/options.yml @@ -1,3 +1,4 @@ qltest_cargo_check: true qltest_dependencies: - libc = { version = "0.2.11" } + - futures = { version = "0.3" } From e2fb1d3892f6e60c8ba4a3c66c420b8768e0810f Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 30 May 2025 17:50:08 +0100 Subject: [PATCH 05/34] Rust: Add test cases involving lifetimes + lifetime annotations. --- .../query-tests/security/CWE-825/lifetime.rs | 49 +++++++++++++++++++ .../test/query-tests/security/CWE-825/main.rs | 3 ++ 2 files changed, 52 insertions(+) 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 d98c7ef8e9d7..ddf8badd3507 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -531,3 +531,52 @@ pub fn test_async() { futures::executor::block_on(async_closure); } + +// --- lifetime annotations --- + +fn select_str<'a>(cond: bool, a: &'a str, b: &'a str) -> &'a str { + if cond { a } else { b } +} + +struct MyRefStr<'a> { + ref_str: &'a str, +} + +pub fn test_lifetime_annotations() { + let str1: *const str; + { + let foo = String::from("foo"); + let bar = String::from("bar"); + str1 = select_str(true, foo.as_str(), bar.as_str()); + + unsafe { + let v1 = &*str1; // GOOD + println!(" v1 = {v1}"); + } + } // (`foo`, `bar` go out of scope, the return value of `select_str` has the same lifetime, thus `str1` is dangling) + + unsafe { + let v2 = &*str1; // $ MISSING: Alert + println!(" v2 = {v2} (!)"); // corrupt in practice + } + + let my_ref; + let str2: *const str; + { + let baz = String::from("baz"); + my_ref = MyRefStr { ref_str: baz.as_str() }; + str2 = &*my_ref.ref_str; + + unsafe { + let v3 = &*str2; // GOOD + println!(" v3 = {v3}"); + } + } // (`baz` goes out of scope, `ref_str` has the same lifetime, thus `str2` is dangling) + + use_the_stack(); + + unsafe { + let v4 = &*str2; // $ MISSING: Alert + 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 81f6161f70ce..a3493497bec3 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -171,4 +171,7 @@ fn main() { println!("test_async:"); test_async(); + + println!("test_lifetime_annotations:"); + test_lifetime_annotations(); } From 66c1e2caceb801c8eb19993ecc3e269048bcdffc Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Jun 2025 23:10:39 +0100 Subject: [PATCH 06/34] Rust: Add test cases for implicit dereferences and more pointer/enum mixes (inspired by early real world results). --- .../query-tests/security/CWE-825/lifetime.rs | 126 +++++++++++++++--- .../test/query-tests/security/CWE-825/main.rs | 10 +- 2 files changed, 115 insertions(+), 21 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 ddf8badd3507..3dc74b978308 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -257,41 +257,103 @@ pub fn test_loop() { } } -// --- enum --- +// --- enums --- enum MyEnum { Value(i64), } -impl Drop for MyEnum { - fn drop(&mut self) { - println!(" drop MyEnum"); - } +enum MyEnum2 { + Pointer(*const i64), } -pub fn test_enum() { +pub fn get_pointer_to_enum() -> *const MyEnum { + let e1 = MyEnum::Value(1); + let result: *const MyEnum = &e1; // $ MISSING: Source[rust/access-after-lifetime-ended]=e1 + + result +} // (e1 goes out of scope, so result is dangling) + +pub fn get_pointer_in_enum() -> MyEnum2 { + let v2 = 2; + let e2 = MyEnum2::Pointer(&v2); // $ MISSING: Source[rust/access-after-lifetime-ended]=v2 + + e2 +} // (v2 goes out of scope, so the contained pointer is dangling) + +pub fn get_pointer_from_enum() -> *const i64 { + let e3 = MyEnum::Value(3); let result: *const i64; - { - let e1 = MyEnum::Value(1); + result = match e3 { + MyEnum::Value(x) => { &x } // $ MISSING: Source[rust/access-after-lifetime-ended]=match_x + }; // (x goes out of scope, so result is possibly dangling already) - result = match e1 { - MyEnum::Value(x) => { &x } - }; // (x goes out of scope, so result is dangling, I think; seen in real world code) + use_the_stack(); - use_the_stack(); + unsafe { + let v0 = *result; // ? + println!(" v0 = {v0} (?)"); + } - unsafe { - let v1 = *result; // $ MISSING: Alert - println!(" v1 = {v1}"); - } - } // (e1 goes out of scope, so result is definitely dangling now) + result +} // (e3 goes out of scope, so result is definitely dangling now) + +pub fn test_enums() { + let e1 = get_pointer_to_enum(); + let e2 = get_pointer_in_enum(); + let result = get_pointer_from_enum(); use_the_stack(); unsafe { - let v2 = *result; // $ MISSING: Alert - println!(" v2 = {v2}"); // dropped in practice + if let MyEnum::Value(v1) = *e1 { // $ MISSING: Alert[rust/access-after-lifetime-ended]=e1 + println!(" v1 = {v1} (!)"); // corrupt in practice + } + if let MyEnum2::Pointer(p2) = e2 { + let v2 = unsafe { *p2 }; // $ MISSING: Alert[rust/access-after-lifetime-ended]=v2 + println!(" v2 = {v2} (!)"); // corrupt in practice + } + let v3 = *result; // $ MISSING: Alert[rust/access-after-lifetime-ended]=match_x + println!(" v3 = {v3} (!)"); // corrupt in practice + } +} + +// --- recursive enum --- + +enum RecursiveEnum { + Wrapper(Box), + Pointer(*const i64), +} + +pub fn get_recursive_enum() -> Box { + let v1 = 1; + let enum1 = RecursiveEnum::Wrapper(Box::new(RecursiveEnum::Pointer(&v1))); // Source[rust/access-after-lifetime-ended]=v1 + let mut ref1 = &enum1; + + while let RecursiveEnum::Wrapper(inner) = ref1 { + println!(" wrapper"); + ref1 = &inner; + } + if let RecursiveEnum::Pointer(ptr) = ref1 { + let v2: i64 = unsafe { **ptr }; // GOOD + println!(" v2 = {v2}"); + } + + return Box::new(enum1); +} // (v1 goes out of scope, thus the contained pointer is dangling) + +pub fn test_recursive_enums() { + let enum1 = *get_recursive_enum(); + let mut ref1 = &enum1; + + while let RecursiveEnum::Wrapper(inner) = ref1 { + println!(" wrapper"); + ref1 = &inner; + } + if let RecursiveEnum::Pointer(ptr) = ref1 { + let v3: i64 = unsafe { **ptr }; // Alert[rust/access-after-lifetime-ended]=v1 + println!(" v3 = {v3} (!)"); // corrupt in practice } } @@ -580,3 +642,29 @@ pub fn test_lifetime_annotations() { println!(" v4 = {v4} (!)"); // corrupt in practice } } + +// --- implicit dereferences --- + +pub fn test_implicit_derefs() { + let ref1; + { + let str2; + { + let str1 = "bar"; + str2 = "foo".to_string() + &str1; // $ MISSING: Source[rust/access-after-lifetime-ended]=str1 + ref1 = &raw const str2; // $ MISSING: Source[rust/access-after-lifetime-ended]=str2 + } // (str1 goes out of scope, but it's been copied into str2) + + unsafe { + let v1 = &*ref1; // GOOD + println!(" v1 = {v1}"); + } + } // (str2 goes out of scope, thus ref1 is dangling) + + use_the_stack(); + + unsafe { + let v2 = &*ref1; // $ MISSING: Alert[rust/access-after-lifetime-ended]=str2 + println!(" v2 = {v2} (!)"); // 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 a3493497bec3..d2316fea79b2 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -154,8 +154,11 @@ fn main() { println!("test_loop:"); test_loop(); - println!("test_enum:"); - test_enum(); + println!("test_enums:"); + test_enums(); + + println!("test_recursive_enums:"); + test_recursive_enums(); println!("test_ptr_to_struct:"); test_ptr_to_struct(mode); @@ -174,4 +177,7 @@ fn main() { println!("test_lifetime_annotations:"); test_lifetime_annotations(); + + println!("test_implicit_derefs:"); + test_implicit_derefs(); } From 96dc34e36d91bc7313af78a94b0ff3541797c153 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:23:38 +0100 Subject: [PATCH 07/34] Rust: Even more test cases (inspired by real world results). --- .../query-tests/security/CWE-825/lifetime.rs | 57 ++++++++++++++++++- .../test/query-tests/security/CWE-825/main.rs | 6 ++ 2 files changed, 62 insertions(+), 1 deletion(-) 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 3dc74b978308..25aedb5eb44c 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -491,7 +491,7 @@ pub fn test_rc() { println!(" v3 = {v3}"); println!(" v4 = {v4}"); } - } // rc1 go out of scope, the reference count is 0, so p1, p2 are dangling + } // rc1 goes out of scope, the reference count is 0, so p1, p2 are dangling unsafe { let v5 = *p1; // $ MISSING: Alert @@ -668,3 +668,58 @@ pub fn test_implicit_derefs() { println!(" v2 = {v2} (!)"); // corrupt in practice } } + +// --- members --- + +struct MyType { + value: i64, +} + +impl MyType { + fn test(&self) { + let r1 = unsafe { + let v1 = &self; + &v1.value + }; + let (r2, r3) = unsafe { + let v2 = &self; + (&v2.value, + &self.value) + }; + + use_the_stack(); + + let v1 = *r1; + let v2 = *r2; + let v3 = *r3; + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + println!(" v3 = {v3}"); + } +} + +pub fn test_members() { + let mt = MyType { value: 1 }; + mt.test(); +} + +// --- macros --- + +macro_rules! my_macro { + () => { + let ptr: *const i64; + { + let val: i64 = 1; + ptr = &val; + } + + unsafe { + let v = *ptr; + println!(" v = {v}"); + } + }; +} + +pub fn test_macros() { + my_macro!(); +} 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 d2316fea79b2..5450dcd6b205 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -180,4 +180,10 @@ fn main() { println!("test_implicit_derefs:"); test_implicit_derefs(); + + println!("test_members:"); + test_members(); + + println!("test_macros:"); + test_macros(); } From 526620ca41a413b171a9e21d0a5a760d1d4cd4e8 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 30 May 2025 18:34:39 +0100 Subject: [PATCH 08/34] Rust: Add some helper predicates for finding enclosing blocks. --- .../lib/codeql/rust/elements/internal/AstNodeImpl.qll | 11 +++++++++++ .../codeql/rust/elements/internal/VariableImpl.qll | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll index b80da6d7084f..163af5da899c 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll @@ -59,6 +59,17 @@ module Impl { ) } + /** Gets the block that encloses this node, if any. */ + cached + BlockExpr getEnclosingBlock() { + exists(AstNode p | p = this.getParentNode() | + result = p + or + not p instanceof BlockExpr and + result = p.getEnclosingBlock() + ) + } + /** Holds if this node is inside a macro expansion. */ predicate isInMacroExpansion() { MacroCallImpl::isInMacroExpansion(_, this) } diff --git a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll index 790186bf2c9f..697672bbaf3f 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll @@ -127,6 +127,10 @@ module Impl { */ Name getName() { variableDecl(definingNode, result, text) } + /** Gets the block that encloses this variable, if any. */ + cached + BlockExpr getEnclosingBlock() { result = definingNode.getEnclosingBlock() } + /** Gets the `self` parameter that declares this variable, if any. */ SelfParam getSelfParam() { result.getName() = this.getName() } From bf4ea02dd22a9628979d83ac19e4f4bd77f5e87c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 30 May 2025 17:55:20 +0100 Subject: [PATCH 09/34] Rust: Implement the query. --- .../AccessAfterLifetimeExtensions.qll | 93 ++++ .../security/CWE-825/AccessAfterLifetime.ql | 32 +- .../CWE-825/AccessAfterLifetime.expected | 447 ++++++++++++++++++ .../query-tests/security/CWE-825/lifetime.rs | 107 ++--- 4 files changed, 623 insertions(+), 56 deletions(-) create mode 100644 rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll new file mode 100644 index 000000000000..e2b0643d9699 --- /dev/null +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -0,0 +1,93 @@ +/** + * Provides classes and predicates for reasoning about accesses to a pointer + * after its lifetime has ended. + */ + +import rust +private import codeql.rust.dataflow.DataFlow +private import codeql.rust.security.AccessInvalidPointerExtensions + +/** + * Provides default sources, sinks and barriers for detecting accesses to a + * pointer after its lifetime has ended, as well as extension points for + * adding your own. Note that a particular `(source, sink)` pair must be + * checked with `dereferenceAfterLifetime` to determine if it is a result. + */ +module AccessAfterLifetime { + /** + * A data flow source for accesses to a pointer after its lifetime has ended, + * that is, creation of a pointer or reference. + */ + abstract class Source extends DataFlow::Node { + /** + * Gets the value this pointer or reference points to. + */ + abstract Expr getTargetValue(); + } + + /** + * A data flow sink for accesses to a pointer after its lifetime has ended, + * that is, a dereference. We re-use the same sinks as for the accesses to + * invalid pointers query. + */ + class Sink = AccessInvalidPointer::Sink; + + /** + * A barrier for accesses to a pointer after its lifetime has ended. + */ + abstract class Barrier extends DataFlow::Node { } + + /** + * Holds if the pair `(source, sink)` that represents a flow from a + * pointer or reference to a dereference of that pointer or reference, + * and the dereference is outside the lifetime of the target value. + */ + bindingset[source, sink] + predicate dereferenceAfterLifetime(Source source, Sink sink) { + exists(BlockExpr valueScope, BlockExpr accessScope | + valueScope(source.getTargetValue(), valueScope) and + accessScope = sink.asExpr().getExpr().getEnclosingBlock() and + not maybeOnStack(valueScope, accessScope) + ) + } + + /** + * Holds if `value` accesses a variable with scope `scope`. + */ + private predicate valueScope(Expr value, BlockExpr scope) { + // variable access + scope = value.(VariableAccess).getVariable().getEnclosingBlock() + or + // field access + valueScope(value.(FieldExpr).getContainer(), scope) + } + + /** + * Holds if block `a` contains block `b`, in the sense that a variable in + * `a` may 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 then depending on the + * caller a variable in `f` or `g` may or may-not be on the stack during `b`). + */ + private predicate maybeOnStack(BlockExpr a, BlockExpr b) { + // `b` is a child of `a` + a = b.getEnclosingBlock*() + or + // propagate through function calls + exists(CallExprBase ce | + maybeOnStack(a, ce.getEnclosingBlock()) and + ce.getStaticTarget() = b.getEnclosingCallable() + ) + } + + /** + * A source that is a `RefExpr`. + */ + private class RefExprSource extends Source { + Expr targetValue; + + RefExprSource() { this.asExpr().getExpr().(RefExpr).getExpr() = targetValue } + + override Expr getTargetValue() { result = targetValue } + } +} diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql index b2530e93fe06..b49d8b59df3f 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -13,7 +13,33 @@ */ import rust +import codeql.rust.dataflow.DataFlow +import codeql.rust.dataflow.TaintTracking +import codeql.rust.security.AccessAfterLifetimeExtensions +import AccessAfterLifetimeFlow::PathGraph -from int n -where none() -select n +/** + * A data flow configuration for detecting accesses to a pointer after its + * lifetime has ended. + */ +module AccessAfterLifetimeConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { node instanceof AccessAfterLifetime::Source } + + predicate isSink(DataFlow::Node node) { node instanceof AccessAfterLifetime::Sink } + + predicate isBarrier(DataFlow::Node barrier) { barrier instanceof AccessAfterLifetime::Barrier } +} + +module AccessAfterLifetimeFlow = TaintTracking::Global; + +from + AccessAfterLifetimeFlow::PathNode sourceNode, AccessAfterLifetimeFlow::PathNode sinkNode, + Expr targetValue +where + // flow from a pointer or reference to the dereference + AccessAfterLifetimeFlow::flowPath(sourceNode, sinkNode) and + targetValue = sourceNode.getNode().(AccessAfterLifetime::Source).getTargetValue() and + // check that the dereference is outside the lifetime of the target + AccessAfterLifetime::dereferenceAfterLifetime(sourceNode.getNode(), sinkNode.getNode()) +select sinkNode.getNode(), sourceNode, sinkNode, + "Access of a pointer to $@ after it's lifetime has ended.", targetValue, targetValue.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 e69de29bb2d1..9e12cf83794a 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -0,0 +1,447 @@ +#select +| lifetime.rs:69:13:69:14 | p1 | lifetime.rs:21:9:21:18 | &my_local1 | lifetime.rs:69:13:69:14 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:21:10:21:18 | my_local1 | my_local1 | +| lifetime.rs:70:13:70:14 | p2 | lifetime.rs:27:9:27:22 | &mut my_local2 | lifetime.rs:70:13:70:14 | p2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:27:14:27:22 | my_local2 | my_local2 | +| lifetime.rs:71:13:71:14 | p3 | lifetime.rs:33:9:33:28 | &raw const my_local3 | lifetime.rs:71:13:71:14 | p3 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:33:20:33:28 | my_local3 | my_local3 | +| lifetime.rs:72:13:72:14 | p4 | lifetime.rs:39:9:39:26 | &raw mut my_local4 | lifetime.rs:72:13:72:14 | p4 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:39:18:39:26 | my_local4 | my_local4 | +| lifetime.rs:74:13:74:14 | p6 | lifetime.rs:50:9:50:18 | &... | lifetime.rs:74:13:74:14 | p6 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:50:10:50:18 | val.value | val.value | +| lifetime.rs:75:13:75:14 | p7 | lifetime.rs:63:8:63:27 | &raw const my_local7 | lifetime.rs:75:13:75:14 | p7 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:63:19:63:27 | my_local7 | my_local7 | +| 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 it's lifetime has ended. | lifetime.rs:27:14:27:22 | 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 it's lifetime has ended. | lifetime.rs:39:18:39:26 | 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 it's lifetime has ended. | lifetime.rs:187:13:187:21 | 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 it's lifetime has ended. | lifetime.rs:251:11:251:19 | 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 it's lifetime has ended. | lifetime.rs:272:31:272:32 | e1 | e1 | +| lifetime.rs:317:13:317:18 | result | lifetime.rs:289:25:289:26 | &x | lifetime.rs:317:13:317:18 | result | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:289:26:289:26 | x | x | +| lifetime.rs:411:16:411:17 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:411:16:411:17 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:383:31:383:37 | my_pair | my_pair | +| lifetime.rs:416:16:416:17 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:416:16:416:17 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:383:31:383:37 | my_pair | my_pair | +| lifetime.rs:428:7:428:8 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:428:7:428:8 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:383:31:383:37 | my_pair | my_pair | +| lifetime.rs:433:7:433:8 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:433:7:433:8 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:383:31:383:37 | my_pair | my_pair | +| lifetime.rs:459:13:459:14 | p1 | lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:459:13:459:14 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:442:18:442:23 | my_val | my_val | +| lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:442:18:442:23 | my_val | my_val | +| lifetime.rs:520:14:520:15 | p3 | lifetime.rs:542:26:542:35 | &my_local3 | lifetime.rs:520:14:520:15 | p3 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:542:27:542:35 | my_local3 | my_local3 | +| lifetime.rs:521:14:521:15 | p4 | lifetime.rs:543:4:543:13 | &my_local4 | lifetime.rs:521:14:521:15 | p4 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:543:5:543:13 | my_local4 | my_local4 | +| lifetime.rs:553:14:553:15 | p2 | lifetime.rs:534:3:534:12 | &my_local5 | lifetime.rs:553:14:553:15 | p2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:534:4:534:12 | my_local5 | my_local5 | +| lifetime.rs:659:15:659:18 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:659:15:659:18 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:654:32:654:35 | str1 | str1 | +| lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:654:32:654:35 | str1 | str1 | +| 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 it's lifetime has ended. | lifetime.rs:655:22:655:25 | str2 | str2 | +| lifetime.rs:692:13:692:14 | r1 | lifetime.rs:682:4:682:12 | &... | lifetime.rs:692:13:692:14 | r1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:682:5:682:12 | v1.value | v1.value | +| lifetime.rs:693:13:693:14 | r2 | lifetime.rs:686:5:686:13 | &... | lifetime.rs:693:13:693:14 | r2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:686:6:686:13 | v2.value | v2.value | +| lifetime.rs:725:2:725:12 | ptr | lifetime.rs:724:2:724:12 | &val | lifetime.rs:725:2:725:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:724:2:724:12 | val | val | +edges +| deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151:15 | p1 | provenance | | +| deallocation.rs:148:6:148:7 | p1 | deallocation.rs:158:14:158:15 | p1 | provenance | | +| deallocation.rs:148:30:148:38 | &raw const my_buffer | deallocation.rs:148:6:148:7 | p1 | provenance | | +| deallocation.rs:228:28:228:43 | ...: ... | deallocation.rs:230:18:230:20 | ptr | provenance | | +| deallocation.rs:240:27:240:42 | ...: ... | deallocation.rs:248:18:248:20 | ptr | provenance | | +| deallocation.rs:257:7:257:10 | ptr1 | deallocation.rs:260:4:260:7 | ptr1 | provenance | | +| deallocation.rs:257:7:257:10 | ptr1 | deallocation.rs:260:4:260:7 | ptr1 | provenance | | +| deallocation.rs:257:14:257:33 | &raw mut ... | deallocation.rs:257:7:257:10 | ptr1 | provenance | | +| deallocation.rs:258:7:258:10 | ptr2 | deallocation.rs:261:4:261:7 | ptr2 | provenance | | +| deallocation.rs:258:7:258:10 | ptr2 | deallocation.rs:261:4:261:7 | ptr2 | provenance | | +| deallocation.rs:258:14:258:33 | &raw mut ... | deallocation.rs:258:7:258:10 | ptr2 | provenance | | +| deallocation.rs:260:4:260:7 | ptr1 | deallocation.rs:263:27:263:30 | ptr1 | provenance | | +| deallocation.rs:261:4:261:7 | ptr2 | deallocation.rs:265:26:265:29 | ptr2 | provenance | | +| deallocation.rs:263:27:263:30 | ptr1 | deallocation.rs:228:28:228:43 | ...: ... | provenance | | +| deallocation.rs:265:26:265:29 | ptr2 | deallocation.rs:240:27:240:42 | ...: ... | provenance | | +| deallocation.rs:276:6:276:9 | ptr1 | deallocation.rs:279:13:279:16 | ptr1 | provenance | | +| deallocation.rs:276:6:276:9 | ptr1 | deallocation.rs:287:13:287:16 | ptr1 | provenance | | +| deallocation.rs:276:13:276:28 | &raw mut ... | deallocation.rs:276:6:276:9 | ptr1 | provenance | | +| deallocation.rs:295:6:295:9 | ptr2 | deallocation.rs:298:13:298:16 | ptr2 | provenance | | +| deallocation.rs:295:6:295:9 | ptr2 | deallocation.rs:308:13:308:16 | ptr2 | provenance | | +| deallocation.rs:295:13:295:28 | &raw mut ... | deallocation.rs:295:6:295:9 | ptr2 | provenance | | +| lifetime.rs:21:2:21:18 | return ... | lifetime.rs:54:11:54:30 | get_local_dangling(...) | provenance | | +| lifetime.rs:21:9:21:18 | &my_local1 | lifetime.rs:21:2:21:18 | return ... | provenance | | +| lifetime.rs:27:2:27:22 | return ... | lifetime.rs:55:11:55:34 | get_local_dangling_mut(...) | provenance | | +| lifetime.rs:27:9:27:22 | &mut my_local2 | lifetime.rs:27:2:27:22 | return ... | provenance | | +| lifetime.rs:33:2:33:28 | return ... | lifetime.rs:56:11:56:40 | get_local_dangling_raw_const(...) | provenance | | +| lifetime.rs:33:9:33:28 | &raw const my_local3 | lifetime.rs:33:2:33:28 | return ... | provenance | | +| lifetime.rs:39:2:39:26 | return ... | lifetime.rs:57:11:57:38 | get_local_dangling_raw_mut(...) | provenance | | +| lifetime.rs:39:9:39:26 | &raw mut my_local4 | lifetime.rs:39:2:39:26 | return ... | provenance | | +| lifetime.rs:43:2:43:15 | return ... | lifetime.rs:58:11:58:31 | get_param_dangling(...) | provenance | | +| lifetime.rs:43:9:43:15 | ¶m5 | lifetime.rs:43:2:43:15 | return ... | provenance | | +| lifetime.rs:50:2:50:18 | return ... | lifetime.rs:59:11:59:36 | get_local_field_dangling(...) | provenance | | +| lifetime.rs:50:9:50:18 | &... | lifetime.rs:50:2:50:18 | return ... | provenance | | +| lifetime.rs:54:6:54:7 | p1 | lifetime.rs:69:13:69:14 | p1 | provenance | | +| lifetime.rs:54:11:54:30 | get_local_dangling(...) | lifetime.rs:54:6:54:7 | p1 | provenance | | +| lifetime.rs:55:6:55:7 | p2 | lifetime.rs:70:13:70:14 | p2 | provenance | | +| lifetime.rs:55:6:55:7 | p2 | lifetime.rs:76:4:76:5 | p2 | provenance | | +| lifetime.rs:55:11:55:34 | get_local_dangling_mut(...) | lifetime.rs:55:6:55:7 | p2 | provenance | | +| lifetime.rs:56:6:56:7 | p3 | lifetime.rs:71:13:71:14 | p3 | provenance | | +| lifetime.rs:56:11:56:40 | get_local_dangling_raw_const(...) | lifetime.rs:56:6:56:7 | p3 | provenance | | +| lifetime.rs:57:6:57:7 | p4 | lifetime.rs:72:13:72:14 | p4 | provenance | | +| lifetime.rs:57:6:57:7 | p4 | lifetime.rs:77:4:77:5 | p4 | provenance | | +| lifetime.rs:57:11:57:38 | get_local_dangling_raw_mut(...) | lifetime.rs:57:6:57:7 | p4 | provenance | | +| lifetime.rs:58:6:58:7 | p5 | lifetime.rs:73:13:73:14 | p5 | provenance | | +| lifetime.rs:58:11:58:31 | get_param_dangling(...) | lifetime.rs:58:6:58:7 | p5 | provenance | | +| lifetime.rs:59:6:59:7 | p6 | lifetime.rs:74:13:74:14 | p6 | provenance | | +| 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:127:2:127:24 | return ... | lifetime.rs:139:11:139:21 | get_const(...) | provenance | | +| lifetime.rs:127:9:127:24 | &MY_GLOBAL_CONST | lifetime.rs:127:2:127:24 | return ... | provenance | | +| lifetime.rs:134:3:134:30 | return ... | lifetime.rs:140:11:140:26 | get_static_mut(...) | provenance | | +| lifetime.rs:134:10:134:30 | &mut MY_GLOBAL_STATIC | lifetime.rs:134:3:134:30 | return ... | provenance | | +| lifetime.rs:139:6:139:7 | p1 | lifetime.rs:147:14:147:15 | p1 | provenance | | +| lifetime.rs:139:11:139:21 | get_const(...) | lifetime.rs:139:6:139:7 | p1 | provenance | | +| lifetime.rs:140:6:140:7 | p2 | lifetime.rs:148:14:148:15 | p2 | provenance | | +| lifetime.rs:140:6:140:7 | p2 | lifetime.rs:154:5:154:6 | p2 | provenance | | +| lifetime.rs:140:11:140:26 | get_static_mut(...) | lifetime.rs:140:6:140:7 | p2 | 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:217:18:217:25 | 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 | | +| lifetime.rs:251:3:251:6 | prev | lifetime.rs:247:15:247:18 | prev | provenance | | +| lifetime.rs:251:3:251:6 | prev | lifetime.rs:255:14:255:17 | prev | provenance | | +| lifetime.rs:251:10:251:19 | &my_local2 | lifetime.rs:251:3:251:6 | prev | provenance | | +| lifetime.rs:270:47:275:1 | { ... } | lifetime.rs:303:11:303:31 | get_pointer_to_enum(...) | provenance | | +| lifetime.rs:272:6:272:11 | result | lifetime.rs:270:47:275:1 | { ... } | provenance | | +| lifetime.rs:272:30:272:32 | &e1 | lifetime.rs:272:6:272:11 | result | provenance | | +| lifetime.rs:284:46:300:1 | { ... } | lifetime.rs:305:15:305:37 | get_pointer_from_enum(...) | provenance | | +| lifetime.rs:288:2:288:7 | result | lifetime.rs:284:46:300:1 | { ... } | provenance | | +| lifetime.rs:288:2:288:7 | result | lifetime.rs:295:13:295:18 | result | provenance | | +| lifetime.rs:289:25:289:26 | &x | lifetime.rs:288:2:288:7 | result | provenance | | +| lifetime.rs:303:6:303:7 | e1 | lifetime.rs:310:31:310:32 | e1 | provenance | | +| lifetime.rs:303:11:303:31 | get_pointer_to_enum(...) | lifetime.rs:303:6:303:7 | e1 | provenance | | +| lifetime.rs:305:6:305:11 | result | lifetime.rs:317:13:317:18 | result | provenance | | +| lifetime.rs:305:15:305:37 | get_pointer_from_enum(...) | lifetime.rs:305:6:305:11 | result | provenance | | +| lifetime.rs:383:3:383:4 | p1 | lifetime.rs:388:15:388:16 | p1 | provenance | | +| lifetime.rs:383:3:383:4 | p1 | lifetime.rs:391:15:391:16 | p1 | provenance | | +| lifetime.rs:383:3:383:4 | p1 | lifetime.rs:399:6:399:7 | p1 | provenance | | +| lifetime.rs:383:3:383:4 | p1 | lifetime.rs:401:6:401:7 | p1 | provenance | | +| lifetime.rs:383:3:383:4 | p1 | lifetime.rs:411:16:411:17 | p1 | provenance | | +| lifetime.rs:383:3:383:4 | p1 | lifetime.rs:416:16:416:17 | p1 | provenance | | +| lifetime.rs:383:3:383:4 | p1 | lifetime.rs:428:7:428:8 | p1 | provenance | | +| lifetime.rs:383:3:383:4 | p1 | lifetime.rs:433:7:433:8 | p1 | provenance | | +| lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:383:3:383:4 | p1 | provenance | | +| lifetime.rs:384:3:384:4 | p2 | lifetime.rs:394:14:394:15 | p2 | provenance | | +| lifetime.rs:384:3:384:4 | p2 | lifetime.rs:421:15:421:16 | p2 | provenance | | +| lifetime.rs:384:27:384:35 | &raw const ... | lifetime.rs:384:3:384:4 | p2 | provenance | | +| lifetime.rs:385:3:385:4 | p3 | lifetime.rs:395:14:395:15 | p3 | provenance | | +| lifetime.rs:385:3:385:4 | p3 | lifetime.rs:400:5:400:6 | p3 | provenance | | +| lifetime.rs:385:3:385:4 | p3 | lifetime.rs:400:5:400:6 | p3 | provenance | | +| lifetime.rs:385:31:385:39 | &raw mut ... | lifetime.rs:385:3:385:4 | p3 | provenance | | +| lifetime.rs:400:5:400:6 | p3 | lifetime.rs:422:15:422:16 | p3 | provenance | | +| lifetime.rs:400:5:400:6 | p3 | lifetime.rs:429:6:429:7 | p3 | provenance | | +| lifetime.rs:442:6:442:7 | r1 | lifetime.rs:443:42:443:43 | r1 | provenance | | +| lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:442:6:442:7 | r1 | provenance | | +| lifetime.rs:443:6:443:7 | p1 | lifetime.rs:446:13:446:14 | p1 | provenance | | +| lifetime.rs:443:6:443:7 | p1 | lifetime.rs:450:2:450:10 | return p1 | provenance | | +| lifetime.rs:443:23:443:44 | ...::from_ref(...) | lifetime.rs:443:6:443:7 | p1 | provenance | | +| lifetime.rs:443:42:443:43 | r1 | lifetime.rs:443:23:443:44 | ...::from_ref(...) | provenance | MaD:1 | +| lifetime.rs:450:2:450:10 | return p1 | lifetime.rs:454:11:454:29 | get_ptr_from_ref(...) | provenance | | +| lifetime.rs:450:2:450:10 | return p1 | lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | provenance | | +| lifetime.rs:454:6:454:7 | p1 | lifetime.rs:459:13:459:14 | p1 | provenance | | +| lifetime.rs:454:11:454:29 | get_ptr_from_ref(...) | lifetime.rs:454:6:454:7 | p1 | provenance | | +| lifetime.rs:509:16:509:29 | ...: ... | lifetime.rs:514:2:527:2 | return ... [captured p3] | provenance | | +| lifetime.rs:509:32:509:45 | ...: ... | lifetime.rs:514:2:527:2 | return ... [captured p4] | provenance | | +| lifetime.rs:512:23:512:32 | &my_local1 | lifetime.rs:514:2:527:2 | return ... [captured p1] | provenance | | +| lifetime.rs:514:2:527:2 | return ... [captured p1] | lifetime.rs:542:13:543:14 | get_closure(...) [captured p1] | provenance | | +| lifetime.rs:515:7:515:8 | p2 | lifetime.rs:519:14:519:15 | p2 | provenance | | +| lifetime.rs:515:24:515:33 | &my_local2 | lifetime.rs:515:7:515:8 | p2 | provenance | | +| lifetime.rs:530:17:530:31 | ...: ... | lifetime.rs:533:10:533:12 | ptr | provenance | | +| lifetime.rs:533:10:533:12 | ptr | lifetime.rs:550:28:550:29 | ... | provenance | | +| lifetime.rs:534:3:534:12 | &my_local5 | lifetime.rs:550:32:550:33 | ... | provenance | | +| lifetime.rs:542:3:542:9 | closure [captured p1] | lifetime.rs:548:2:548:8 | closure [captured p1] | provenance | | +| lifetime.rs:542:3:542:9 | closure [captured p3] | lifetime.rs:548:2:548:8 | closure [captured p3] | provenance | | +| lifetime.rs:542:3:542:9 | closure [captured p4] | lifetime.rs:548:2:548:8 | closure [captured p4] | provenance | | +| lifetime.rs:542:13:543:14 | get_closure(...) [captured p1] | lifetime.rs:542:3:542:9 | closure [captured p1] | provenance | | +| lifetime.rs:542:13:543:14 | get_closure(...) [captured p3] | lifetime.rs:542:3:542:9 | closure [captured p3] | provenance | | +| lifetime.rs:542:13:543:14 | get_closure(...) [captured p4] | lifetime.rs:542:3:542:9 | closure [captured p4] | provenance | | +| lifetime.rs:542:26:542:35 | &my_local3 | lifetime.rs:509:16:509:29 | ...: ... | provenance | | +| lifetime.rs:542:26:542:35 | &my_local3 | lifetime.rs:542:13:543:14 | get_closure(...) [captured p3] | provenance | | +| lifetime.rs:543:4:543:13 | &my_local4 | lifetime.rs:509:32:509:45 | ...: ... | provenance | | +| lifetime.rs:543:4:543:13 | &my_local4 | lifetime.rs:542:13:543:14 | get_closure(...) [captured p4] | provenance | | +| lifetime.rs:548:2:548:8 | closure [captured p1] | lifetime.rs:518:14:518:15 | p1 | provenance | | +| lifetime.rs:548:2:548:8 | closure [captured p3] | lifetime.rs:520:14:520:15 | p3 | provenance | | +| lifetime.rs:548:2:548:8 | closure [captured p4] | lifetime.rs:521:14:521:15 | p4 | provenance | | +| lifetime.rs:550:15:550:24 | &my_local3 | lifetime.rs:530:17:530:31 | ...: ... | provenance | | +| lifetime.rs:550:28:550:29 | ... | lifetime.rs:552:14:552:15 | p1 | provenance | | +| lifetime.rs:550:32:550:33 | ... | lifetime.rs:553:14:553:15 | p2 | provenance | | +| lifetime.rs:568:7:568:8 | p2 | lifetime.rs:572:14:572:15 | p2 | provenance | | +| lifetime.rs:568:24:568:33 | &my_local2 | lifetime.rs:568:7:568:8 | p2 | provenance | | +| lifetime.rs:630:3:630:6 | str2 | lifetime.rs:633:15:633:18 | str2 | provenance | | +| lifetime.rs:630:3:630:6 | str2 | lifetime.rs:641:14:641:17 | str2 | provenance | | +| lifetime.rs:630:10:630:25 | &... | lifetime.rs:630:3:630:6 | str2 | provenance | | +| lifetime.rs:654:4:654:7 | str2 | lifetime.rs:655:22:655:25 | str2 | provenance | | +| lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:654:4:654:7 | str2 | provenance | | +| lifetime.rs:655:4:655:7 | ref1 | lifetime.rs:659:15:659:18 | ref1 | provenance | | +| lifetime.rs:655:4:655:7 | ref1 | lifetime.rs:667:14:667:17 | ref1 | provenance | | +| lifetime.rs:655:4:655:7 | ref1 [&ref] | lifetime.rs:659:15:659:18 | ref1 | provenance | | +| lifetime.rs:655:4:655:7 | ref1 [&ref] | lifetime.rs:667:14:667:17 | ref1 | provenance | | +| lifetime.rs:655:11:655:25 | &raw const str2 | lifetime.rs:655:4:655:7 | ref1 | provenance | | +| lifetime.rs:655:11:655:25 | &raw const str2 [&ref] | lifetime.rs:655:4:655:7 | ref1 [&ref] | provenance | | +| lifetime.rs:655:22:655:25 | str2 | lifetime.rs:655:11:655:25 | &raw const str2 [&ref] | provenance | | +| lifetime.rs:680:7:680:8 | r1 | lifetime.rs:692:13:692:14 | r1 | provenance | | +| lifetime.rs:682:4:682:12 | &... | lifetime.rs:680:7:680:8 | r1 | provenance | | +| lifetime.rs:684:7:684:14 | TuplePat [tuple.0] | lifetime.rs:684:8:684:9 | r2 | provenance | | +| lifetime.rs:684:7:684:14 | TuplePat [tuple.1] | lifetime.rs:684:12:684:13 | r3 | provenance | | +| lifetime.rs:684:8:684:9 | r2 | lifetime.rs:693:13:693:14 | r2 | provenance | | +| lifetime.rs:684:12:684:13 | r3 | lifetime.rs:694:13:694:14 | r3 | provenance | | +| lifetime.rs:686:4:687:16 | TupleExpr [tuple.0] | lifetime.rs:684:7:684:14 | TuplePat [tuple.0] | provenance | | +| lifetime.rs:686:4:687:16 | TupleExpr [tuple.1] | lifetime.rs:684:7:684:14 | TuplePat [tuple.1] | provenance | | +| lifetime.rs:686:5:686:13 | &... | lifetime.rs:686:4:687:16 | TupleExpr [tuple.0] | provenance | | +| lifetime.rs:687:5:687:15 | &... | lifetime.rs:686:4:687:16 | TupleExpr [tuple.1] | provenance | | +| lifetime.rs:724:2:724:12 | &val | lifetime.rs:724:2:724:12 | ptr | provenance | | +| lifetime.rs:724:2:724:12 | ptr | lifetime.rs:725:2:725:12 | ptr | provenance | | +models +| 1 | Summary: lang:core; crate::ptr::from_ref; Argument[0]; ReturnValue; value | +nodes +| deallocation.rs:148:6:148:7 | p1 | semmle.label | p1 | +| deallocation.rs:148:30:148:38 | &raw const my_buffer | semmle.label | &raw const my_buffer | +| deallocation.rs:151:14:151:15 | p1 | semmle.label | p1 | +| deallocation.rs:158:14:158:15 | p1 | semmle.label | p1 | +| deallocation.rs:228:28:228:43 | ...: ... | semmle.label | ...: ... | +| deallocation.rs:230:18:230:20 | ptr | semmle.label | ptr | +| deallocation.rs:240:27:240:42 | ...: ... | semmle.label | ...: ... | +| deallocation.rs:248:18:248:20 | ptr | semmle.label | ptr | +| deallocation.rs:257:7:257:10 | ptr1 | semmle.label | ptr1 | +| deallocation.rs:257:14:257:33 | &raw mut ... | semmle.label | &raw mut ... | +| deallocation.rs:258:7:258:10 | ptr2 | semmle.label | ptr2 | +| deallocation.rs:258:14:258:33 | &raw mut ... | semmle.label | &raw mut ... | +| deallocation.rs:260:4:260:7 | ptr1 | semmle.label | ptr1 | +| deallocation.rs:260:4:260:7 | ptr1 | semmle.label | ptr1 | +| deallocation.rs:261:4:261:7 | ptr2 | semmle.label | ptr2 | +| deallocation.rs:261:4:261:7 | ptr2 | semmle.label | ptr2 | +| deallocation.rs:263:27:263:30 | ptr1 | semmle.label | ptr1 | +| deallocation.rs:265:26:265:29 | ptr2 | semmle.label | ptr2 | +| deallocation.rs:276:6:276:9 | ptr1 | semmle.label | ptr1 | +| deallocation.rs:276:13:276:28 | &raw mut ... | semmle.label | &raw mut ... | +| deallocation.rs:279:13:279:16 | ptr1 | semmle.label | ptr1 | +| deallocation.rs:287:13:287:16 | ptr1 | semmle.label | ptr1 | +| deallocation.rs:295:6:295:9 | ptr2 | semmle.label | ptr2 | +| deallocation.rs:295:13:295:28 | &raw mut ... | semmle.label | &raw mut ... | +| deallocation.rs:298:13:298:16 | ptr2 | semmle.label | ptr2 | +| deallocation.rs:308:13:308:16 | ptr2 | semmle.label | ptr2 | +| lifetime.rs:21:2:21:18 | return ... | semmle.label | return ... | +| lifetime.rs:21:9:21:18 | &my_local1 | semmle.label | &my_local1 | +| lifetime.rs:27:2:27:22 | return ... | semmle.label | return ... | +| lifetime.rs:27:9:27:22 | &mut my_local2 | semmle.label | &mut my_local2 | +| lifetime.rs:33:2:33:28 | return ... | semmle.label | return ... | +| lifetime.rs:33:9:33:28 | &raw const my_local3 | semmle.label | &raw const my_local3 | +| lifetime.rs:39:2:39:26 | return ... | semmle.label | return ... | +| lifetime.rs:39:9:39:26 | &raw mut my_local4 | semmle.label | &raw mut my_local4 | +| lifetime.rs:43:2:43:15 | return ... | semmle.label | return ... | +| lifetime.rs:43:9:43:15 | ¶m5 | semmle.label | ¶m5 | +| lifetime.rs:50:2:50:18 | return ... | semmle.label | return ... | +| lifetime.rs:50:9:50:18 | &... | semmle.label | &... | +| lifetime.rs:54:6:54:7 | p1 | semmle.label | p1 | +| lifetime.rs:54:11:54:30 | get_local_dangling(...) | semmle.label | get_local_dangling(...) | +| lifetime.rs:55:6:55:7 | p2 | semmle.label | p2 | +| lifetime.rs:55:11:55:34 | get_local_dangling_mut(...) | semmle.label | get_local_dangling_mut(...) | +| lifetime.rs:56:6:56:7 | p3 | semmle.label | p3 | +| lifetime.rs:56:11:56:40 | get_local_dangling_raw_const(...) | semmle.label | get_local_dangling_raw_const(...) | +| lifetime.rs:57:6:57:7 | p4 | semmle.label | p4 | +| lifetime.rs:57:11:57:38 | get_local_dangling_raw_mut(...) | semmle.label | get_local_dangling_raw_mut(...) | +| lifetime.rs:58:6:58:7 | p5 | semmle.label | p5 | +| lifetime.rs:58:11:58:31 | get_param_dangling(...) | semmle.label | get_param_dangling(...) | +| lifetime.rs:59:6:59:7 | p6 | semmle.label | p6 | +| lifetime.rs:59:11:59:36 | get_local_field_dangling(...) | semmle.label | get_local_field_dangling(...) | +| lifetime.rs:63:3:63:4 | p7 | semmle.label | p7 | +| lifetime.rs:63:8:63:27 | &raw const my_local7 | semmle.label | &raw const my_local7 | +| lifetime.rs:69:13:69:14 | p1 | semmle.label | p1 | +| lifetime.rs:70:13:70:14 | p2 | semmle.label | p2 | +| lifetime.rs:71:13:71:14 | p3 | semmle.label | p3 | +| lifetime.rs:72:13:72:14 | p4 | semmle.label | p4 | +| lifetime.rs:73:13:73:14 | p5 | semmle.label | p5 | +| lifetime.rs:74:13:74:14 | p6 | semmle.label | p6 | +| 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:127:2:127:24 | return ... | semmle.label | return ... | +| lifetime.rs:127:9:127:24 | &MY_GLOBAL_CONST | semmle.label | &MY_GLOBAL_CONST | +| lifetime.rs:134:3:134:30 | return ... | semmle.label | return ... | +| lifetime.rs:134:10:134:30 | &mut MY_GLOBAL_STATIC | semmle.label | &mut MY_GLOBAL_STATIC | +| lifetime.rs:139:6:139:7 | p1 | semmle.label | p1 | +| lifetime.rs:139:11:139:21 | get_const(...) | semmle.label | get_const(...) | +| lifetime.rs:140:6:140:7 | p2 | semmle.label | p2 | +| lifetime.rs:140:11:140:26 | get_static_mut(...) | semmle.label | get_static_mut(...) | +| lifetime.rs:147:14:147:15 | p1 | semmle.label | p1 | +| lifetime.rs:148:14:148:15 | p2 | semmle.label | p2 | +| lifetime.rs:154:5:154:6 | p2 | semmle.label | p2 | +| 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:217:18:217:25 | ptr_ours | semmle.label | ptr_ours | +| 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 | +| lifetime.rs:251:3:251:6 | prev | semmle.label | prev | +| lifetime.rs:251:10:251:19 | &my_local2 | semmle.label | &my_local2 | +| lifetime.rs:255:14:255:17 | prev | semmle.label | prev | +| lifetime.rs:270:47:275:1 | { ... } | semmle.label | { ... } | +| lifetime.rs:272:6:272:11 | result | semmle.label | result | +| lifetime.rs:272:30:272:32 | &e1 | semmle.label | &e1 | +| lifetime.rs:284:46:300:1 | { ... } | semmle.label | { ... } | +| lifetime.rs:288:2:288:7 | result | semmle.label | result | +| lifetime.rs:289:25:289:26 | &x | semmle.label | &x | +| lifetime.rs:295:13:295:18 | result | semmle.label | result | +| lifetime.rs:303:6:303:7 | e1 | semmle.label | e1 | +| lifetime.rs:303:11:303:31 | get_pointer_to_enum(...) | semmle.label | get_pointer_to_enum(...) | +| lifetime.rs:305:6:305:11 | result | semmle.label | result | +| lifetime.rs:305:15:305:37 | get_pointer_from_enum(...) | semmle.label | get_pointer_from_enum(...) | +| lifetime.rs:310:31:310:32 | e1 | semmle.label | e1 | +| lifetime.rs:317:13:317:18 | result | semmle.label | result | +| lifetime.rs:383:3:383:4 | p1 | semmle.label | p1 | +| lifetime.rs:383:31:383:37 | &raw mut my_pair | semmle.label | &raw mut my_pair | +| lifetime.rs:384:3:384:4 | p2 | semmle.label | p2 | +| lifetime.rs:384:27:384:35 | &raw const ... | semmle.label | &raw const ... | +| lifetime.rs:385:3:385:4 | p3 | semmle.label | p3 | +| lifetime.rs:385:31:385:39 | &raw mut ... | semmle.label | &raw mut ... | +| lifetime.rs:388:15:388:16 | p1 | semmle.label | p1 | +| lifetime.rs:391:15:391:16 | p1 | semmle.label | p1 | +| lifetime.rs:394:14:394:15 | p2 | semmle.label | p2 | +| lifetime.rs:395:14:395:15 | p3 | semmle.label | p3 | +| lifetime.rs:399:6:399:7 | p1 | semmle.label | p1 | +| lifetime.rs:400:5:400:6 | p3 | semmle.label | p3 | +| lifetime.rs:400:5:400:6 | p3 | semmle.label | p3 | +| lifetime.rs:401:6:401:7 | p1 | semmle.label | p1 | +| lifetime.rs:411:16:411:17 | p1 | semmle.label | p1 | +| lifetime.rs:416:16:416:17 | p1 | semmle.label | p1 | +| lifetime.rs:421:15:421:16 | p2 | semmle.label | p2 | +| lifetime.rs:422:15:422:16 | p3 | semmle.label | p3 | +| lifetime.rs:428:7:428:8 | p1 | semmle.label | p1 | +| lifetime.rs:429:6:429:7 | p3 | semmle.label | p3 | +| lifetime.rs:433:7:433:8 | p1 | semmle.label | p1 | +| lifetime.rs:442:6:442:7 | r1 | semmle.label | r1 | +| lifetime.rs:442:17:442:23 | &my_val | semmle.label | &my_val | +| lifetime.rs:443:6:443:7 | p1 | semmle.label | p1 | +| lifetime.rs:443:23:443:44 | ...::from_ref(...) | semmle.label | ...::from_ref(...) | +| lifetime.rs:443:42:443:43 | r1 | semmle.label | r1 | +| lifetime.rs:446:13:446:14 | p1 | semmle.label | p1 | +| lifetime.rs:450:2:450:10 | return p1 | semmle.label | return p1 | +| lifetime.rs:454:6:454:7 | p1 | semmle.label | p1 | +| lifetime.rs:454:11:454:29 | get_ptr_from_ref(...) | semmle.label | get_ptr_from_ref(...) | +| lifetime.rs:459:13:459:14 | p1 | semmle.label | p1 | +| lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | semmle.label | get_ptr_from_ref(...) | +| lifetime.rs:509:16:509:29 | ...: ... | semmle.label | ...: ... | +| lifetime.rs:509:32:509:45 | ...: ... | semmle.label | ...: ... | +| lifetime.rs:512:23:512:32 | &my_local1 | semmle.label | &my_local1 | +| lifetime.rs:514:2:527:2 | return ... [captured p1] | semmle.label | return ... [captured p1] | +| lifetime.rs:514:2:527:2 | return ... [captured p3] | semmle.label | return ... [captured p3] | +| lifetime.rs:514:2:527:2 | return ... [captured p4] | semmle.label | return ... [captured p4] | +| lifetime.rs:515:7:515:8 | p2 | semmle.label | p2 | +| lifetime.rs:515:24:515:33 | &my_local2 | semmle.label | &my_local2 | +| lifetime.rs:518:14:518:15 | p1 | semmle.label | p1 | +| lifetime.rs:519:14:519:15 | p2 | semmle.label | p2 | +| lifetime.rs:520:14:520:15 | p3 | semmle.label | p3 | +| lifetime.rs:521:14:521:15 | p4 | semmle.label | p4 | +| lifetime.rs:530:17:530:31 | ...: ... | semmle.label | ...: ... | +| lifetime.rs:533:10:533:12 | ptr | semmle.label | ptr | +| lifetime.rs:534:3:534:12 | &my_local5 | semmle.label | &my_local5 | +| lifetime.rs:542:3:542:9 | closure [captured p1] | semmle.label | closure [captured p1] | +| lifetime.rs:542:3:542:9 | closure [captured p3] | semmle.label | closure [captured p3] | +| lifetime.rs:542:3:542:9 | closure [captured p4] | semmle.label | closure [captured p4] | +| lifetime.rs:542:13:543:14 | get_closure(...) [captured p1] | semmle.label | get_closure(...) [captured p1] | +| lifetime.rs:542:13:543:14 | get_closure(...) [captured p3] | semmle.label | get_closure(...) [captured p3] | +| lifetime.rs:542:13:543:14 | get_closure(...) [captured p4] | semmle.label | get_closure(...) [captured p4] | +| lifetime.rs:542:26:542:35 | &my_local3 | semmle.label | &my_local3 | +| lifetime.rs:543:4:543:13 | &my_local4 | semmle.label | &my_local4 | +| lifetime.rs:548:2:548:8 | closure [captured p1] | semmle.label | closure [captured p1] | +| lifetime.rs:548:2:548:8 | closure [captured p3] | semmle.label | closure [captured p3] | +| lifetime.rs:548:2:548:8 | closure [captured p4] | semmle.label | closure [captured p4] | +| lifetime.rs:550:15:550:24 | &my_local3 | semmle.label | &my_local3 | +| lifetime.rs:550:28:550:29 | ... | semmle.label | ... | +| lifetime.rs:550:32:550:33 | ... | semmle.label | ... | +| lifetime.rs:552:14:552:15 | p1 | semmle.label | p1 | +| lifetime.rs:553:14:553:15 | p2 | semmle.label | p2 | +| lifetime.rs:568:7:568:8 | p2 | semmle.label | p2 | +| lifetime.rs:568:24:568:33 | &my_local2 | semmle.label | &my_local2 | +| lifetime.rs:572:14:572:15 | p2 | semmle.label | p2 | +| lifetime.rs:630:3:630:6 | str2 | semmle.label | str2 | +| lifetime.rs:630:10:630:25 | &... | semmle.label | &... | +| lifetime.rs:633:15:633:18 | str2 | semmle.label | str2 | +| lifetime.rs:641:14:641:17 | str2 | semmle.label | str2 | +| lifetime.rs:654:4:654:7 | str2 | semmle.label | str2 | +| lifetime.rs:654:31:654:35 | &str1 | semmle.label | &str1 | +| lifetime.rs:655:4:655:7 | ref1 | semmle.label | ref1 | +| lifetime.rs:655:4:655:7 | ref1 [&ref] | semmle.label | ref1 [&ref] | +| lifetime.rs:655:11:655:25 | &raw const str2 | semmle.label | &raw const str2 | +| lifetime.rs:655:11:655:25 | &raw const str2 [&ref] | semmle.label | &raw const str2 [&ref] | +| lifetime.rs:655:22:655:25 | str2 | semmle.label | str2 | +| lifetime.rs:659:15:659:18 | ref1 | semmle.label | ref1 | +| lifetime.rs:667:14:667:17 | ref1 | semmle.label | ref1 | +| lifetime.rs:680:7:680:8 | r1 | semmle.label | r1 | +| lifetime.rs:682:4:682:12 | &... | semmle.label | &... | +| lifetime.rs:684:7:684:14 | TuplePat [tuple.0] | semmle.label | TuplePat [tuple.0] | +| lifetime.rs:684:7:684:14 | TuplePat [tuple.1] | semmle.label | TuplePat [tuple.1] | +| lifetime.rs:684:8:684:9 | r2 | semmle.label | r2 | +| lifetime.rs:684:12:684:13 | r3 | semmle.label | r3 | +| lifetime.rs:686:4:687:16 | TupleExpr [tuple.0] | semmle.label | TupleExpr [tuple.0] | +| lifetime.rs:686:4:687:16 | TupleExpr [tuple.1] | semmle.label | TupleExpr [tuple.1] | +| lifetime.rs:686:5:686:13 | &... | semmle.label | &... | +| lifetime.rs:687:5:687:15 | &... | semmle.label | &... | +| lifetime.rs:692:13:692:14 | r1 | semmle.label | r1 | +| lifetime.rs:693:13:693:14 | r2 | semmle.label | r2 | +| lifetime.rs:694:13:694:14 | r3 | semmle.label | r3 | +| lifetime.rs:724:2:724:12 | &val | semmle.label | &val | +| lifetime.rs:724:2:724:12 | ptr | semmle.label | ptr | +| lifetime.rs:725:2:725:12 | ptr | semmle.label | ptr | +subpaths +| lifetime.rs:542:26:542:35 | &my_local3 | lifetime.rs:509:16:509:29 | ...: ... | lifetime.rs:514:2:527:2 | return ... [captured p3] | lifetime.rs:542:13:543:14 | get_closure(...) [captured p3] | +| lifetime.rs:543:4:543:13 | &my_local4 | lifetime.rs:509:32:509:45 | ...: ... | lifetime.rs:514:2:527:2 | return ... [captured p4] | lifetime.rs:542:13:543:14 | get_closure(...) [captured p4] | 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 25aedb5eb44c..05a994bceb3e 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -18,36 +18,36 @@ impl Drop for MyValue { fn get_local_dangling() -> *const i64 { let my_local1: i64 = 1; - return &my_local1; + return &my_local1; // $ Source[rust/access-after-lifetime-ended]=local1 } // (return value immediately becomes dangling) fn get_local_dangling_mut() -> *mut i64 { let mut my_local2: i64 = 2; - return &mut my_local2; + return &mut my_local2; // $ Source[rust/access-after-lifetime-ended]=local2 } // (return value immediately becomes dangling) fn get_local_dangling_raw_const() -> *const i64 { let my_local3: i64 = 3; - return &raw const my_local3; + return &raw const my_local3; // $ Source[rust/access-after-lifetime-ended]=local3 } // (return value immediately becomes dangling) fn get_local_dangling_raw_mut() -> *mut i64 { let mut my_local4: i64 = 4; - return &raw mut my_local4; + return &raw mut my_local4; // $ Source[rust/access-after-lifetime-ended]=local4 } // (return value immediately becomes dangling) fn get_param_dangling(param5: i64) -> *const i64 { - return ¶m5; + return ¶m5; // $ MISSING: Source[rust/access-after-lifetime-ended]=param5 } // (return value immediately becomes dangling) fn get_local_field_dangling() -> *const i64 { let val: MyValue; val = MyValue { value: 6 }; - return &val.value; + return &val.value; // $ Source[rust/access-after-lifetime-ended]=localfield } pub fn test_local_dangling() { @@ -60,21 +60,21 @@ pub fn test_local_dangling() { let p7: *const i64; { let my_local7 = 7; - p7 = &raw const my_local7; + p7 = &raw const my_local7; // $ Source[rust/access-after-lifetime-ended]=local7 } // (my_local goes out of scope, thus p7 is dangling) use_the_stack(); unsafe { - let v1 = *p1; // $ MISSING: Alert - let v2 = *p2; // $ MISSING: Alert - let v3 = *p3; // $ MISSING: Alert - let v4 = *p4; // $ MISSING: Alert - let v5 = *p5; // $ MISSING: Alert - let v6 = *p6; // $ MISSING: Alert - let v7 = *p7; // $ MISSING: Alert - *p2 = 8; // $ MISSING: Alert - *p4 = 9; // $ MISSING: Alert + let v1 = *p1; // $ Alert[rust/access-after-lifetime-ended]=local1 + let v2 = *p2; // $ Alert[rust/access-after-lifetime-ended]=local2 + let v3 = *p3; // $ Alert[rust/access-after-lifetime-ended]=local3 + let v4 = *p4; // $ Alert[rust/access-after-lifetime-ended]=local4 + let v5 = *p5; // $ MISSING: Alert[rust/access-after-lifetime-ended]=param5 + let v6 = *p6; // $ Alert[rust/access-after-lifetime-ended]=localfield + let v7 = *p7; // $ Alert[rust/access-after-lifetime-ended]=local7 + *p2 = 8; // $ Alert[rust/access-after-lifetime-ended]=local2 + *p4 = 9; // $ Alert[rust/access-after-lifetime-ended]=local4 println!(" v1 = {v1} (!)"); // corrupt in practice println!(" v2 = {v2} (!)"); // corrupt in practice @@ -169,7 +169,7 @@ fn access_ptr_1(ptr: *const i64) { fn access_ptr_2(ptr: *const i64) { // only called with `ptr` dangling unsafe { - let v2 = *ptr; // $ MISSING: Alert + let v2 = *ptr; // $ Alert[rust/access-after-lifetime-ended]=local1 println!(" v2 = {v2} (!)"); // corrupt in practice } } @@ -184,7 +184,7 @@ fn access_ptr_3(ptr: *const i64) { fn access_and_get_dangling() -> *const i64 { let my_local1 = 1; - let ptr = &my_local1; + let ptr = &my_local1; // $ Source[rust/access-after-lifetime-ended]=local1 access_ptr_1(ptr); access_ptr_3(ptr); @@ -244,15 +244,15 @@ pub fn test_loop() { use_the_stack(); unsafe { - let v1 = (*prev)[0]; // $ MISSING: Alert + let v1 = (*prev)[0]; // $ MISSING: Alert[rust/access-after-lifetime-ended]=local2 println!(" v1 = {v1} (!)"); // incorrect values in practice (except first iteration) } - prev = &my_local2; + prev = &my_local2; // $ Source[rust/access-after-lifetime-ended]=local2 } // (my_local2 goes out of scope, thus prev is dangling) unsafe { - let v2 = (*prev)[0]; // $ MISSING: Alert + let v2 = (*prev)[0]; // $ Alert[rust/access-after-lifetime-ended]=local2 println!(" v2 = {v2} (!)"); // corrupt in practice } } @@ -269,7 +269,7 @@ enum MyEnum2 { pub fn get_pointer_to_enum() -> *const MyEnum { let e1 = MyEnum::Value(1); - let result: *const MyEnum = &e1; // $ MISSING: Source[rust/access-after-lifetime-ended]=e1 + let result: *const MyEnum = &e1; // $ Source[rust/access-after-lifetime-ended]=e1 result } // (e1 goes out of scope, so result is dangling) @@ -286,7 +286,7 @@ pub fn get_pointer_from_enum() -> *const i64 { let result: *const i64; result = match e3 { - MyEnum::Value(x) => { &x } // $ MISSING: Source[rust/access-after-lifetime-ended]=match_x + MyEnum::Value(x) => { &x } // $ Source[rust/access-after-lifetime-ended]=match_x }; // (x goes out of scope, so result is possibly dangling already) use_the_stack(); @@ -307,14 +307,14 @@ pub fn test_enums() { use_the_stack(); unsafe { - if let MyEnum::Value(v1) = *e1 { // $ MISSING: Alert[rust/access-after-lifetime-ended]=e1 + if let MyEnum::Value(v1) = *e1 { // $ Alert[rust/access-after-lifetime-ended]=e1 println!(" v1 = {v1} (!)"); // corrupt in practice } if let MyEnum2::Pointer(p2) = e2 { let v2 = unsafe { *p2 }; // $ MISSING: Alert[rust/access-after-lifetime-ended]=v2 println!(" v2 = {v2} (!)"); // corrupt in practice } - let v3 = *result; // $ MISSING: Alert[rust/access-after-lifetime-ended]=match_x + let v3 = *result; // $ Alert[rust/access-after-lifetime-ended]=match_x println!(" v3 = {v3} (!)"); // corrupt in practice } } @@ -380,9 +380,9 @@ pub fn test_ptr_to_struct(mode: i32) { { let mut my_pair = MyPair { a: 1, b: 2}; - p1 = std::ptr::addr_of_mut!(my_pair); - p2 = std::ptr::addr_of!(my_pair.a); - p3 = std::ptr::addr_of_mut!(my_pair.b); + p1 = std::ptr::addr_of_mut!(my_pair); // $ Source[rust/access-after-lifetime-ended]=my_pair + p2 = std::ptr::addr_of!(my_pair.a); // $ MISSING: Source[rust/access-after-lifetime-ended]=my_pair_a + p3 = std::ptr::addr_of_mut!(my_pair.b); // $ MISSING: Source[rust/access-after-lifetime-ended]=my_pair_b unsafe { let v1 = (*p1).a; // GOOD @@ -408,12 +408,12 @@ pub fn test_ptr_to_struct(mode: i32) { match mode { 0 => { // read - let v5 = (*p1).a; // $ MISSING: Alert + let v5 = (*p1).a; // $ Alert[rust/access-after-lifetime-ended]=my_pair println!(" v5 = {v5} (!)"); // dropped in practice }, 220 => { // another read - let v6 = (*p1).b; // $ MISSING: Alert + let v6 = (*p1).b; // $ Alert[rust/access-after-lifetime-ended]=my_pair println!(" v6 = {v6} (!)"); // dropped in practice }, 221 => { @@ -425,12 +425,12 @@ pub fn test_ptr_to_struct(mode: i32) { }, 222 => { // writes - (*p1).a = 6; // $ MISSING: Alert + (*p1).a = 6; // $ Alert[rust/access-after-lifetime-ended]=my_pair *p3 = 7; // $ MISSING: Alert }, 223 => { // another write - (*p1).b = 8; // $ MISSING: Alert + (*p1).b = 8; // $ Alert[rust/access-after-lifetime-ended]=my_pair }, _ => {} } @@ -439,7 +439,7 @@ pub fn test_ptr_to_struct(mode: i32) { fn get_ptr_from_ref(val: i32) -> *const i32 { let my_val = val; - let r1: &i32 = &my_val; + let r1: &i32 = &my_val; // $ Source[rust/access-after-lifetime-ended]=my_val let p1: *const i32 = std::ptr::from_ref(r1); unsafe { @@ -456,8 +456,8 @@ pub fn test_ptr_from_ref() { use_the_stack(); unsafe { - let v2 = *p1; // $ MISSING: Alert - let v3 = *get_ptr_from_ref(2); // $ MISSING: Alert + let v2 = *p1; // $ Alert[rust/access-after-lifetime-ended]=my_val + let v3 = *get_ptr_from_ref(2); // $ Alert[rust/access-after-lifetime-ended]=my_val println!(" v2 = {v2} (!)"); // corrupt in practice println!(" v3 = {v3} (!)"); } @@ -509,16 +509,16 @@ pub fn test_rc() { fn get_closure(p3: *const i64, p4: *const i64) -> impl FnOnce() { let my_local1: i64 = 1; let my_local2: i64 = 2; - let p1: *const i64 = &my_local1; + let p1: *const i64 = &my_local1; // $ MISSING: Source[rust/access-after-lifetime-ended]=local1 return move || { // captures `my_local2`, `p1`, `p3`, `p4` by value (due to `move`) let p2: *const i64 = &my_local2; unsafe { - let v1 = *p1; // $ MISSING: Alert + let v1 = *p1; // $ MISSING: Alert[rust/access-after-lifetime-ended]=local1 let v2 = *p2; // GOOD - let v3 = *p3; // GOOD - let v4 = *p4; // $ MISSING: Alert + let v3 = *p3; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=local3 + let v4 = *p4; // $ Alert[rust/access-after-lifetime-ended]=local4 println!(" v1 = {v1} (!)"); // corrupt in practice println!(" v2 = {v2}"); println!(" v3 = {v3}"); @@ -531,7 +531,7 @@ fn with_closure(ptr: *const i64, closure: fn(*const i64, *const i64)) { let my_local5: i64 = 5; closure(ptr, - &my_local5); + &my_local5); // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=local5 } pub fn test_closures() { @@ -539,8 +539,8 @@ pub fn test_closures() { let my_local3: i64 = 3; { let my_local4: i64 = 4; - closure = get_closure( &my_local3, - &my_local4); + closure = get_closure( &my_local3, // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=local3 + &my_local4); // $ Source[rust/access-after-lifetime-ended]=local4 } // (`my_local4` goes out of scope, so `p4` is dangling) use_the_stack(); @@ -550,7 +550,7 @@ pub fn test_closures() { with_closure(&my_local3, |p1, p2| { unsafe { let v5 = *p1; // GOOD - let v6 = *p2; // GOOD + let v6 = *p2; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=local5 println!(" v5 = {v5}"); println!(" v6 = {v6}"); } @@ -651,12 +651,12 @@ pub fn test_implicit_derefs() { let str2; { let str1 = "bar"; - str2 = "foo".to_string() + &str1; // $ MISSING: Source[rust/access-after-lifetime-ended]=str1 - ref1 = &raw const str2; // $ MISSING: Source[rust/access-after-lifetime-ended]=str2 + str2 = "foo".to_string() + &str1; // $ Source[rust/access-after-lifetime-ended]=str1 + ref1 = &raw const str2; // $ Source[rust/access-after-lifetime-ended]=str2 } // (str1 goes out of scope, but it's been copied into str2) unsafe { - let v1 = &*ref1; // GOOD + let v1 = &*ref1; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=str1 println!(" v1 = {v1}"); } } // (str2 goes out of scope, thus ref1 is dangling) @@ -664,7 +664,7 @@ pub fn test_implicit_derefs() { use_the_stack(); unsafe { - let v2 = &*ref1; // $ MISSING: Alert[rust/access-after-lifetime-ended]=str2 + let v2 = &*ref1; // $ Alert[rust/access-after-lifetime-ended]=str2 SPURIOUS: Alert[rust/access-after-lifetime-ended]=str1 println!(" v2 = {v2} (!)"); // corrupt in practice } } @@ -679,18 +679,18 @@ impl MyType { fn test(&self) { let r1 = unsafe { let v1 = &self; - &v1.value + &v1.value // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=v1 }; let (r2, r3) = unsafe { let v2 = &self; - (&v2.value, + (&v2.value, // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=v2 &self.value) }; use_the_stack(); - let v1 = *r1; - let v2 = *r2; + let v1 = *r1; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=v1 + let v2 = *r2; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=v2 let v3 = *r3; println!(" v1 = {v1}"); println!(" v2 = {v2}"); @@ -717,9 +717,10 @@ macro_rules! my_macro { let v = *ptr; println!(" v = {v}"); } - }; + } } pub fn test_macros() { - my_macro!(); + my_macro!(); // $ SPURIOUS: Source[rust/access-after-lifetime-ended] + my_macro!(); // $ SPURIOUS: Alert[rust/access-after-lifetime-ended] } From 79f8584efb66b84aaa5f33f1021c23ee7b9b2793 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:57:07 +0100 Subject: [PATCH 10/34] Rust: Fix spurious results involving closures. --- .../rust/security/AccessAfterLifetimeExtensions.qll | 5 ++++- .../security/CWE-825/AccessAfterLifetime.expected | 3 --- .../ql/test/query-tests/security/CWE-825/lifetime.rs | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index e2b0643d9699..63041eaf9c49 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -47,7 +47,10 @@ module AccessAfterLifetime { exists(BlockExpr valueScope, BlockExpr accessScope | valueScope(source.getTargetValue(), valueScope) and accessScope = sink.asExpr().getExpr().getEnclosingBlock() and - not maybeOnStack(valueScope, accessScope) + not maybeOnStack(valueScope, accessScope) and + // exclude results where the access is in a closure, since we don't + // model where a closure is actually called here. + not accessScope.getEnclosingBlock*() = any(ClosureExpr ce).getBody() ) } 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 9e12cf83794a..aef7b45d26e9 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -17,9 +17,6 @@ | lifetime.rs:433:7:433:8 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:433:7:433:8 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:383:31:383:37 | my_pair | my_pair | | lifetime.rs:459:13:459:14 | p1 | lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:459:13:459:14 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:442:18:442:23 | my_val | my_val | | lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:442:18:442:23 | my_val | my_val | -| lifetime.rs:520:14:520:15 | p3 | lifetime.rs:542:26:542:35 | &my_local3 | lifetime.rs:520:14:520:15 | p3 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:542:27:542:35 | my_local3 | my_local3 | -| lifetime.rs:521:14:521:15 | p4 | lifetime.rs:543:4:543:13 | &my_local4 | lifetime.rs:521:14:521:15 | p4 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:543:5:543:13 | my_local4 | my_local4 | -| lifetime.rs:553:14:553:15 | p2 | lifetime.rs:534:3:534:12 | &my_local5 | lifetime.rs:553:14:553:15 | p2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:534:4:534:12 | my_local5 | my_local5 | | lifetime.rs:659:15:659:18 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:659:15:659:18 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:654:32:654:35 | str1 | str1 | | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:654:32:654:35 | str1 | str1 | | 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 it's lifetime has ended. | lifetime.rs:655:22:655:25 | str2 | str2 | 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 05a994bceb3e..52e65d9bcda2 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -517,8 +517,8 @@ fn get_closure(p3: *const i64, p4: *const i64) -> impl FnOnce() { unsafe { let v1 = *p1; // $ MISSING: Alert[rust/access-after-lifetime-ended]=local1 let v2 = *p2; // GOOD - let v3 = *p3; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=local3 - let v4 = *p4; // $ Alert[rust/access-after-lifetime-ended]=local4 + let v3 = *p3; // GOOD + let v4 = *p4; // $ MISSING: Alert[rust/access-after-lifetime-ended]=local4 println!(" v1 = {v1} (!)"); // corrupt in practice println!(" v2 = {v2}"); println!(" v3 = {v3}"); @@ -531,7 +531,7 @@ fn with_closure(ptr: *const i64, closure: fn(*const i64, *const i64)) { let my_local5: i64 = 5; closure(ptr, - &my_local5); // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=local5 + &my_local5); } pub fn test_closures() { @@ -539,8 +539,8 @@ pub fn test_closures() { let my_local3: i64 = 3; { let my_local4: i64 = 4; - closure = get_closure( &my_local3, // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=local3 - &my_local4); // $ Source[rust/access-after-lifetime-ended]=local4 + closure = get_closure( &my_local3, + &my_local4); // $ MISSING: Source[rust/access-after-lifetime-ended]=local4 } // (`my_local4` goes out of scope, so `p4` is dangling) use_the_stack(); @@ -550,7 +550,7 @@ pub fn test_closures() { with_closure(&my_local3, |p1, p2| { unsafe { let v5 = *p1; // GOOD - let v6 = *p2; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=local5 + let v6 = *p2; // $ GOOD println!(" v5 = {v5}"); println!(" v6 = {v6}"); } From 21b4baeb4251c85fc97cad342fb18b68ff5475c7 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Jun 2025 19:33:49 +0100 Subject: [PATCH 11/34] Rust: Have the alert message cite the variable, so it's easier to understand whether the alert is correct. --- .../AccessAfterLifetimeExtensions.qll | 23 +++++----- .../security/CWE-825/AccessAfterLifetime.ql | 7 ++- .../CWE-825/AccessAfterLifetime.expected | 46 +++++++++---------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index 63041eaf9c49..630763b9e365 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -22,7 +22,7 @@ module AccessAfterLifetime { /** * Gets the value this pointer or reference points to. */ - abstract Expr getTargetValue(); + abstract Expr getTarget(); } /** @@ -38,14 +38,14 @@ module AccessAfterLifetime { abstract class Barrier extends DataFlow::Node { } /** - * Holds if the pair `(source, sink)` that represents a flow from a - * pointer or reference to a dereference of that pointer or reference, - * and the dereference is outside the lifetime of the target value. + * 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`. */ bindingset[source, sink] - predicate dereferenceAfterLifetime(Source source, Sink sink) { + predicate dereferenceAfterLifetime(Source source, Sink sink, Variable target) { exists(BlockExpr valueScope, BlockExpr accessScope | - valueScope(source.getTargetValue(), valueScope) and + valueScope(source.getTarget(), target, valueScope) and accessScope = sink.asExpr().getExpr().getEnclosingBlock() and not maybeOnStack(valueScope, accessScope) and // exclude results where the access is in a closure, since we don't @@ -55,14 +55,15 @@ module AccessAfterLifetime { } /** - * Holds if `value` accesses a variable with scope `scope`. + * Holds if `value` accesses a variable `target` with scope `scope`. */ - private predicate valueScope(Expr value, BlockExpr scope) { + private predicate valueScope(Expr value, Variable target, BlockExpr scope) { // variable access - scope = value.(VariableAccess).getVariable().getEnclosingBlock() + target = value.(VariableAccess).getVariable() and + scope = target.getEnclosingBlock() or // field access - valueScope(value.(FieldExpr).getContainer(), scope) + valueScope(value.(FieldExpr).getContainer(), target, scope) } /** @@ -91,6 +92,6 @@ module AccessAfterLifetime { RefExprSource() { this.asExpr().getExpr().(RefExpr).getExpr() = targetValue } - override Expr getTargetValue() { result = targetValue } + override Expr getTarget() { result = targetValue } } } diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql index b49d8b59df3f..a7f9da347832 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -34,12 +34,11 @@ module AccessAfterLifetimeFlow = TaintTracking::Global Date: Mon, 2 Jun 2025 19:57:16 +0100 Subject: [PATCH 12/34] Rust: More robust fix for closures. --- .../AccessAfterLifetimeExtensions.qll | 13 +++-- .../CWE-825/AccessAfterLifetime.expected | 58 ------------------- 2 files changed, 9 insertions(+), 62 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index 630763b9e365..2534a5acdb31 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -47,10 +47,7 @@ module AccessAfterLifetime { exists(BlockExpr valueScope, BlockExpr accessScope | valueScope(source.getTarget(), target, valueScope) and accessScope = sink.asExpr().getExpr().getEnclosingBlock() and - not maybeOnStack(valueScope, accessScope) and - // exclude results where the access is in a closure, since we don't - // model where a closure is actually called here. - not accessScope.getEnclosingBlock*() = any(ClosureExpr ce).getBody() + not maybeOnStack(valueScope, accessScope) ) } @@ -94,4 +91,12 @@ module AccessAfterLifetime { override Expr getTarget() { result = targetValue } } + + /** + * A barrier for nodes inside closures, as we don't model lifetimes of + * variables through closures properly. + */ + private class ClosureBarrier extends Barrier { + ClosureBarrier() { this.asExpr().getExpr().getEnclosingCallable() instanceof ClosureExpr } + } } 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 e6479ca141ad..50e450af0589 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -161,31 +161,6 @@ edges | lifetime.rs:450:2:450:10 | return p1 | lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | provenance | | | lifetime.rs:454:6:454:7 | p1 | lifetime.rs:459:13:459:14 | p1 | provenance | | | lifetime.rs:454:11:454:29 | get_ptr_from_ref(...) | lifetime.rs:454:6:454:7 | p1 | provenance | | -| lifetime.rs:509:16:509:29 | ...: ... | lifetime.rs:514:2:527:2 | return ... [captured p3] | provenance | | -| lifetime.rs:509:32:509:45 | ...: ... | lifetime.rs:514:2:527:2 | return ... [captured p4] | provenance | | -| lifetime.rs:512:23:512:32 | &my_local1 | lifetime.rs:514:2:527:2 | return ... [captured p1] | provenance | | -| lifetime.rs:514:2:527:2 | return ... [captured p1] | lifetime.rs:542:13:543:14 | get_closure(...) [captured p1] | provenance | | -| lifetime.rs:515:7:515:8 | p2 | lifetime.rs:519:14:519:15 | p2 | provenance | | -| lifetime.rs:515:24:515:33 | &my_local2 | lifetime.rs:515:7:515:8 | p2 | provenance | | -| lifetime.rs:530:17:530:31 | ...: ... | lifetime.rs:533:10:533:12 | ptr | provenance | | -| lifetime.rs:533:10:533:12 | ptr | lifetime.rs:550:28:550:29 | ... | provenance | | -| lifetime.rs:534:3:534:12 | &my_local5 | lifetime.rs:550:32:550:33 | ... | provenance | | -| lifetime.rs:542:3:542:9 | closure [captured p1] | lifetime.rs:548:2:548:8 | closure [captured p1] | provenance | | -| lifetime.rs:542:3:542:9 | closure [captured p3] | lifetime.rs:548:2:548:8 | closure [captured p3] | provenance | | -| lifetime.rs:542:3:542:9 | closure [captured p4] | lifetime.rs:548:2:548:8 | closure [captured p4] | provenance | | -| lifetime.rs:542:13:543:14 | get_closure(...) [captured p1] | lifetime.rs:542:3:542:9 | closure [captured p1] | provenance | | -| lifetime.rs:542:13:543:14 | get_closure(...) [captured p3] | lifetime.rs:542:3:542:9 | closure [captured p3] | provenance | | -| lifetime.rs:542:13:543:14 | get_closure(...) [captured p4] | lifetime.rs:542:3:542:9 | closure [captured p4] | provenance | | -| lifetime.rs:542:26:542:35 | &my_local3 | lifetime.rs:509:16:509:29 | ...: ... | provenance | | -| lifetime.rs:542:26:542:35 | &my_local3 | lifetime.rs:542:13:543:14 | get_closure(...) [captured p3] | provenance | | -| lifetime.rs:543:4:543:13 | &my_local4 | lifetime.rs:509:32:509:45 | ...: ... | provenance | | -| lifetime.rs:543:4:543:13 | &my_local4 | lifetime.rs:542:13:543:14 | get_closure(...) [captured p4] | provenance | | -| lifetime.rs:548:2:548:8 | closure [captured p1] | lifetime.rs:518:14:518:15 | p1 | provenance | | -| lifetime.rs:548:2:548:8 | closure [captured p3] | lifetime.rs:520:14:520:15 | p3 | provenance | | -| lifetime.rs:548:2:548:8 | closure [captured p4] | lifetime.rs:521:14:521:15 | p4 | provenance | | -| lifetime.rs:550:15:550:24 | &my_local3 | lifetime.rs:530:17:530:31 | ...: ... | provenance | | -| lifetime.rs:550:28:550:29 | ... | lifetime.rs:552:14:552:15 | p1 | provenance | | -| lifetime.rs:550:32:550:33 | ... | lifetime.rs:553:14:553:15 | p2 | provenance | | | lifetime.rs:568:7:568:8 | p2 | lifetime.rs:572:14:572:15 | p2 | provenance | | | lifetime.rs:568:24:568:33 | &my_local2 | lifetime.rs:568:7:568:8 | p2 | provenance | | | lifetime.rs:630:3:630:6 | str2 | lifetime.rs:633:15:633:18 | str2 | provenance | | @@ -376,37 +351,6 @@ nodes | lifetime.rs:454:11:454:29 | get_ptr_from_ref(...) | semmle.label | get_ptr_from_ref(...) | | lifetime.rs:459:13:459:14 | p1 | semmle.label | p1 | | lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | semmle.label | get_ptr_from_ref(...) | -| lifetime.rs:509:16:509:29 | ...: ... | semmle.label | ...: ... | -| lifetime.rs:509:32:509:45 | ...: ... | semmle.label | ...: ... | -| lifetime.rs:512:23:512:32 | &my_local1 | semmle.label | &my_local1 | -| lifetime.rs:514:2:527:2 | return ... [captured p1] | semmle.label | return ... [captured p1] | -| lifetime.rs:514:2:527:2 | return ... [captured p3] | semmle.label | return ... [captured p3] | -| lifetime.rs:514:2:527:2 | return ... [captured p4] | semmle.label | return ... [captured p4] | -| lifetime.rs:515:7:515:8 | p2 | semmle.label | p2 | -| lifetime.rs:515:24:515:33 | &my_local2 | semmle.label | &my_local2 | -| lifetime.rs:518:14:518:15 | p1 | semmle.label | p1 | -| lifetime.rs:519:14:519:15 | p2 | semmle.label | p2 | -| lifetime.rs:520:14:520:15 | p3 | semmle.label | p3 | -| lifetime.rs:521:14:521:15 | p4 | semmle.label | p4 | -| lifetime.rs:530:17:530:31 | ...: ... | semmle.label | ...: ... | -| lifetime.rs:533:10:533:12 | ptr | semmle.label | ptr | -| lifetime.rs:534:3:534:12 | &my_local5 | semmle.label | &my_local5 | -| lifetime.rs:542:3:542:9 | closure [captured p1] | semmle.label | closure [captured p1] | -| lifetime.rs:542:3:542:9 | closure [captured p3] | semmle.label | closure [captured p3] | -| lifetime.rs:542:3:542:9 | closure [captured p4] | semmle.label | closure [captured p4] | -| lifetime.rs:542:13:543:14 | get_closure(...) [captured p1] | semmle.label | get_closure(...) [captured p1] | -| lifetime.rs:542:13:543:14 | get_closure(...) [captured p3] | semmle.label | get_closure(...) [captured p3] | -| lifetime.rs:542:13:543:14 | get_closure(...) [captured p4] | semmle.label | get_closure(...) [captured p4] | -| lifetime.rs:542:26:542:35 | &my_local3 | semmle.label | &my_local3 | -| lifetime.rs:543:4:543:13 | &my_local4 | semmle.label | &my_local4 | -| lifetime.rs:548:2:548:8 | closure [captured p1] | semmle.label | closure [captured p1] | -| lifetime.rs:548:2:548:8 | closure [captured p3] | semmle.label | closure [captured p3] | -| lifetime.rs:548:2:548:8 | closure [captured p4] | semmle.label | closure [captured p4] | -| lifetime.rs:550:15:550:24 | &my_local3 | semmle.label | &my_local3 | -| lifetime.rs:550:28:550:29 | ... | semmle.label | ... | -| lifetime.rs:550:32:550:33 | ... | semmle.label | ... | -| lifetime.rs:552:14:552:15 | p1 | semmle.label | p1 | -| lifetime.rs:553:14:553:15 | p2 | semmle.label | p2 | | lifetime.rs:568:7:568:8 | p2 | semmle.label | p2 | | lifetime.rs:568:24:568:33 | &my_local2 | semmle.label | &my_local2 | | lifetime.rs:572:14:572:15 | p2 | semmle.label | p2 | @@ -440,5 +384,3 @@ nodes | lifetime.rs:724:2:724:12 | ptr | semmle.label | ptr | | lifetime.rs:725:2:725:12 | ptr | semmle.label | ptr | subpaths -| lifetime.rs:542:26:542:35 | &my_local3 | lifetime.rs:509:16:509:29 | ...: ... | lifetime.rs:514:2:527:2 | return ... [captured p3] | lifetime.rs:542:13:543:14 | get_closure(...) [captured p3] | -| lifetime.rs:543:4:543:13 | &my_local4 | lifetime.rs:509:32:509:45 | ...: ... | lifetime.rs:514:2:527:2 | return ... [captured p4] | lifetime.rs:542:13:543:14 | get_closure(...) [captured p4] | From 26f85585fd555e5ab7b14261385a3608cdeec7f3 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 3 Jun 2025 17:40:16 +0100 Subject: [PATCH 13/34] Rust: Add qhelp, examples, and examples as tests. --- .../CWE-825/AccessAfterLifetime.qhelp | 50 +++++++++++++++++++ .../CWE-825/AccessAfterLifetimeBad.rs | 19 +++++++ .../CWE-825/AccessAfterLifetimeGood.rs | 17 +++++++ .../CWE-825/AccessAfterLifetime.expected | 10 ++++ .../query-tests/security/CWE-825/lifetime.rs | 38 ++++++++++++++ .../test/query-tests/security/CWE-825/main.rs | 6 +++ 6 files changed, 140 insertions(+) create mode 100644 rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp create mode 100644 rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs create mode 100644 rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp new file mode 100644 index 000000000000..41d62af44e34 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp @@ -0,0 +1,50 @@ + + + + +

+Dereferencing a pointer after the lifetime of its target has ended causes undefined behavior. Memory +may be corrupted causing the program to crash or behave incorrectly, in some cases exposing the program +to potential attacks. +

+ +
+ + +

+When dereferencing a pointer in unsafe code, take care that the pointer is still valid +at the time it is dereferenced. Code may need to be rearranged or changed to extend lifetimes. If +possible, rewrite the code using safe Rust types to avoid this kind of problem altogether. +

+ +
+ + +

+In the following example, val is local to get_pointer so its lifetime +ends when that function returns. However, a pointer to val is returned and dereferenced +after that lifetime has ended, causing undefined behavior: +

+ + + +

+One way to fix this is to change the return type of the function from a pointer to a Box, +which ensures that the value it points to remains on the heap for the lifetime of the Box +itself. Notice that there is no longer a need for an unsafe block as the code no longer +handles pointers directly: +

+ + + +
+ + +
  • Rust Documentation: Behavior considered undefined >> Dangling pointers.
  • +
  • Rust Documentation: Module ptr - Safety.
  • +
  • Massachusetts Institute of Technology: Unsafe Rust - Dereferencing a Raw Pointer.
  • + +
    +
    diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs new file mode 100644 index 000000000000..61f981e40199 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs @@ -0,0 +1,19 @@ + +fn get_pointer() -> *const i64 { + let val = 123; + + return &val; +} // lifetime of `val` ends here, the pointer becomes dangling + +fn example() { + let ptr = get_pointer(); + let val; + + // ... + + unsafe { + val = *ptr; // BAD: dereferences `ptr` after the lifetime of `val` has ended + } + + // ... +} diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs new file mode 100644 index 000000000000..e8d0017d007b --- /dev/null +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs @@ -0,0 +1,17 @@ + +fn get_box() -> Box { + let val = 123; + + return Box::new(val); // copies `val` onto the heap, where it remains for the lifetime of the `Box`. +} + +fn example() { + let ptr = get_box(); + let val; + + // ... + + val = *ptr; // GOOD + + // ... +} 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 50e450af0589..c54369b8c736 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -23,6 +23,7 @@ | lifetime.rs:692:13:692:14 | r1 | lifetime.rs:682:4:682:12 | &... | lifetime.rs:692:13:692:14 | r1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:681:8:681:9 | v1 | v1 | | lifetime.rs:693:13:693:14 | r2 | lifetime.rs:686:5:686:13 | &... | lifetime.rs:693:13:693:14 | r2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:685:8:685:9 | v2 | v2 | | lifetime.rs:725:2:725:12 | ptr | lifetime.rs:724:2:724:12 | &val | lifetime.rs:725:2:725:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:724:2:724:12 | val | val | +| lifetime.rs:743:10:743:12 | ptr | lifetime.rs:733:9:733:12 | &val | lifetime.rs:743:10:743:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:731:6:731:8 | val | val | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151:15 | p1 | provenance | | | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:158:14:158:15 | p1 | provenance | | @@ -187,6 +188,10 @@ edges | lifetime.rs:687:5:687:15 | &... | lifetime.rs:686:4:687:16 | TupleExpr [tuple.1] | provenance | | | lifetime.rs:724:2:724:12 | &val | lifetime.rs:724:2:724:12 | ptr | provenance | | | lifetime.rs:724:2:724:12 | ptr | lifetime.rs:725:2:725:12 | ptr | provenance | | +| lifetime.rs:733:2:733:12 | return ... | lifetime.rs:737:12:737:24 | get_pointer(...) | provenance | | +| lifetime.rs:733:9:733:12 | &val | lifetime.rs:733:2:733:12 | return ... | provenance | | +| lifetime.rs:737:6:737:8 | ptr | lifetime.rs:743:10:743:12 | ptr | provenance | | +| lifetime.rs:737:12:737:24 | get_pointer(...) | lifetime.rs:737:6:737:8 | ptr | provenance | | models | 1 | Summary: lang:core; crate::ptr::from_ref; Argument[0]; ReturnValue; value | nodes @@ -383,4 +388,9 @@ nodes | lifetime.rs:724:2:724:12 | &val | semmle.label | &val | | lifetime.rs:724:2:724:12 | ptr | semmle.label | ptr | | lifetime.rs:725:2:725:12 | ptr | semmle.label | ptr | +| lifetime.rs:733:2:733:12 | return ... | semmle.label | return ... | +| lifetime.rs:733:9:733:12 | &val | semmle.label | &val | +| lifetime.rs:737:6:737:8 | ptr | semmle.label | ptr | +| lifetime.rs:737:12:737:24 | get_pointer(...) | semmle.label | get_pointer(...) | +| lifetime.rs:743:10:743:12 | ptr | semmle.label | ptr | subpaths 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 52e65d9bcda2..1ef7bb5e2922 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -724,3 +724,41 @@ pub fn test_macros() { my_macro!(); // $ SPURIOUS: Source[rust/access-after-lifetime-ended] my_macro!(); // $ SPURIOUS: Alert[rust/access-after-lifetime-ended] } + +// --- examples from qhelp --- + +fn get_pointer() -> *const i64 { + let val = 123; + + return &val; // $ Source[rust/access-after-lifetime-ended]=val +} // lifetime of `val` ends here, the pointer becomes dangling + +pub fn test_lifetimes_example_bad() { + let ptr = get_pointer(); + let val; + + use_the_stack(); + + unsafe { + val = *ptr; // $ Alert[rust/access-after-lifetime-ended]=val + } + + println!(" val = {val} (!)"); // corrupt in practice +} + +fn get_box() -> Box { + let val = 123; + + return Box::new(val); +} + +pub fn test_lifetimes_example_good() { + let ptr = get_box(); + let val; + + use_the_stack(); + + val = *ptr; // GOOD + + println!(" val = {val}"); +} 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 5450dcd6b205..5c1afa0fd119 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -186,4 +186,10 @@ fn main() { println!("test_macros:"); test_macros(); + + println!("test_lifetimes_example_bad:"); + test_lifetimes_example_bad(); + + println!("test_lifetimes_example_good:"); + test_lifetimes_example_good(); } From 7bae451af375dc2c4163aee9d03919157a37cdb5 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:15:38 +0100 Subject: [PATCH 14/34] Rust: Exclude results in macro invocations. --- rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql | 4 +++- .../query-tests/security/CWE-825/AccessAfterLifetime.expected | 1 - rust/ql/test/query-tests/security/CWE-825/lifetime.rs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql index a7f9da347832..570039a1a4ae 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -39,6 +39,8 @@ 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 - AccessAfterLifetime::dereferenceAfterLifetime(sourceNode.getNode(), sinkNode.getNode(), target) + AccessAfterLifetime::dereferenceAfterLifetime(sourceNode.getNode(), sinkNode.getNode(), target) and + // exclude sinks in macros, since these results are difficult to interpret + not sinkNode.getNode().asExpr().getExpr().isFromMacroExpansion() select sinkNode.getNode(), sourceNode, sinkNode, "Access of a pointer to $@ after it's 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 c54369b8c736..677c2fd506b7 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 it's lifetime has ended. | lifetime.rs:651:7:651:10 | str2 | str2 | | lifetime.rs:692:13:692:14 | r1 | lifetime.rs:682:4:682:12 | &... | lifetime.rs:692:13:692:14 | r1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:681:8:681:9 | v1 | v1 | | lifetime.rs:693:13:693:14 | r2 | lifetime.rs:686:5:686:13 | &... | lifetime.rs:693:13:693:14 | r2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:685:8:685:9 | v2 | v2 | -| lifetime.rs:725:2:725:12 | ptr | lifetime.rs:724:2:724:12 | &val | lifetime.rs:725:2:725:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:724:2:724:12 | val | val | | lifetime.rs:743:10:743:12 | ptr | lifetime.rs:733:9:733:12 | &val | lifetime.rs:743:10:743:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:731:6:731:8 | val | val | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151: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 1ef7bb5e2922..97d76d8757dd 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -721,8 +721,8 @@ macro_rules! my_macro { } pub fn test_macros() { - my_macro!(); // $ SPURIOUS: Source[rust/access-after-lifetime-ended] - my_macro!(); // $ SPURIOUS: Alert[rust/access-after-lifetime-ended] + my_macro!(); + my_macro!(); } // --- examples from qhelp --- From 858eec390da752b624881478e2bae9764421fbb1 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:47:41 +0100 Subject: [PATCH 15/34] Rust: Exclude results where the source is a reference. --- .../rust/security/AccessAfterLifetimeExtensions.qll | 7 +++++-- .../security/CWE-825/AccessAfterLifetime.expected | 2 -- rust/ql/test/query-tests/security/CWE-825/lifetime.rs | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index 2534a5acdb31..919b534dee5d 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -6,6 +6,8 @@ import rust private import codeql.rust.dataflow.DataFlow private import codeql.rust.security.AccessInvalidPointerExtensions +private import codeql.rust.internal.Type +private import codeql.rust.internal.TypeInference as TypeInference /** * Provides default sources, sinks and barriers for detecting accesses to a @@ -55,9 +57,10 @@ module AccessAfterLifetime { * Holds if `value` accesses a variable `target` with scope `scope`. */ private predicate valueScope(Expr value, Variable target, BlockExpr scope) { - // variable access + // variable access (to a non-reference) target = value.(VariableAccess).getVariable() and - scope = target.getEnclosingBlock() + scope = target.getEnclosingBlock() and + not TypeInference::inferType(value) instanceof RefType or // field access valueScope(value.(FieldExpr).getContainer(), target, scope) 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 677c2fd506b7..f9f3683baf64 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -20,8 +20,6 @@ | lifetime.rs:659:15:659:18 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:659:15:659:18 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | 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 it's lifetime has ended. | lifetime.rs:651:7:651:10 | str2 | str2 | -| lifetime.rs:692:13:692:14 | r1 | lifetime.rs:682:4:682:12 | &... | lifetime.rs:692:13:692:14 | r1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:681:8:681:9 | v1 | v1 | -| lifetime.rs:693:13:693:14 | r2 | lifetime.rs:686:5:686:13 | &... | lifetime.rs:693:13:693:14 | r2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:685:8:685:9 | v2 | v2 | | lifetime.rs:743:10:743:12 | ptr | lifetime.rs:733:9:733:12 | &val | lifetime.rs:743:10:743:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:731:6:731:8 | val | val | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151: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 97d76d8757dd..36c0001b98e8 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -679,18 +679,18 @@ impl MyType { fn test(&self) { let r1 = unsafe { let v1 = &self; - &v1.value // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=v1 + &v1.value }; let (r2, r3) = unsafe { let v2 = &self; - (&v2.value, // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=v2 + (&v2.value, &self.value) }; use_the_stack(); - let v1 = *r1; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=v1 - let v2 = *r2; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=v2 + let v1 = *r1; + let v2 = *r2; let v3 = *r3; println!(" v1 = {v1}"); println!(" v2 = {v2}"); From d3d0a533b515eb4b55a08e77e2c535ac0c256b55 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:16:54 +0100 Subject: [PATCH 16/34] Rust: Add test showing yet another spurious result. --- .../CWE-825/AccessAfterLifetime.expected | 42 ++++++++++++------- .../query-tests/security/CWE-825/lifetime.rs | 32 ++++++++++++++ .../test/query-tests/security/CWE-825/main.rs | 3 ++ 3 files changed, 62 insertions(+), 15 deletions(-) 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 f9f3683baf64..584cf156fd15 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -20,7 +20,8 @@ | lifetime.rs:659:15:659:18 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:659:15:659:18 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | 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 it's lifetime has ended. | lifetime.rs:651:7:651:10 | str2 | str2 | -| lifetime.rs:743:10:743:12 | ptr | lifetime.rs:733:9:733:12 | &val | lifetime.rs:743:10:743:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:731:6:731:8 | val | val | +| lifetime.rs:734:12:734:13 | r1 | lifetime.rs:719:26:719:34 | &... | lifetime.rs:734:12:734:13 | r1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:719:19:719:20 | v2 | v2 | +| lifetime.rs:775:10:775:12 | ptr | lifetime.rs:765:9:765:12 | &val | lifetime.rs:775:10:775:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:763:6:763:8 | val | val | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151:15 | p1 | provenance | | | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:158:14:158:15 | p1 | provenance | | @@ -183,12 +184,17 @@ edges | lifetime.rs:686:4:687:16 | TupleExpr [tuple.1] | lifetime.rs:684:7:684:14 | TuplePat [tuple.1] | provenance | | | lifetime.rs:686:5:686:13 | &... | lifetime.rs:686:4:687:16 | TupleExpr [tuple.0] | provenance | | | lifetime.rs:687:5:687:15 | &... | lifetime.rs:686:4:687:16 | TupleExpr [tuple.1] | provenance | | -| lifetime.rs:724:2:724:12 | &val | lifetime.rs:724:2:724:12 | ptr | provenance | | -| lifetime.rs:724:2:724:12 | ptr | lifetime.rs:725:2:725:12 | ptr | provenance | | -| lifetime.rs:733:2:733:12 | return ... | lifetime.rs:737:12:737:24 | get_pointer(...) | provenance | | -| lifetime.rs:733:9:733:12 | &val | lifetime.rs:733:2:733:12 | return ... | provenance | | -| lifetime.rs:737:6:737:8 | ptr | lifetime.rs:743:10:743:12 | ptr | provenance | | -| lifetime.rs:737:12:737:24 | get_pointer(...) | lifetime.rs:737:6:737:8 | ptr | provenance | | +| lifetime.rs:717:35:723:2 | { ... } | lifetime.rs:730:11:730:25 | e1.test_match() | provenance | | +| lifetime.rs:718:7:718:8 | r1 | lifetime.rs:717:35:723:2 | { ... } | provenance | | +| lifetime.rs:719:26:719:34 | &... | lifetime.rs:718:7:718:8 | r1 | provenance | | +| lifetime.rs:730:6:730:7 | r1 | lifetime.rs:734:12:734:13 | r1 | provenance | | +| lifetime.rs:730:11:730:25 | e1.test_match() | lifetime.rs:730:6:730:7 | r1 | provenance | | +| lifetime.rs:756:2:756:12 | &val | lifetime.rs:756:2:756:12 | ptr | provenance | | +| lifetime.rs:756:2:756:12 | ptr | lifetime.rs:757:2:757:12 | ptr | provenance | | +| lifetime.rs:765:2:765:12 | return ... | lifetime.rs:769:12:769:24 | get_pointer(...) | provenance | | +| lifetime.rs:765:9:765:12 | &val | lifetime.rs:765:2:765:12 | return ... | provenance | | +| lifetime.rs:769:6:769:8 | ptr | lifetime.rs:775:10:775:12 | ptr | provenance | | +| lifetime.rs:769:12:769:24 | get_pointer(...) | lifetime.rs:769:6:769:8 | ptr | provenance | | models | 1 | Summary: lang:core; crate::ptr::from_ref; Argument[0]; ReturnValue; value | nodes @@ -382,12 +388,18 @@ nodes | lifetime.rs:692:13:692:14 | r1 | semmle.label | r1 | | lifetime.rs:693:13:693:14 | r2 | semmle.label | r2 | | lifetime.rs:694:13:694:14 | r3 | semmle.label | r3 | -| lifetime.rs:724:2:724:12 | &val | semmle.label | &val | -| lifetime.rs:724:2:724:12 | ptr | semmle.label | ptr | -| lifetime.rs:725:2:725:12 | ptr | semmle.label | ptr | -| lifetime.rs:733:2:733:12 | return ... | semmle.label | return ... | -| lifetime.rs:733:9:733:12 | &val | semmle.label | &val | -| lifetime.rs:737:6:737:8 | ptr | semmle.label | ptr | -| lifetime.rs:737:12:737:24 | get_pointer(...) | semmle.label | get_pointer(...) | -| lifetime.rs:743:10:743:12 | ptr | semmle.label | ptr | +| lifetime.rs:717:35:723:2 | { ... } | semmle.label | { ... } | +| lifetime.rs:718:7:718:8 | r1 | semmle.label | r1 | +| lifetime.rs:719:26:719:34 | &... | semmle.label | &... | +| lifetime.rs:730:6:730:7 | r1 | semmle.label | r1 | +| lifetime.rs:730:11:730:25 | e1.test_match() | semmle.label | e1.test_match() | +| lifetime.rs:734:12:734:13 | r1 | semmle.label | r1 | +| lifetime.rs:756:2:756:12 | &val | semmle.label | &val | +| lifetime.rs:756:2:756:12 | ptr | semmle.label | ptr | +| lifetime.rs:757:2:757:12 | ptr | semmle.label | ptr | +| lifetime.rs:765:2:765:12 | return ... | semmle.label | return ... | +| lifetime.rs:765:9:765:12 | &val | semmle.label | &val | +| lifetime.rs:769:6:769:8 | ptr | semmle.label | ptr | +| lifetime.rs:769:12:769:24 | get_pointer(...) | semmle.label | get_pointer(...) | +| lifetime.rs:775:10:775:12 | ptr | semmle.label | ptr | subpaths 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 36c0001b98e8..d43004807e08 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -703,6 +703,38 @@ pub fn test_members() { mt.test(); } +// --- enum members --- + +struct MyValue2 { + value: i64 +} + +enum MyEnum3 { + Value(MyValue2), +} + +impl MyEnum3 { + pub fn test_match(&self) -> &i64 { + let r1 = match self { + MyEnum3::Value(v2) => &v2.value, // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=v2_value + }; + + r1 + } +} + +pub fn test_enum_members() { + let v1 = MyValue2 { value: 1 }; + let e1 = MyEnum3::Value(v1); + + let r1 = e1.test_match(); + + use_the_stack(); + + let v3 = *r1; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=v2_value + println!(" v3 = {v3}"); +} + // --- macros --- macro_rules! my_macro { 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 5c1afa0fd119..e134c212ba09 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -184,6 +184,9 @@ fn main() { println!("test_members:"); test_members(); + println!("test_enum_members:"); + test_enum_members(); + println!("test_macros:"); test_macros(); From b3330b56361730d8827b2074c5f7d1678b1b1de2 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:22:32 +0100 Subject: [PATCH 17/34] Rust: Allow parameter accesses as sources. --- .../security/AccessAfterLifetimeExtensions.qll | 16 +++++++++++++++- .../CWE-825/AccessAfterLifetime.expected | 1 + .../query-tests/security/CWE-825/lifetime.rs | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index 919b534dee5d..aaab0c309929 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -53,13 +53,27 @@ module AccessAfterLifetime { ) } + /** + * Holds if `var` has scope `scope`. + */ + private predicate variableScope(Variable var, BlockExpr scope) { + // local variable + scope = var.getEnclosingBlock() + or + // parameter + exists(Callable c | + var.getParameter().getEnclosingCallable() = c and + scope.getParentNode() = c + ) + } + /** * Holds if `value` accesses a variable `target` with scope `scope`. */ private predicate valueScope(Expr value, Variable target, BlockExpr scope) { // variable access (to a non-reference) target = value.(VariableAccess).getVariable() and - scope = target.getEnclosingBlock() and + variableScope(target, scope) and not TypeInference::inferType(value) instanceof RefType or // field access 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 584cf156fd15..b28c3fa22929 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -3,6 +3,7 @@ | lifetime.rs:70:13:70:14 | p2 | lifetime.rs:27:9:27:22 | &mut my_local2 | lifetime.rs:70:13:70:14 | p2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:25:10:25:18 | my_local2 | my_local2 | | lifetime.rs:71:13:71:14 | p3 | lifetime.rs:33:9:33:28 | &raw const my_local3 | lifetime.rs:71:13:71:14 | p3 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:31:6:31:14 | my_local3 | my_local3 | | lifetime.rs:72:13:72:14 | p4 | lifetime.rs:39:9:39:26 | &raw mut my_local4 | lifetime.rs:72:13:72:14 | p4 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:37:10:37:18 | my_local4 | my_local4 | +| lifetime.rs:73:13:73:14 | p5 | lifetime.rs:43:9:43:15 | ¶m5 | lifetime.rs:73:13:73:14 | p5 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:42:23:42:28 | param5 | param5 | | lifetime.rs:74:13:74:14 | p6 | lifetime.rs:50:9:50:18 | &... | lifetime.rs:74:13:74:14 | p6 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:47:6:47:8 | val | val | | lifetime.rs:75:13:75:14 | p7 | lifetime.rs:63:8:63:27 | &raw const my_local7 | lifetime.rs:75:13:75:14 | p7 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:62:7:62:15 | my_local7 | my_local7 | | 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 it's lifetime has ended. | lifetime.rs:25:10:25:18 | my_local2 | my_local2 | 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 d43004807e08..656d7c692f8e 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -40,7 +40,7 @@ fn get_local_dangling_raw_mut() -> *mut i64 { } // (return value immediately becomes dangling) fn get_param_dangling(param5: i64) -> *const i64 { - return ¶m5; // $ MISSING: Source[rust/access-after-lifetime-ended]=param5 + return ¶m5; // $ Source[rust/access-after-lifetime-ended]=param5 } // (return value immediately becomes dangling) fn get_local_field_dangling() -> *const i64 { @@ -70,7 +70,7 @@ pub fn test_local_dangling() { let v2 = *p2; // $ Alert[rust/access-after-lifetime-ended]=local2 let v3 = *p3; // $ Alert[rust/access-after-lifetime-ended]=local3 let v4 = *p4; // $ Alert[rust/access-after-lifetime-ended]=local4 - let v5 = *p5; // $ MISSING: Alert[rust/access-after-lifetime-ended]=param5 + let v5 = *p5; // $ Alert[rust/access-after-lifetime-ended]=param5 let v6 = *p6; // $ Alert[rust/access-after-lifetime-ended]=localfield let v7 = *p7; // $ Alert[rust/access-after-lifetime-ended]=local7 *p2 = 8; // $ Alert[rust/access-after-lifetime-ended]=local2 From 9b0ee8fb9f693f466beeb24ec83a539d5c580e9b Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:32:29 +0100 Subject: [PATCH 18/34] Rust: Add security-severity tag and reduce precision to medium for now. precis --- rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql index 570039a1a4ae..4bfc4249d269 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -4,8 +4,8 @@ * causes undefined behavior and may result in memory corruption. * @kind path-problem * @problem.severity error - * @security-severity TODO - * @precision high + * @security-severity 9.8 + * @precision medium * @id rust/access-after-lifetime-ended * @tags reliability * security From e7945e16cb0d616796844544c03c28c7ac93ca90 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:06:34 +0100 Subject: [PATCH 19/34] Rust: Accept the query in suite listings. --- .../query-suite/rust-security-and-quality.qls.expected | 1 + .../query-suite/rust-security-extended.qls.expected | 1 + 2 files changed, 2 insertions(+) diff --git a/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected b/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected index c21b79749d17..650bf3169412 100644 --- a/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected +++ b/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected @@ -16,6 +16,7 @@ ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql ql/rust/ql/src/queries/security/CWE-696/BadCtorInitialization.ql ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql +ql/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql ql/rust/ql/src/queries/security/CWE-825/AccessInvalidPointer.ql ql/rust/ql/src/queries/summary/LinesOfCode.ql ql/rust/ql/src/queries/summary/LinesOfUserCode.ql diff --git a/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected b/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected index b3683f02d927..b5df88f96eca 100644 --- a/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected +++ b/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected @@ -15,6 +15,7 @@ ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql +ql/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql ql/rust/ql/src/queries/security/CWE-825/AccessInvalidPointer.ql ql/rust/ql/src/queries/summary/LinesOfCode.ql ql/rust/ql/src/queries/summary/LinesOfUserCode.ql From 74ce4e81056f48f678468cf40f50b165a11dc91e Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:10:39 +0100 Subject: [PATCH 20/34] Update rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql index 4bfc4249d269..e874b0eba8a5 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -43,4 +43,4 @@ where // exclude sinks in macros, since these results are difficult to interpret not sinkNode.getNode().asExpr().getExpr().isFromMacroExpansion() select sinkNode.getNode(), sourceNode, sinkNode, - "Access of a pointer to $@ after it's lifetime has ended.", target, target.toString() + "Access of a pointer to $@ after its lifetime has ended.", target, target.toString() From a9d5d8b2b3dbba050dd34d066ae2c22db9ed5976 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:14:14 +0100 Subject: [PATCH 21/34] Rust: Accept the new alert message in tests. --- .../CWE-825/AccessAfterLifetime.expected | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) 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 b28c3fa22929..998a63b8f77d 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -1,28 +1,28 @@ #select -| lifetime.rs:69:13:69:14 | p1 | lifetime.rs:21:9:21:18 | &my_local1 | lifetime.rs:69:13:69:14 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:19:6:19:14 | my_local1 | my_local1 | -| lifetime.rs:70:13:70:14 | p2 | lifetime.rs:27:9:27:22 | &mut my_local2 | lifetime.rs:70:13:70:14 | p2 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:25:10:25:18 | my_local2 | my_local2 | -| lifetime.rs:71:13:71:14 | p3 | lifetime.rs:33:9:33:28 | &raw const my_local3 | lifetime.rs:71:13:71:14 | p3 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:31:6:31:14 | my_local3 | my_local3 | -| lifetime.rs:72:13:72:14 | p4 | lifetime.rs:39:9:39:26 | &raw mut my_local4 | lifetime.rs:72:13:72:14 | p4 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:37:10:37:18 | my_local4 | my_local4 | -| lifetime.rs:73:13:73:14 | p5 | lifetime.rs:43:9:43:15 | ¶m5 | lifetime.rs:73:13:73:14 | p5 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:42:23:42:28 | param5 | param5 | -| lifetime.rs:74:13:74:14 | p6 | lifetime.rs:50:9:50:18 | &... | lifetime.rs:74:13:74:14 | p6 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:47:6:47:8 | val | val | -| lifetime.rs:75:13:75:14 | p7 | lifetime.rs:63:8:63:27 | &raw const my_local7 | lifetime.rs:75:13:75:14 | p7 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:62:7:62:15 | my_local7 | my_local7 | -| 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 it's 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 it's 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 it's 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 it's 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 it's lifetime has ended. | lifetime.rs:271:6:271:7 | e1 | e1 | -| lifetime.rs:317:13:317:18 | result | lifetime.rs:289:25:289:26 | &x | lifetime.rs:317:13:317:18 | result | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:289:17:289:17 | x | x | -| lifetime.rs:411:16:411:17 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:411:16:411:17 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:382:11:382:17 | my_pair | my_pair | -| lifetime.rs:416:16:416:17 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:416:16:416:17 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:382:11:382:17 | my_pair | my_pair | -| lifetime.rs:428:7:428:8 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:428:7:428:8 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:382:11:382:17 | my_pair | my_pair | -| lifetime.rs:433:7:433:8 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:433:7:433:8 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:382:11:382:17 | my_pair | my_pair | -| lifetime.rs:459:13:459:14 | p1 | lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:459:13:459:14 | p1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:441:6:441:11 | my_val | my_val | -| lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:441:6:441:11 | my_val | my_val | -| lifetime.rs:659:15:659:18 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:659:15:659:18 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | -| lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | -| 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 it's lifetime has ended. | lifetime.rs:651:7:651:10 | str2 | str2 | -| lifetime.rs:734:12:734:13 | r1 | lifetime.rs:719:26:719:34 | &... | lifetime.rs:734:12:734:13 | r1 | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:719:19:719:20 | v2 | v2 | -| lifetime.rs:775:10:775:12 | ptr | lifetime.rs:765:9:765:12 | &val | lifetime.rs:775:10:775:12 | ptr | Access of a pointer to $@ after it's lifetime has ended. | lifetime.rs:763:6:763:8 | val | val | +| lifetime.rs:69:13:69:14 | p1 | lifetime.rs:21:9:21:18 | &my_local1 | lifetime.rs:69:13:69:14 | p1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:19:6:19:14 | my_local1 | my_local1 | +| lifetime.rs:70:13:70:14 | p2 | lifetime.rs:27:9:27:22 | &mut my_local2 | lifetime.rs:70:13:70:14 | p2 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:25:10:25:18 | my_local2 | my_local2 | +| lifetime.rs:71:13:71:14 | p3 | lifetime.rs:33:9:33:28 | &raw const my_local3 | lifetime.rs:71:13:71:14 | p3 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:31:6:31:14 | my_local3 | my_local3 | +| lifetime.rs:72:13:72:14 | p4 | lifetime.rs:39:9:39:26 | &raw mut my_local4 | lifetime.rs:72:13:72:14 | p4 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:37:10:37:18 | my_local4 | my_local4 | +| lifetime.rs:73:13:73:14 | p5 | lifetime.rs:43:9:43:15 | ¶m5 | lifetime.rs:73:13:73:14 | p5 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:42:23:42:28 | param5 | param5 | +| lifetime.rs:74:13:74:14 | p6 | lifetime.rs:50:9:50:18 | &... | lifetime.rs:74:13:74:14 | p6 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:47:6:47:8 | val | val | +| lifetime.rs:75:13:75:14 | p7 | lifetime.rs:63:8:63:27 | &raw const my_local7 | lifetime.rs:75:13:75:14 | p7 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:62:7:62:15 | my_local7 | my_local7 | +| 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: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:317:13:317:18 | result | lifetime.rs:289:25:289:26 | &x | lifetime.rs:317:13:317:18 | result | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:289:17:289:17 | x | x | +| lifetime.rs:411:16:411:17 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:411:16:411:17 | p1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:382:11:382:17 | my_pair | my_pair | +| lifetime.rs:416:16:416:17 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:416:16:416:17 | p1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:382:11:382:17 | my_pair | my_pair | +| lifetime.rs:428:7:428:8 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:428:7:428:8 | p1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:382:11:382:17 | my_pair | my_pair | +| lifetime.rs:433:7:433:8 | p1 | lifetime.rs:383:31:383:37 | &raw mut my_pair | lifetime.rs:433:7:433:8 | p1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:382:11:382:17 | my_pair | my_pair | +| lifetime.rs:459:13:459:14 | p1 | lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:459:13:459:14 | p1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:441:6:441:11 | my_val | my_val | +| lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | lifetime.rs:442:17:442:23 | &my_val | lifetime.rs:460:13:460:31 | get_ptr_from_ref(...) | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:441:6:441:11 | my_val | my_val | +| lifetime.rs:659:15:659:18 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:659:15:659:18 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | +| lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | +| 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:734:12:734:13 | r1 | lifetime.rs:719:26:719:34 | &... | lifetime.rs:734:12:734:13 | r1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:719:19:719:20 | v2 | v2 | +| lifetime.rs:775:10:775:12 | ptr | lifetime.rs:765:9:765:12 | &val | lifetime.rs:775:10:775:12 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:763:6:763:8 | val | val | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151:15 | p1 | provenance | | | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:158:14:158:15 | p1 | provenance | | From ecac0dbe699f4a6304c337d7ee2c2b98f55aa196 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:52:52 +0100 Subject: [PATCH 22/34] Rust: Accept consistency check failures. --- .../security/CWE-825/CONSISTENCY/SsaConsistency.expected | 6 ++++++ .../CWE-825/CONSISTENCY/VariableCaptureConsistency.expected | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/SsaConsistency.expected create mode 100644 rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/VariableCaptureConsistency.expected diff --git a/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/SsaConsistency.expected b/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/SsaConsistency.expected new file mode 100644 index 000000000000..c29442521169 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/SsaConsistency.expected @@ -0,0 +1,6 @@ +readWithoutDef +| lifetime.rs:511:6:511:14 | my_local2 | lifetime.rs:514:9:527:2 | enter \|...\| ... | 2 | +| lifetime.rs:564:6:564:14 | my_local2 | lifetime.rs:567:9:580:2 | enter { ... } | 2 | +readWithoutPriorRef +| lifetime.rs:511:6:511:14 | my_local2 | lifetime.rs:514:9:527:2 | enter \|...\| ... | 2 | +| lifetime.rs:564:6:564:14 | my_local2 | lifetime.rs:567:9:580:2 | enter { ... } | 2 | diff --git a/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/VariableCaptureConsistency.expected b/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/VariableCaptureConsistency.expected new file mode 100644 index 000000000000..641265313115 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/VariableCaptureConsistency.expected @@ -0,0 +1,5 @@ +variableIsCaptured +| lifetime.rs:511:6:511:14 | my_local2 | CapturedVariable is not captured | +| lifetime.rs:564:6:564:14 | my_local2 | CapturedVariable is not captured | +consistencyOverview +| CapturedVariable is not captured | 2 | From b29deed919ea85f42404bd51c0318aada4a71bd5 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 11 Jun 2025 18:09:22 +0100 Subject: [PATCH 23/34] Rust: Accept changes in an unrelated test reported by CI. --- rust/ql/test/extractor-tests/crate_graph/crates.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/ql/test/extractor-tests/crate_graph/crates.expected b/rust/ql/test/extractor-tests/crate_graph/crates.expected index acc8aa3dec87..f934618db9c5 100644 --- a/rust/ql/test/extractor-tests/crate_graph/crates.expected +++ b/rust/ql/test/extractor-tests/crate_graph/crates.expected @@ -18,7 +18,7 @@ #-----| core -> Crate(core@0.0.0) #-----| compiler_builtins -> Crate(compiler_builtins@0.1.140) -#-----| Crate(cfg_if@1.0.0) +#-----| Crate(cfg_if@1.0.1) #-----| proc_macro -> Crate(proc_macro@0.0.0) #-----| alloc -> Crate(alloc@0.0.0) #-----| core -> Crate(core@0.0.0) @@ -89,7 +89,7 @@ main.rs: #-----| core -> Crate(core@0.0.0) #-----| std -> Crate(std@0.0.0) #-----| test -> Crate(test@0.0.0) -#-----| cfg_if -> Crate(cfg_if@1.0.0) +#-----| cfg_if -> Crate(cfg_if@1.0.1) #-----| digest -> Crate(digest@0.10.7) #-----| Crate(md5@0.7.0) From 168246005cc60d3132c87816ecfeb4337f6868b5 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 11 Jun 2025 18:33:59 +0100 Subject: [PATCH 24/34] Rust: Extend tests based on cases found in DCA. --- .../CWE-825/AccessAfterLifetime.expected | 36 +++++++++++-------- .../query-tests/security/CWE-825/lifetime.rs | 22 ++++++++++-- 2 files changed, 41 insertions(+), 17 deletions(-) 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 998a63b8f77d..f0e454879cea 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,8 @@ | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | 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:734:12:734:13 | r1 | lifetime.rs:719:26:719:34 | &... | lifetime.rs:734:12:734:13 | r1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:719:19:719:20 | v2 | v2 | -| lifetime.rs:775:10:775:12 | ptr | lifetime.rs:765:9:765:12 | &val | lifetime.rs:775:10:775:12 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:763:6:763:8 | val | val | +| lifetime.rs:771:12:771:14 | ptr | lifetime.rs:769:12:769:23 | &val | lifetime.rs:771:12:771:14 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:769:12:769:23 | val | val | +| lifetime.rs:791:10:791:12 | ptr | lifetime.rs:781:9:781:12 | &val | lifetime.rs:791:10:791:12 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:779:6:779:8 | val | val | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151:15 | p1 | provenance | | | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:158:14:158:15 | p1 | provenance | | @@ -190,12 +191,15 @@ edges | lifetime.rs:719:26:719:34 | &... | lifetime.rs:718:7:718:8 | r1 | provenance | | | lifetime.rs:730:6:730:7 | r1 | lifetime.rs:734:12:734:13 | r1 | provenance | | | lifetime.rs:730:11:730:25 | e1.test_match() | lifetime.rs:730:6:730:7 | r1 | provenance | | -| lifetime.rs:756:2:756:12 | &val | lifetime.rs:756:2:756:12 | ptr | provenance | | -| lifetime.rs:756:2:756:12 | ptr | lifetime.rs:757:2:757:12 | ptr | provenance | | -| lifetime.rs:765:2:765:12 | return ... | lifetime.rs:769:12:769:24 | get_pointer(...) | provenance | | -| lifetime.rs:765:9:765:12 | &val | lifetime.rs:765:2:765:12 | return ... | provenance | | -| lifetime.rs:769:6:769:8 | ptr | lifetime.rs:775:10:775:12 | ptr | provenance | | -| lifetime.rs:769:12:769:24 | get_pointer(...) | lifetime.rs:769:6:769:8 | ptr | provenance | | +| lifetime.rs:766:2:766:13 | &val | lifetime.rs:766:2:766:13 | ptr | provenance | | +| lifetime.rs:766:2:766:13 | ptr | lifetime.rs:767:2:767:13 | ptr | provenance | | +| lifetime.rs:769:6:769:8 | ptr | lifetime.rs:771:12:771:14 | ptr | provenance | | +| lifetime.rs:769:12:769:23 | &val | lifetime.rs:769:12:769:23 | ptr | provenance | | +| lifetime.rs:769:12:769:23 | ptr | lifetime.rs:769:6:769:8 | ptr | provenance | | +| lifetime.rs:781:2:781:12 | return ... | lifetime.rs:785:12:785:24 | get_pointer(...) | provenance | | +| lifetime.rs:781:9:781:12 | &val | lifetime.rs:781:2:781:12 | return ... | provenance | | +| lifetime.rs:785:6:785:8 | ptr | lifetime.rs:791:10:791:12 | ptr | provenance | | +| lifetime.rs:785:12:785:24 | get_pointer(...) | lifetime.rs:785:6:785:8 | ptr | provenance | | models | 1 | Summary: lang:core; crate::ptr::from_ref; Argument[0]; ReturnValue; value | nodes @@ -395,12 +399,16 @@ nodes | lifetime.rs:730:6:730:7 | r1 | semmle.label | r1 | | lifetime.rs:730:11:730:25 | e1.test_match() | semmle.label | e1.test_match() | | lifetime.rs:734:12:734:13 | r1 | semmle.label | r1 | -| lifetime.rs:756:2:756:12 | &val | semmle.label | &val | -| lifetime.rs:756:2:756:12 | ptr | semmle.label | ptr | -| lifetime.rs:757:2:757:12 | ptr | semmle.label | ptr | -| lifetime.rs:765:2:765:12 | return ... | semmle.label | return ... | -| lifetime.rs:765:9:765:12 | &val | semmle.label | &val | +| lifetime.rs:766:2:766:13 | &val | semmle.label | &val | +| lifetime.rs:766:2:766:13 | ptr | semmle.label | ptr | +| lifetime.rs:767:2:767:13 | ptr | semmle.label | ptr | | lifetime.rs:769:6:769:8 | ptr | semmle.label | ptr | -| lifetime.rs:769:12:769:24 | get_pointer(...) | semmle.label | get_pointer(...) | -| lifetime.rs:775:10:775:12 | ptr | semmle.label | ptr | +| lifetime.rs:769:12:769:23 | &val | semmle.label | &val | +| lifetime.rs:769:12:769:23 | ptr | semmle.label | ptr | +| lifetime.rs:771:12:771:14 | ptr | semmle.label | ptr | +| lifetime.rs:781:2:781:12 | return ... | semmle.label | return ... | +| lifetime.rs:781:9:781:12 | &val | semmle.label | &val | +| lifetime.rs:785:6:785:8 | ptr | semmle.label | ptr | +| lifetime.rs:785:12:785:24 | get_pointer(...) | semmle.label | get_pointer(...) | +| lifetime.rs:791:10:791:12 | ptr | semmle.label | ptr | subpaths 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 656d7c692f8e..386601f71fd2 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -737,7 +737,7 @@ pub fn test_enum_members() { // --- macros --- -macro_rules! my_macro { +macro_rules! my_macro1 { () => { let ptr: *const i64; { @@ -752,9 +752,25 @@ macro_rules! my_macro { } } +macro_rules! my_macro2 { + () => { + { + let val: i64 = 1; + let ptr: *const i64 = &val; + ptr + } + } +} + pub fn test_macros() { - my_macro!(); - my_macro!(); + my_macro1!(); + my_macro1!(); + + let ptr = my_macro2!(); // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=ptr + unsafe { + let v = *ptr; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=ptr + println!(" v = {v}"); + } } // --- examples from qhelp --- From 087e66665858d784dcc8f08e9bc3e5299a8f439e Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 11 Jun 2025 18:48:23 +0100 Subject: [PATCH 25/34] Rust: Exclude sources in macro expansions. --- rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql | 3 ++- .../query-tests/security/CWE-825/AccessAfterLifetime.expected | 1 - rust/ql/test/query-tests/security/CWE-825/lifetime.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql index e874b0eba8a5..0661932a41f6 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -40,7 +40,8 @@ where AccessAfterLifetimeFlow::flowPath(sourceNode, sinkNode) and // check that the dereference is outside the lifetime of the target AccessAfterLifetime::dereferenceAfterLifetime(sourceNode.getNode(), sinkNode.getNode(), target) and - // exclude sinks in macros, since these results are difficult to interpret + // exclude cases with sources / sinks in macros, since these results are difficult to interpret + not sourceNode.getNode().asExpr().getExpr().isFromMacroExpansion() and not sinkNode.getNode().asExpr().getExpr().isFromMacroExpansion() 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 f0e454879cea..f99bb399fd41 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:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | 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:734:12:734:13 | r1 | lifetime.rs:719:26:719:34 | &... | lifetime.rs:734:12:734:13 | r1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:719:19:719:20 | v2 | v2 | -| lifetime.rs:771:12:771:14 | ptr | lifetime.rs:769:12:769:23 | &val | lifetime.rs:771:12:771:14 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:769:12:769:23 | val | val | | lifetime.rs:791:10:791:12 | ptr | lifetime.rs:781:9:781:12 | &val | lifetime.rs:791:10:791:12 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:779:6:779:8 | val | val | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151: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 386601f71fd2..fc5d64263163 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -766,9 +766,9 @@ pub fn test_macros() { my_macro1!(); my_macro1!(); - let ptr = my_macro2!(); // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=ptr + let ptr = my_macro2!(); unsafe { - let v = *ptr; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=ptr + let v = *ptr; println!(" v = {v}"); } } From 14b75a968bad11f70cde492724ecfed09cd0df15 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 13 Jun 2025 14:09:49 +0100 Subject: [PATCH 26/34] Apply suggestions from code review Co-authored-by: mc <42146119+mchammer01@users.noreply.github.com> --- .../ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp index 41d62af44e34..fe5dd64a270f 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp @@ -6,7 +6,7 @@

    Dereferencing a pointer after the lifetime of its target has ended causes undefined behavior. Memory -may be corrupted causing the program to crash or behave incorrectly, in some cases exposing the program +may be corrupted, causing the program to crash or behave incorrectly, in some cases exposing the program to potential attacks.

    @@ -33,7 +33,7 @@ after that lifetime has ended, causing undefined behavior:

    One way to fix this is to change the return type of the function from a pointer to a Box, which ensures that the value it points to remains on the heap for the lifetime of the Box -itself. Notice that there is no longer a need for an unsafe block as the code no longer +itself. Note that there is no longer a need for an unsafe block as the code no longer handles pointers directly:

    From df221ea8f8d3803695b00509fac681c37d2f1862 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:17:17 +0100 Subject: [PATCH 27/34] Rust: Remove excess 'cached' annotation. --- rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll index 697672bbaf3f..3c50487edd85 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll @@ -128,7 +128,6 @@ module Impl { Name getName() { variableDecl(definingNode, result, text) } /** Gets the block that encloses this variable, if any. */ - cached BlockExpr getEnclosingBlock() { result = definingNode.getEnclosingBlock() } /** Gets the `self` parameter that declares this variable, if any. */ From 5bf799e7172ccc78527d737c66bad1786a9827b6 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:52:02 +0100 Subject: [PATCH 28/34] Apply suggestions from code review Co-authored-by: Simon Friis Vindum --- .../codeql/rust/security/AccessAfterLifetimeExtensions.qll | 6 +++--- .../src/queries/security/CWE-825/AccessAfterLifetimeBad.rs | 2 +- .../src/queries/security/CWE-825/AccessAfterLifetimeGood.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index aaab0c309929..63ab070dac10 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -81,13 +81,13 @@ module AccessAfterLifetime { } /** - * Holds if block `a` contains block `b`, in the sense that a variable in - * `a` may be on the stack during execution of `b`. This is interprocedural, + * 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 then depending on the * caller a variable in `f` or `g` may or may-not be on the stack during `b`). */ - private predicate maybeOnStack(BlockExpr a, BlockExpr b) { + private predicate blockStackEnclosing(BlockExpr a, BlockExpr b) { // `b` is a child of `a` a = b.getEnclosingBlock*() or diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs index 61f981e40199..c5f5cf607d10 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs @@ -2,7 +2,7 @@ fn get_pointer() -> *const i64 { let val = 123; - return &val; + &val } // lifetime of `val` ends here, the pointer becomes dangling fn example() { diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs index e8d0017d007b..944f6905b70b 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs @@ -2,7 +2,7 @@ fn get_box() -> Box { let val = 123; - return Box::new(val); // copies `val` onto the heap, where it remains for the lifetime of the `Box`. + Box::new(val) // copies `val` onto the heap, where it remains for the lifetime of the `Box`. } fn example() { From 79cedc25863d343051d2e772cb07a90da163d4b8 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:56:04 +0100 Subject: [PATCH 29/34] Rust: Rename predicate again. --- .../codeql/rust/security/AccessAfterLifetimeExtensions.qll | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll index 63ab070dac10..4b3177c9df95 100644 --- a/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll @@ -49,7 +49,7 @@ module AccessAfterLifetime { exists(BlockExpr valueScope, BlockExpr accessScope | valueScope(source.getTarget(), target, valueScope) and accessScope = sink.asExpr().getExpr().getEnclosingBlock() and - not maybeOnStack(valueScope, accessScope) + not mayEncloseOnStack(valueScope, accessScope) ) } @@ -87,13 +87,13 @@ module AccessAfterLifetime { * (for example if `f` and `g` both call `b`, then then depending on the * caller a variable in `f` or `g` may or may-not be on the stack during `b`). */ - private predicate blockStackEnclosing(BlockExpr a, BlockExpr b) { + private predicate mayEncloseOnStack(BlockExpr a, BlockExpr b) { // `b` is a child of `a` a = b.getEnclosingBlock*() or // propagate through function calls exists(CallExprBase ce | - maybeOnStack(a, ce.getEnclosingBlock()) and + mayEncloseOnStack(a, ce.getEnclosingBlock()) and ce.getStaticTarget() = b.getEnclosingCallable() ) } From dbde8418bb7036189315647b76b426f56720f05f Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:55:04 +0100 Subject: [PATCH 30/34] Rust: Another test case (unsafe function). --- .../CWE-825/AccessAfterLifetime.expected | 30 ++++++++++++------- .../query-tests/security/CWE-825/lifetime.rs | 17 +++++++++++ .../test/query-tests/security/CWE-825/main.rs | 5 ++++ 3 files changed, 42 insertions(+), 10 deletions(-) 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 f99bb399fd41..06010d93003e 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,8 @@ | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | 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:734:12:734:13 | r1 | lifetime.rs:719:26:719:34 | &... | lifetime.rs:734:12:734:13 | r1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:719:19:719:20 | v2 | v2 | -| lifetime.rs:791:10:791:12 | ptr | lifetime.rs:781:9:781:12 | &val | lifetime.rs:791:10:791:12 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:779:6:779:8 | val | val | +| 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:10:808:12 | ptr | lifetime.rs:798:9:798:12 | &val | lifetime.rs:808:10:808:12 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:796:6:796:8 | val | val | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151:15 | p1 | provenance | | | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:158:14:158:15 | p1 | provenance | | @@ -195,10 +196,14 @@ edges | lifetime.rs:769:6:769:8 | ptr | lifetime.rs:771:12:771:14 | ptr | provenance | | | lifetime.rs:769:12:769:23 | &val | lifetime.rs:769:12:769:23 | ptr | provenance | | | lifetime.rs:769:12:769:23 | ptr | lifetime.rs:769:6:769:8 | ptr | provenance | | -| lifetime.rs:781:2:781:12 | return ... | lifetime.rs:785:12:785:24 | get_pointer(...) | provenance | | -| lifetime.rs:781:9:781:12 | &val | lifetime.rs:781:2:781:12 | return ... | provenance | | -| lifetime.rs:785:6:785:8 | ptr | lifetime.rs:791:10:791:12 | ptr | provenance | | -| lifetime.rs:785:12:785:24 | get_pointer(...) | lifetime.rs:785:6:785:8 | ptr | provenance | | +| lifetime.rs:781:2:781:19 | return ... | lifetime.rs:785:11:785:41 | get_local_for_unsafe_function(...) | provenance | | +| lifetime.rs:781:9:781:19 | &my_local10 | lifetime.rs:781:2:781:19 | return ... | provenance | | +| lifetime.rs:785:6:785:7 | p1 | lifetime.rs:789:12:789:13 | p1 | provenance | | +| lifetime.rs:785:11:785:41 | get_local_for_unsafe_function(...) | lifetime.rs:785:6:785:7 | p1 | provenance | | +| lifetime.rs:798:2:798:12 | return ... | lifetime.rs:802:12:802:24 | get_pointer(...) | provenance | | +| 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:10:808:12 | ptr | provenance | | +| lifetime.rs:802:12:802:24 | get_pointer(...) | lifetime.rs:802:6:802:8 | ptr | provenance | | models | 1 | Summary: lang:core; crate::ptr::from_ref; Argument[0]; ReturnValue; value | nodes @@ -405,9 +410,14 @@ nodes | lifetime.rs:769:12:769:23 | &val | semmle.label | &val | | lifetime.rs:769:12:769:23 | ptr | semmle.label | ptr | | lifetime.rs:771:12:771:14 | ptr | semmle.label | ptr | -| lifetime.rs:781:2:781:12 | return ... | semmle.label | return ... | -| lifetime.rs:781:9:781:12 | &val | semmle.label | &val | -| lifetime.rs:785:6:785:8 | ptr | semmle.label | ptr | -| lifetime.rs:785:12:785:24 | get_pointer(...) | semmle.label | get_pointer(...) | -| lifetime.rs:791:10:791:12 | ptr | semmle.label | ptr | +| lifetime.rs:781:2:781:19 | return ... | semmle.label | return ... | +| lifetime.rs:781:9:781:19 | &my_local10 | semmle.label | &my_local10 | +| lifetime.rs:785:6:785:7 | p1 | semmle.label | p1 | +| lifetime.rs:785:11:785:41 | get_local_for_unsafe_function(...) | semmle.label | get_local_for_unsafe_function(...) | +| lifetime.rs:789:12:789:13 | p1 | semmle.label | p1 | +| lifetime.rs:798:2:798:12 | return ... | semmle.label | return ... | +| lifetime.rs:798:9:798:12 | &val | semmle.label | &val | +| 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:10:808:12 | ptr | semmle.label | ptr | subpaths 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 fc5d64263163..22ae67b543c8 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -773,6 +773,23 @@ pub fn test_macros() { } } +// --- unsafe function --- + +fn get_local_for_unsafe_function() -> *const f64 { + let my_local10: f64 = 1.23; + + return &my_local10; // $ Source[rust/access-after-lifetime-ended]=local10 +} // (return value immediately becomes dangling) + +pub unsafe fn test_unsafe_function() { + let p1 = get_local_for_unsafe_function(); + + use_the_stack(); + + let v1 = *p1; // $ Alert[rust/access-after-lifetime-ended]=local10 + println!(" v1 = {v1} (!)"); // corrupt in practice +} + // --- examples from qhelp --- fn get_pointer() -> *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 e134c212ba09..5f66313ae85e 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -190,6 +190,11 @@ fn main() { println!("test_macros:"); test_macros(); + println!("test_unsafe_function:"); + unsafe { + test_unsafe_function(); + } + println!("test_lifetimes_example_bad:"); test_lifetimes_example_bad(); From 5edd6e85e7782aeabb4b43782f6f9d8f5ae063a9 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:45:58 +0100 Subject: [PATCH 31/34] Rust: Restrict results to 'unsafe' blocks. --- rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql | 5 +++++ .../security/CWE-825/AccessAfterLifetime.expected | 1 - rust/ql/test/query-tests/security/CWE-825/lifetime.rs | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql index 0661932a41f6..b4f652668b71 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql @@ -40,6 +40,11 @@ where AccessAfterLifetimeFlow::flowPath(sourceNode, sinkNode) and // check that the dereference is outside the lifetime of the target AccessAfterLifetime::dereferenceAfterLifetime(sourceNode.getNode(), sinkNode.getNode(), target) and + // include only results inside `unsafe` blocks, as other results tend to be false positives + ( + sinkNode.getNode().asExpr().getExpr().getEnclosingBlock*().isUnsafe() or + sinkNode.getNode().asExpr().getExpr().getEnclosingCallable().(Function).isUnsafe() + ) and // exclude cases with sources / sinks in macros, since these results are difficult to interpret not sourceNode.getNode().asExpr().getExpr().isFromMacroExpansion() and not sinkNode.getNode().asExpr().getExpr().isFromMacroExpansion() 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 06010d93003e..c4f00465654a 100644 --- a/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected +++ b/rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.expected @@ -21,7 +21,6 @@ | lifetime.rs:659:15:659:18 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:659:15:659:18 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | 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:734:12:734:13 | r1 | lifetime.rs:719:26:719:34 | &... | lifetime.rs:734:12:734:13 | r1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:719:19:719:20 | v2 | v2 | | 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:10:808:12 | ptr | lifetime.rs:798:9:798:12 | &val | lifetime.rs:808:10:808:12 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:796:6:796:8 | val | val | edges 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 22ae67b543c8..559b2d96bf2f 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -716,7 +716,7 @@ enum MyEnum3 { impl MyEnum3 { pub fn test_match(&self) -> &i64 { let r1 = match self { - MyEnum3::Value(v2) => &v2.value, // $ SPURIOUS: Source[rust/access-after-lifetime-ended]=v2_value + MyEnum3::Value(v2) => &v2.value, }; r1 @@ -731,7 +731,7 @@ pub fn test_enum_members() { use_the_stack(); - let v3 = *r1; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=v2_value + let v3 = *r1; println!(" v3 = {v3}"); } From 36cf4b613e332dd2249def2a4efa4a984b4716d4 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 18 Jun 2025 17:32:20 +0100 Subject: [PATCH 32/34] Rust: Accept consistency changes. --- .../CWE-825/CONSISTENCY/PathResolutionConsistency.expected | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/PathResolutionConsistency.expected index 804c13f6434b..b92e3920e3cd 100644 --- a/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/PathResolutionConsistency.expected +++ b/rust/ql/test/query-tests/security/CWE-825/CONSISTENCY/PathResolutionConsistency.expected @@ -1,11 +1,11 @@ multiplePathResolutions | deallocation.rs:106:16:106:19 | libc | file://:0:0:0:0 | Crate(libc@0.2.171) | -| deallocation.rs:106:16:106:19 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) | +| deallocation.rs:106:16:106:19 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) | | deallocation.rs:106:16:106:27 | ...::malloc | file://:0:0:0:0 | fn malloc | | deallocation.rs:106:16:106:27 | ...::malloc | file://:0:0:0:0 | fn malloc | | deallocation.rs:112:3:112:6 | libc | file://:0:0:0:0 | Crate(libc@0.2.171) | -| deallocation.rs:112:3:112:6 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) | +| deallocation.rs:112:3:112:6 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) | | deallocation.rs:112:3:112:12 | ...::free | file://:0:0:0:0 | fn free | | deallocation.rs:112:3:112:12 | ...::free | file://:0:0:0:0 | fn free | | deallocation.rs:112:29:112:32 | libc | file://:0:0:0:0 | Crate(libc@0.2.171) | -| deallocation.rs:112:29:112:32 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) | +| deallocation.rs:112:29:112:32 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) | From b82a7ab7452d750c1d3ca00cee0dd23b15d6e421 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:18:51 +0100 Subject: [PATCH 33/34] Rust: Update variable name in examples. --- .../security/CWE-825/AccessAfterLifetimeBad.rs | 4 ++-- .../security/CWE-825/AccessAfterLifetimeGood.rs | 4 ++-- .../security/CWE-825/AccessAfterLifetime.expected | 6 +++--- .../ql/test/query-tests/security/CWE-825/lifetime.rs | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs index c5f5cf607d10..b2512f9424f2 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs @@ -7,12 +7,12 @@ fn get_pointer() -> *const i64 { fn example() { let ptr = get_pointer(); - let val; + let dereferenced_ptr; // ... unsafe { - val = *ptr; // BAD: dereferences `ptr` after the lifetime of `val` has ended + dereferenced_ptr = *ptr; // BAD: dereferences `ptr` after the lifetime of `val` has ended } // ... diff --git a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs index 944f6905b70b..84f19a8a6c90 100644 --- a/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs +++ b/rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs @@ -7,11 +7,11 @@ fn get_box() -> Box { fn example() { let ptr = get_box(); - let val; + let dereferenced_ptr; // ... - val = *ptr; // GOOD + dereferenced_ptr = *ptr; // GOOD // ... } 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 c4f00465654a..2f4c38e4f363 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,7 @@ | lifetime.rs:667:14:667:17 | ref1 | lifetime.rs:654:31:654:35 | &str1 | lifetime.rs:667:14:667:17 | ref1 | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:653:8:653:11 | str1 | str1 | | 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:10:808:12 | ptr | lifetime.rs:798:9:798:12 | &val | lifetime.rs:808:10:808:12 | ptr | Access of a pointer to $@ after its lifetime has ended. | lifetime.rs:796:6:796:8 | val | val | +| 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 | edges | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:151:14:151:15 | p1 | provenance | | | deallocation.rs:148:6:148:7 | p1 | deallocation.rs:158:14:158:15 | p1 | provenance | | @@ -201,7 +201,7 @@ edges | lifetime.rs:785:11:785:41 | get_local_for_unsafe_function(...) | lifetime.rs:785:6:785:7 | p1 | provenance | | | lifetime.rs:798:2:798:12 | return ... | lifetime.rs:802:12:802:24 | get_pointer(...) | provenance | | | 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:10:808:12 | ptr | 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 | | models | 1 | Summary: lang:core; crate::ptr::from_ref; Argument[0]; ReturnValue; value | @@ -418,5 +418,5 @@ nodes | lifetime.rs:798:9:798:12 | &val | semmle.label | &val | | 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:10:808:12 | ptr | semmle.label | ptr | +| lifetime.rs:808:23:808:25 | ptr | semmle.label | ptr | subpaths 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 559b2d96bf2f..f388aff5aaf2 100644 --- a/rust/ql/test/query-tests/security/CWE-825/lifetime.rs +++ b/rust/ql/test/query-tests/security/CWE-825/lifetime.rs @@ -800,15 +800,15 @@ fn get_pointer() -> *const i64 { pub fn test_lifetimes_example_bad() { let ptr = get_pointer(); - let val; + let dereferenced_ptr; use_the_stack(); unsafe { - val = *ptr; // $ Alert[rust/access-after-lifetime-ended]=val + dereferenced_ptr = *ptr; // $ Alert[rust/access-after-lifetime-ended]=val } - println!(" val = {val} (!)"); // corrupt in practice + println!(" val = {dereferenced_ptr} (!)"); // corrupt in practice } fn get_box() -> Box { @@ -819,11 +819,11 @@ fn get_box() -> Box { pub fn test_lifetimes_example_good() { let ptr = get_box(); - let val; + let dereferenced_ptr; use_the_stack(); - val = *ptr; // GOOD + dereferenced_ptr = *ptr; // GOOD - println!(" val = {val}"); + println!(" val = {dereferenced_ptr}"); } From 869c974745eb371ece5d9d19db6fda5d94da1698 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:34:54 +0100 Subject: [PATCH 34/34] Rust: Change note. --- .../change-notes/2025-06-24-access-after-lifetime-ended.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rust/ql/src/change-notes/2025-06-24-access-after-lifetime-ended.md diff --git a/rust/ql/src/change-notes/2025-06-24-access-after-lifetime-ended.md b/rust/ql/src/change-notes/2025-06-24-access-after-lifetime-ended.md new file mode 100644 index 000000000000..7b92a3de78b7 --- /dev/null +++ b/rust/ql/src/change-notes/2025-06-24-access-after-lifetime-ended.md @@ -0,0 +1,4 @@ +--- +category: newQuery +--- +* Added a new query, `rust/access-after-lifetime-ended`, for detecting pointer dereferences after the lifetime of the pointed-to object has ended.