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] 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 | 132 +++++++++++++++--- .../test/query-tests/security/CWE-825/main.rs | 10 +- 2 files changed, 118 insertions(+), 24 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 ddf8badd350..3dc74b97830 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 e1 { - MyEnum::Value(x) => { &x } - }; // (x goes out of scope, so result is dangling, I think; seen in real world code) - - use_the_stack(); - - unsafe { - let v1 = *result; // $ MISSING: Alert - println!(" v1 = {v1}"); - } - } // (e1 goes out of scope, so result is definitely dangling now) + 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) use_the_stack(); unsafe { - let v2 = *result; // $ MISSING: Alert - println!(" v2 = {v2}"); // dropped in practice + let v0 = *result; // ? + println!(" v0 = {v0} (?)"); + } + + 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 { + 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 a3493497bec..d2316fea79b 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(); }