From 968c233cd199fdbcb928f27fd2309045db45733f Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:48:13 +0000 Subject: [PATCH 01/10] Rust: Add tests for dangling pointers. --- .../test/query-tests/security/CWE-825/main.rs | 674 ++++++++++++++++++ .../query-tests/security/CWE-825/options.yml | 3 + 2 files changed, 677 insertions(+) create mode 100644 rust/ql/test/query-tests/security/CWE-825/main.rs create mode 100644 rust/ql/test/query-tests/security/CWE-825/options.yml diff --git a/rust/ql/test/query-tests/security/CWE-825/main.rs b/rust/ql/test/query-tests/security/CWE-825/main.rs new file mode 100644 index 000000000000..8636e7a65920 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -0,0 +1,674 @@ +#![allow(static_mut_refs)] +#![feature(box_as_ptr)] +#![feature(box_into_inner)] + +fn use_the_stack() { + let _buffer: [u8; 256] = [0xFF; 256]; +} + +fn use_the_heap() { + let _a = Box::new(0x7FFFFFFF); + let _b = Box::new(0x7FFFFFFF); +} + +struct MyValue { + value: i64 +} + +impl Drop for MyValue { + fn drop(&mut self) { + println!(" drop MyValue '{}'", self.value) + } +} + +// --- local dangling --- + +fn get_local_dangling() -> *const i64 { + let my_local1: i64 = 1; + + return &my_local1; // immediately becomes dangling +} + +fn get_local_dangling_mut() -> *mut i64 { + let mut my_local2: i64 = 2; + + return &mut my_local2; // immediately becomes dangling +} + +fn get_local_dangling_raw_const() -> *const i64 { + let my_local3: i64 = 3; + + return &raw const my_local3; // immediately becomes dangling +} + +fn get_local_dangling_raw_mut() -> *mut i64 { + let mut my_local4: i64 = 4; + + return &raw mut my_local4; // immediately becomes dangling +} + +fn get_param_dangling(param5: i64) -> *const i64 { + return ¶m5; // immediately becomes dangling +} + +fn get_local_field_dangling() -> *const i64 { + let val: MyValue; + + val = MyValue { value: 6 }; + return &val.value; +} + +fn test_local_dangling() { + let p1 = get_local_dangling(); + let p2 = get_local_dangling_mut(); + let p3 = get_local_dangling_raw_const(); + let p4 = get_local_dangling_raw_mut(); + let p5 = get_param_dangling(5); + let p6 = get_local_field_dangling(); + let p7: *const i64; + { + let my_local7 = 7; + p7 = &raw const my_local7; + } // my_local goes out of scope, thus p7 is dangling + + use_the_stack(); + + unsafe { + let v1 = *p1; // BAD + let v2 = *p2; // BAD + let v3 = *p3; // BAD + let v4 = *p4; // BAD + let v5 = *p5; // BAD + let v6 = *p6; // BAD + let v7 = *p7; // BAD + *p2 = 8; // BAD + *p4 = 9; // BAD + + println!(" v1 = {v1} (!)"); // corrupt + println!(" v2 = {v2} (!)"); // corrupt + println!(" v3 = {v3} (!)"); // corrupt + println!(" v4 = {v4} (!)"); // corrupt + println!(" v5 = {v5} (!)"); // corrupt + println!(" v6 = {v6} (!)"); // corrupt + println!(" v7 = {v7} (!)"); // potentially corrupt + } +} + +// --- local in scope --- + +fn use_pointers(p1: *const i64, p2: *mut i64) { + let p3: *const i64; + let my_local10 = 10; + p3 = &my_local10; + + use_the_stack(); + + unsafe { + let v1 = *p1; + let v2 = *p2; + let v3 = *p3; + *p2 = 10; + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + println!(" v3 = {v3}"); + } +} + +fn test_local_in_scope() { + let my_local11: i64 = 11; + let mut my_local_mut12: i64 = 12; + + use_pointers(&my_local11, &mut my_local_mut12); +} + +// --- static lifetime pointers --- + +const MY_GLOBAL_CONST: i64 = 20; + +fn get_const() -> *const i64 { + return &MY_GLOBAL_CONST; +} + +static mut MY_GLOBAL_STATIC: i64 = 21; + +fn get_static_mut() -> *mut i64 { + unsafe { + return &mut MY_GLOBAL_STATIC; + } +} + +fn test_static() { + let p1 = get_const(); + let p2 = get_static_mut(); + + use_the_stack(); + + unsafe { + let v1 = *p1; + let v2 = *p2; + *p2 = 22; + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + } +} + +// --- overwritten object --- + +fn test_overwrite(maybe: bool) { + let mut val: MyValue; + + val = MyValue { value: 30 }; + let p1: *const i64 = &val.value; + + val = MyValue { value: 31 }; // original msg goes out of scope, thus p1 is dangling + let p2: *const i64 = &val.value; + + if maybe { + val = MyValue { value: 32 }; // thus p2 is (maybe) dangling also + } + let p3: *const i64 = &val.value; + + { + let val: MyValue; + val = MyValue { value: 33 }; // doesn't overwrite shadowed `val` + _ = val.value; + } + + unsafe { + let v1 = *p1; // BAD + let v2 = *p2; // BAD + let v3 = *p3; + println!(" v1 = {v1} (!)"); // corrupt + println!(" v2 = {v2} (!)"); // corrupt + println!(" v3 = {v3}"); + } +} + +// --- call contexts --- + +fn access_ptr_1(ptr: *const i64) { + // only called with `ptr` safe + unsafe { + let v1 = *ptr; + println!(" v1 = {v1}"); + } +} + +fn access_ptr_2(ptr: *const i64) { + // only called with `ptr` dangling + unsafe { + let v2 = *ptr; // BAD + println!(" v2 = {v2} (!)"); // corrupt + } +} + +fn access_ptr_3(ptr: *const i64) { + // called from contexts with `ptr` safe and dangling + unsafe { + let v3 = *ptr; // BAD + println!(" v3 = {v3} (!)"); // corrupt (in one context) + } +} + +fn access_and_get_dangling() -> *const i64 { + let my_local40 = 40; + let ptr = &my_local40; + + access_ptr_1(ptr); + access_ptr_3(ptr); + + return ptr; // becomes dangling here +} + +fn test_call_contexts() { + let ptr = access_and_get_dangling(); + + use_the_stack(); + + access_ptr_2(ptr); + access_ptr_3(ptr); +} + +// --- call contexts (recursive) --- + +fn access_ptr_rec(ptr_up: *const i64, count: i64) -> *const i64 { + let my_local_rec = 50 + count; + let ptr_ours = &my_local_rec; + + if count < 5 { + let ptr_down = access_ptr_rec(ptr_ours, count + 1); + + use_the_stack(); + + unsafe { + let v_up = *ptr_up; + let v_ours = *ptr_ours; + let v_down = *ptr_down; // BAD + println!(" v_up = {v_up}"); + println!(" v_ours = {v_ours}"); + println!(" v_down = {v_down} (!)"); // potentially corrupt + } + } + + return ptr_ours; // becomes dangling here +} + +fn test_call_contexts_rec() { + let my_local_rec2 = 50; + let ptr_start = &my_local_rec2; + + _ = access_ptr_rec(ptr_start, 1); +} + +// --- boxes --- + +fn test_boxes_1(do_dangerous_writes: bool) { + let p1: *const i64; + let p2: *const i64; + let p3: *mut i64; + + { + let b1: Box = Box::new(1); + p1 = Box::into_raw(b1); // now owned by p1 + + let b2: Box = Box::new(2); + p2 = Box::as_ptr(&b2); // still owned by b2 + + let mut b3: Box = Box::new(3); + p3 = Box::as_mut_ptr(&mut b3); // still owned by b3 + + unsafe { + let v1 = *p1; + let v2 = *p2; + let v3 = *p3; + *p3 = 4; + + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + println!(" v3 = {v3}"); + } + } // b2, b3 go out of scope, thus p2, p3 are dangling + + unsafe { + let v4 = *p1; + let v5 = *p2; // BAD + let v6 = *p3; // BAD + + if do_dangerous_writes { + *p3 = 5; // BAD + use_the_heap(); // "malloc: Heap corruption detected" "Incorrect guard value: 34" + } + + println!(" v4 = {v4}"); + println!(" v5 = {v5} (!)"); // corrupt + println!(" v6 = {v6} (!)"); // corrupt + } +} + +fn test_boxes_2() { + let b1: Box = Box::new(10); // b1 owns the memory for '40' + let p1 = Box::into_raw(b1); // now p1 owns the memory + + unsafe { + let _b2 = Box::from_raw(p1); // now b2 owns the memory + + let v1 = *p1; + println!(" v1 = {v1}"); + } // b2 goes out of scope, thus the memory is freed and p1 is dangling + + unsafe { + let v2 = *p1; // BAD + println!(" v2 = {v2} (!)"); // corrupt + } +} + +fn test_boxes_3() { + let b1: Box = Box::new(20); // b1 owns the memory for '50' + let p1 = Box::as_ptr(&b1); // b1 still owns the memory + + unsafe { + let v1 = *p1; + println!(" v1 = {v1}"); + } + + let v2 = Box::into_inner(b1); // b1 is explicitly freed here, thus p1 is dangling + println!(" v2 = {v2}"); + + unsafe { + let v3 = *p1; // BAD + println!(" v3 = {v3} (!)"); // corrupt + } +} + +// --- rc (reference counting pointer) --- + +fn test_rc() { + let p1: *const i64; + let p2: *const i64; + + { + let rc1: std::rc::Rc = std::rc::Rc::new(1); + p1 = std::rc::Rc::::as_ptr(&rc1); + + { + let rc2: std::rc::Rc = std::rc::Rc::clone(&rc1); + p2 = std::rc::Rc::::as_ptr(&rc2); + + unsafe { + let v1 = *p1; + let v2 = *p2; + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + } + } // rc2 goes out of scope, but the reference count is still 1 so the pointer remains still valid + + unsafe { + let v3 = *p1; + let v4 = *p2; // good + println!(" v3 = {v3}"); + println!(" v4 = {v4}"); + } + } // rc1 go out of scope, the reference count is 0, so p1, p2 are dangling + + unsafe { + let v5 = *p1; // BAD + let v6 = *p2; // BAD + println!(" v5 = {v5} (!)"); // corrupt + println!(" v6 = {v6} (!)"); // corrupt + } + + // note: simialar things are likely possible with Ref, RefMut, RefCell, + // Vec and others. +} + +// --- std::ptr --- + +#[derive(Debug)] +struct MyPair { + a: i64, + b: i64 +} + +fn test_ptr_to_struct() { + let p1: *mut MyPair; + let p2: *const i64; + let p3: *mut i64; + + { + 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); + + unsafe { + let v1 = (*p1).a; + let v2 = (*p1).b; + let v3 = *p2; + let v4 = *p3; + (*p1).a = 3; + (*p1).b = 4; + *p3 = 5; + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + println!(" v3 = {v3}"); + println!(" v4 = {v4}"); + } + }; // my_pair goes out of scope, thus p1, p2, p3 are dangling + + unsafe { + let v5 = (*p1).a; // BAD + let v6 = (*p1).b; // BAD + let v7 = *p2; // BAD + let v8 = *p3; // BAD + (*p1).a = 6; // BAD + (*p1).b = 7; // BAD + *p3 = 8; // BAD + println!(" v5 = {v5} (!)"); // potentially corrupt + println!(" v6 = {v6} (!)"); // potentially corrupt + println!(" v7 = {v7} (!)"); // potentially corrupt + println!(" v8 = {v8} (!)"); // potentially corrupt + } +} + +fn test_ptr_explicit(do_dangerous_access: bool) { + let p1: *const i64 = std::ptr::dangling(); + let p2: *mut i64 = std::ptr::dangling_mut(); + let p3: *const i64 = std::ptr::null(); + + if do_dangerous_access { + unsafe { + // (a segmentation fault occurs in the code below) + let v1 = *p1; // BAD + let v2 = *p2; // BAD + let v3 = *p3; // BAD + println!(" v1 = {v1} (!)"); + println!(" v2 = {v2} (!)"); + println!(" v3 = {v3} (!)"); + } + } +} + +fn get_ptr_from_ref(val: i32) -> *const i32 { + let my_val = val; + let r1: &i32 = &my_val; + let p1: *const i32 = std::ptr::from_ref(r1); + + unsafe { + let v1 = *p1; + println!(" v1 = {v1}"); + } + + return p1; // becomes dangling here +} + +fn test_ptr_from_ref() { + let p1 = get_ptr_from_ref(1); + + use_the_stack(); + + unsafe { + let v2 = *p1; // BAD + let v3 = *get_ptr_from_ref(2); // BAD + println!(" v2 = {v2} (!)"); // corrupt + println!(" v3 = {v3} (!)"); // potentially corrupt + } +} + +// --- alloc and variations --- + +fn test_alloc(do_dangerous_writes: bool) { + unsafe { + let layout = std::alloc::Layout::new::(); + let m1 = std::alloc::alloc(layout); // *mut u8 + let m2 = m1 as *mut i64; // *mut i64 + *m2 = 1; + + let v1 = *m1; + let v2 = *m2; + let v3 = std::ptr::read::(m1); + let v4 = std::ptr::read::(m2); + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + println!(" v3 = {v3}"); + println!(" v4 = {v4}"); + + std::alloc::dealloc(m1, layout); // m1, m2 are now dangling + + let v5 = *m1; + let v6 = *m2; + let v7 = std::ptr::read::(m1); // BAD + let v8 = std::ptr::read::(m2); // BAD + println!(" v5 = {v5} (!)"); // corrupt + println!(" v6 = {v6} (!)"); // corrupt + println!(" v7 = {v7} (!)"); // corrupt + println!(" v8 = {v8} (!)"); // corrupt + + if do_dangerous_writes { + *m1 = 2; // BAD + *m2 = 3; // BAD + std::ptr::write::(m1, 4); // BAD + std::ptr::write::(m2, 5); // BAD + } + } +} + +fn test_alloc_array(do_dangerous_writes: bool) { + unsafe { + let layout = std::alloc::Layout::new::<[u8; 10]>(); + let m1 = std::alloc::alloc(layout); + let m2 = m1 as *mut [u8; 10]; + (*m2)[0] = 1; + (*m2)[1] = 2; + + let v1 = (*m2)[0]; + let v2 = (*m2)[1]; + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + + std::alloc::dealloc(m2 as *mut u8, layout); // m1, m2 are now dangling + + let v3 = (*m2)[0]; // BAD + let v4 = (*m2)[1]; // BAD + println!(" v3 = {v3} (!)"); // corrupt + println!(" v4 = {v4} (!)"); // corrupt + + if do_dangerous_writes { + (*m2)[0] = 3; // BAD + (*m2)[1] = 4; // BAD + std::ptr::write::(m1, 5); // BAD + std::ptr::write::<[u8; 10]>(m2, [6; 10]); // BAD + } + } +} + +fn test_libc() { + unsafe { + let my_ptr = libc::malloc(256) as *mut i64; + *my_ptr = 1; + + let v1 = *my_ptr; + println!(" v1 = {v1}"); + + libc::free(my_ptr as *mut libc::c_void); + + let v2 = *my_ptr; // BAD + println!(" v2 = {v2} (!)"); // corrupt + } +} + +// --- loops --- + +fn test_loop() { + let my_local1 = 0; + let first: *const i32 = &my_local1; + let mut prev: *const i32 = &my_local1; + + for i in 1..5 { + let my_local2 = i; + let ours: *const i32 = &my_local2; + + use_the_stack(); + + unsafe { + let v1 = *first; + let v2 = *ours; + let v3 = *prev; // BAD + println!(" v1 = {v1}"); + println!(" v2 = {v2}"); + println!(" v3 = {v3} (!)"); // corrupt + } + + prev = ours; + } +} + +// --- enum --- + +enum MyEnum { + Value(i64), +} + +fn test_enum() { + let result: *const i64; + + { + let e1 = MyEnum::Value(1); + + result = match e1 { + MyEnum::Value(x) => { &x } + }; + + use_the_stack(); + + unsafe { + // not sure if this is OK or BAD. Seen in real world code. + let v1 = *result; + println!(" v1 = {v1}"); + } + } + + use_the_stack(); + + unsafe { + let v2 = *result; // BAD + println!(" v2 = {v2}"); + } +} + +// --- main --- + +fn main() { + println!("test_local_dangling:"); + test_local_dangling(); + + println!("test_local_in_scope:"); + test_local_in_scope(); + + println!("test_static:"); + test_static(); + + println!("test_overwrite:"); + test_overwrite(true); + + println!("test_call_contexts:"); + test_call_contexts(); + + println!("test_call_contexts_rec:"); + test_call_contexts_rec(); + + println!("test_boxes_1:"); + test_boxes_1(false); + + println!("test_boxes_2:"); + test_boxes_2(); + + println!("test_boxes_3:"); + test_boxes_3(); + + println!("test_rc:"); + test_rc(); + + println!("test_ptr_to_struct:"); + test_ptr_to_struct(); + + println!("test_ptr_explicit:"); + test_ptr_explicit(false); + + println!("test_ptr_from_ref:"); + test_ptr_from_ref(); + + println!("test_alloc:"); + test_alloc(false); + + println!("test_alloc_array:"); + test_alloc_array(false); + + println!("test_libc_malloc:"); + test_libc(); + + println!("test_loop:"); + test_loop(); + + println!("test_enum:"); + test_enum(); +} diff --git a/rust/ql/test/query-tests/security/CWE-825/options.yml b/rust/ql/test/query-tests/security/CWE-825/options.yml new file mode 100644 index 000000000000..95a17a53b431 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/options.yml @@ -0,0 +1,3 @@ +qltest_cargo_check: true +qltest_dependencies: + - libc = { version = "0.2.11" } From 1327e2095d04f6216e6a665063a7fdfef459f2c2 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:33:14 +0000 Subject: [PATCH 02/10] Rust: Identify flow from pointer creation to dereferences. Annotate test properly. --- .../security/DanglingPointerExtensions.qll | 31 ++++ .../security/CWE-825/PointerDerefs.expected | 0 .../security/CWE-825/PointerDerefs.ql | 25 +++ .../test/query-tests/security/CWE-825/main.rs | 172 +++++++++--------- 4 files changed, 142 insertions(+), 86 deletions(-) create mode 100644 rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll create mode 100644 rust/ql/test/query-tests/security/CWE-825/PointerDerefs.expected create mode 100644 rust/ql/test/query-tests/security/CWE-825/PointerDerefs.ql diff --git a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll new file mode 100644 index 000000000000..7457d73b26aa --- /dev/null +++ b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll @@ -0,0 +1,31 @@ +import rust +private import codeql.rust.dataflow.DataFlow + +/** + * Holds if `createsPointer` creates a pointer pointing at `targetValue`. + */ +predicate createsPointer(DataFlow::Node createsPointer, DataFlow::Node targetValue) { + exists(RefExpr re | + re = createsPointer.asExpr().getExpr() and + re.getExpr() = targetValue.asExpr().getExpr() + ) +} + +/** + * Holds if `derefPointer` dereferences a pointer (in unsafe code). + */ +predicate dereferencesPointer(DataFlow::Node derefPointer) { + exists(PrefixExpr pe | + pe.getOperatorName() = "*" and + pe.getExpr() = derefPointer.asExpr().getExpr() + ) +} + +/** + * A taint configuration for a pointer that is created and later dereferenced. + */ +module PointerDereferenceConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { createsPointer(node, _) } + + predicate isSink(DataFlow::Node node) { dereferencesPointer(node) } +} diff --git a/rust/ql/test/query-tests/security/CWE-825/PointerDerefs.expected b/rust/ql/test/query-tests/security/CWE-825/PointerDerefs.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/rust/ql/test/query-tests/security/CWE-825/PointerDerefs.ql b/rust/ql/test/query-tests/security/CWE-825/PointerDerefs.ql new file mode 100644 index 000000000000..01a869cf010e --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/PointerDerefs.ql @@ -0,0 +1,25 @@ +import rust +import codeql.rust.dataflow.DataFlow +import codeql.rust.dataflow.TaintTracking +import codeql.rust.security.DanglingPointerExtensions +import utils.InlineExpectationsTest + +module PointerDereferenceFlow = TaintTracking::Global; + +module PointerDereferenceTest implements TestSig { + string getARelevantTag() { result = "deref" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(DataFlow::Node sourceNode, DataFlow::Node sinkNode, DataFlow::Node targetValue | + PointerDereferenceFlow::flow(sourceNode, sinkNode) and + createsPointer(sourceNode, targetValue) and + location = sinkNode.getLocation() and + location.getFile().getBaseName() != "" and + element = sinkNode.toString() and + tag = "deref" and + value = targetValue.toString() + ) + } +} + +import MakeTest 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 8636e7a65920..a068aec65c09 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -74,15 +74,15 @@ fn test_local_dangling() { use_the_stack(); unsafe { - let v1 = *p1; // BAD - let v2 = *p2; // BAD - let v3 = *p3; // BAD - let v4 = *p4; // BAD - let v5 = *p5; // BAD - let v6 = *p6; // BAD - let v7 = *p7; // BAD - *p2 = 8; // BAD - *p4 = 9; // BAD + let v1 = *p1; // $ deref=my_local1 MISSING: Alert[rust/dangling-ptr] + let v2 = *p2; // $ deref=my_local2 MISSING: Alert[rust/dangling-ptr] + let v3 = *p3; // $ deref=my_local3 MISSING: Alert[rust/dangling-ptr] + let v4 = *p4; // $ deref=my_local4 MISSING: Alert[rust/dangling-ptr] + let v5 = *p5; // $ deref=param5 MISSING: Alert[rust/dangling-ptr] + let v6 = *p6; // $ deref=val.value MISSING: Alert[rust/dangling-ptr] + let v7 = *p7; // $ deref=my_local7 MISSING: Alert[rust/dangling-ptr] + *p2 = 8; // $ deref=my_local2 MISSING: Alert[rust/dangling-ptr] + *p4 = 9; // $ deref=my_local4 MISSING: Alert[rust/dangling-ptr] println!(" v1 = {v1} (!)"); // corrupt println!(" v2 = {v2} (!)"); // corrupt @@ -104,10 +104,10 @@ fn use_pointers(p1: *const i64, p2: *mut i64) { use_the_stack(); unsafe { - let v1 = *p1; - let v2 = *p2; - let v3 = *p3; - *p2 = 10; + let v1 = *p1; // $ deref=my_local11 + let v2 = *p2; // $ deref=my_local_mut12 + let v3 = *p3; // $ deref=my_local10 + *p2 = 10; // $ deref=my_local_mut12 println!(" v1 = {v1}"); println!(" v2 = {v2}"); println!(" v3 = {v3}"); @@ -144,9 +144,9 @@ fn test_static() { use_the_stack(); unsafe { - let v1 = *p1; - let v2 = *p2; - *p2 = 22; + let v1 = *p1; // $ deref=MY_GLOBAL_CONST + let v2 = *p2; // $ deref=MY_GLOBAL_STATIC + *p2 = 22; // $ deref=MY_GLOBAL_STATIC println!(" v1 = {v1}"); println!(" v2 = {v2}"); } @@ -175,9 +175,9 @@ fn test_overwrite(maybe: bool) { } unsafe { - let v1 = *p1; // BAD - let v2 = *p2; // BAD - let v3 = *p3; + let v1 = *p1; // $ deref=val.value MISSING: Alert[rust/dangling-ptr] + let v2 = *p2; // $ deref=val.value MISSING: Alert[rust/dangling-ptr] + let v3 = *p3; // $ deref=val.value println!(" v1 = {v1} (!)"); // corrupt println!(" v2 = {v2} (!)"); // corrupt println!(" v3 = {v3}"); @@ -189,7 +189,7 @@ fn test_overwrite(maybe: bool) { fn access_ptr_1(ptr: *const i64) { // only called with `ptr` safe unsafe { - let v1 = *ptr; + let v1 = *ptr; // $ deref=my_local40 println!(" v1 = {v1}"); } } @@ -197,7 +197,7 @@ fn access_ptr_1(ptr: *const i64) { fn access_ptr_2(ptr: *const i64) { // only called with `ptr` dangling unsafe { - let v2 = *ptr; // BAD + let v2 = *ptr; // $ deref=my_local40 MISSING: Alert[rust/dangling-ptr] println!(" v2 = {v2} (!)"); // corrupt } } @@ -205,7 +205,7 @@ fn access_ptr_2(ptr: *const i64) { fn access_ptr_3(ptr: *const i64) { // called from contexts with `ptr` safe and dangling unsafe { - let v3 = *ptr; // BAD + let v3 = *ptr; // $ deref=my_local40 MISSING: Alert[rust/dangling-ptr] println!(" v3 = {v3} (!)"); // corrupt (in one context) } } @@ -241,9 +241,9 @@ fn access_ptr_rec(ptr_up: *const i64, count: i64) -> *const i64 { use_the_stack(); unsafe { - let v_up = *ptr_up; - let v_ours = *ptr_ours; - let v_down = *ptr_down; // BAD + let v_up = *ptr_up; // $ deref=my_local_rec2 deref=my_local_rec + let v_ours = *ptr_ours; // $ deref=my_local_rec + let v_down = *ptr_down; // $ deref=my_local_rec MISSING: Alert[rust/dangling-ptr] println!(" v_up = {v_up}"); println!(" v_ours = {v_ours}"); println!(" v_down = {v_down} (!)"); // potentially corrupt @@ -290,12 +290,12 @@ fn test_boxes_1(do_dangerous_writes: bool) { } // b2, b3 go out of scope, thus p2, p3 are dangling unsafe { - let v4 = *p1; - let v5 = *p2; // BAD - let v6 = *p3; // BAD + let v4 = *p1; // $ MISSING: deref=b1 + let v5 = *p2; // $ MISSING: deref=b2 MISSING: Alert[rust/dangling-ptr] + let v6 = *p3; // $ MISSING: deref=b3 MISSING: Alert[rust/dangling-ptr] if do_dangerous_writes { - *p3 = 5; // BAD + *p3 = 5; // $ MISSING: deref=b3 MISSING: Alert[rust/dangling-ptr] use_the_heap(); // "malloc: Heap corruption detected" "Incorrect guard value: 34" } @@ -312,12 +312,12 @@ fn test_boxes_2() { unsafe { let _b2 = Box::from_raw(p1); // now b2 owns the memory - let v1 = *p1; + let v1 = *p1; // $ MISSING: deref=b1 println!(" v1 = {v1}"); } // b2 goes out of scope, thus the memory is freed and p1 is dangling unsafe { - let v2 = *p1; // BAD + let v2 = *p1; // $ MISSING: deref=b1 MISSING: Alert[rust/dangling-ptr] println!(" v2 = {v2} (!)"); // corrupt } } @@ -327,7 +327,7 @@ fn test_boxes_3() { let p1 = Box::as_ptr(&b1); // b1 still owns the memory unsafe { - let v1 = *p1; + let v1 = *p1; // $ MISSING: deref=b1 println!(" v1 = {v1}"); } @@ -335,7 +335,7 @@ fn test_boxes_3() { println!(" v2 = {v2}"); unsafe { - let v3 = *p1; // BAD + let v3 = *p1; // $ MISSING: deref=b1 MISSING: Alert[rust/dangling-ptr] println!(" v3 = {v3} (!)"); // corrupt } } @@ -355,24 +355,24 @@ fn test_rc() { p2 = std::rc::Rc::::as_ptr(&rc2); unsafe { - let v1 = *p1; - let v2 = *p2; + let v1 = *p1; // $ MISSING: deref=rc1 + let v2 = *p2; // $ MISSING: deref=rc2 println!(" v1 = {v1}"); println!(" v2 = {v2}"); } } // rc2 goes out of scope, but the reference count is still 1 so the pointer remains still valid unsafe { - let v3 = *p1; - let v4 = *p2; // good + let v3 = *p1; // $ MISSING: deref=rc1 + let v4 = *p2; // $ MISSING: deref=rc2 println!(" v3 = {v3}"); println!(" v4 = {v4}"); } } // rc1 go out of scope, the reference count is 0, so p1, p2 are dangling unsafe { - let v5 = *p1; // BAD - let v6 = *p2; // BAD + let v5 = *p1; // $ MISSING: deref=rc1 MISSING: Alert[rust/dangling-ptr] + let v6 = *p2; // $ MISSING: deref=rc2 MISSING: Alert[rust/dangling-ptr] println!(" v5 = {v5} (!)"); // corrupt println!(" v6 = {v6} (!)"); // corrupt } @@ -401,13 +401,13 @@ fn test_ptr_to_struct() { p3 = std::ptr::addr_of_mut!(my_pair.b); unsafe { - let v1 = (*p1).a; - let v2 = (*p1).b; - let v3 = *p2; - let v4 = *p3; - (*p1).a = 3; - (*p1).b = 4; - *p3 = 5; + let v1 = (*p1).a; // $ MISSING: deref=? + let v2 = (*p1).b; // $ MISSING: deref=? + let v3 = *p2; // $ MISSING: deref=? + let v4 = *p3; // $ MISSING: deref=? + (*p1).a = 3; // $ MISSING: deref=? + (*p1).b = 4; // $ MISSING: deref=? + *p3 = 5; // $ MISSING: deref=? println!(" v1 = {v1}"); println!(" v2 = {v2}"); println!(" v3 = {v3}"); @@ -416,13 +416,13 @@ fn test_ptr_to_struct() { }; // my_pair goes out of scope, thus p1, p2, p3 are dangling unsafe { - let v5 = (*p1).a; // BAD - let v6 = (*p1).b; // BAD - let v7 = *p2; // BAD - let v8 = *p3; // BAD - (*p1).a = 6; // BAD - (*p1).b = 7; // BAD - *p3 = 8; // BAD + let v5 = (*p1).a; // $ MISSING: deref=? Alert[rust/dangling-ptr] + let v6 = (*p1).b; // $ MISSING: deref=? Alert[rust/dangling-ptr] + let v7 = *p2; // $ MISSING: deref=? Alert[rust/dangling-ptr] + let v8 = *p3; // $ MISSING: deref=? Alert[rust/dangling-ptr] + (*p1).a = 6; // $ MISSING: deref=? Alert[rust/dangling-ptr] + (*p1).b = 7; // $ MISSING: deref=? Alert[rust/dangling-ptr] + *p3 = 8; // $ MISSING: deref=? Alert[rust/dangling-ptr] println!(" v5 = {v5} (!)"); // potentially corrupt println!(" v6 = {v6} (!)"); // potentially corrupt println!(" v7 = {v7} (!)"); // potentially corrupt @@ -438,9 +438,9 @@ fn test_ptr_explicit(do_dangerous_access: bool) { if do_dangerous_access { unsafe { // (a segmentation fault occurs in the code below) - let v1 = *p1; // BAD - let v2 = *p2; // BAD - let v3 = *p3; // BAD + let v1 = *p1; // $ MISSING: deref=? Alert[rust/dangling-ptr] + let v2 = *p2; // $ MISSING: deref=? Alert[rust/dangling-ptr] + let v3 = *p3; // $ MISSING: deref=? Alert[rust/dangling-ptr] println!(" v1 = {v1} (!)"); println!(" v2 = {v2} (!)"); println!(" v3 = {v3} (!)"); @@ -454,7 +454,7 @@ fn get_ptr_from_ref(val: i32) -> *const i32 { let p1: *const i32 = std::ptr::from_ref(r1); unsafe { - let v1 = *p1; + let v1 = *p1; // $ MISSING: deref=my_val Alert[rust/dangling-ptr] println!(" v1 = {v1}"); } @@ -467,8 +467,8 @@ fn test_ptr_from_ref() { use_the_stack(); unsafe { - let v2 = *p1; // BAD - let v3 = *get_ptr_from_ref(2); // BAD + let v2 = *p1; // $ MISSING: deref=my_val MISSING: Alert[rust/dangling-ptr] + let v3 = *get_ptr_from_ref(2); // $ MISSING: deref=my_val MISSING: Alert[rust/dangling-ptr] println!(" v2 = {v2} (!)"); // corrupt println!(" v3 = {v3} (!)"); // potentially corrupt } @@ -483,10 +483,10 @@ fn test_alloc(do_dangerous_writes: bool) { let m2 = m1 as *mut i64; // *mut i64 *m2 = 1; - let v1 = *m1; - let v2 = *m2; - let v3 = std::ptr::read::(m1); - let v4 = std::ptr::read::(m2); + let v1 = *m1; // $ MISSING: deref=? + let v2 = *m2; // $ MISSING: deref=? + let v3 = std::ptr::read::(m1); // $ MISSING: deref=? + let v4 = std::ptr::read::(m2); // $ MISSING: deref=? println!(" v1 = {v1}"); println!(" v2 = {v2}"); println!(" v3 = {v3}"); @@ -494,20 +494,20 @@ fn test_alloc(do_dangerous_writes: bool) { std::alloc::dealloc(m1, layout); // m1, m2 are now dangling - let v5 = *m1; - let v6 = *m2; - let v7 = std::ptr::read::(m1); // BAD - let v8 = std::ptr::read::(m2); // BAD + let v5 = *m1; // $ MISSING: deref=? Alert[rust/dangling-ptr] + let v6 = *m2; // $ MISSING: deref=? Alert[rust/dangling-ptr] + let v7 = std::ptr::read::(m1); // $ MISSING: deref=? Alert[rust/dangling-ptr] + let v8 = std::ptr::read::(m2); // $ MISSING: deref=? Alert[rust/dangling-ptr] println!(" v5 = {v5} (!)"); // corrupt println!(" v6 = {v6} (!)"); // corrupt println!(" v7 = {v7} (!)"); // corrupt println!(" v8 = {v8} (!)"); // corrupt if do_dangerous_writes { - *m1 = 2; // BAD - *m2 = 3; // BAD - std::ptr::write::(m1, 4); // BAD - std::ptr::write::(m2, 5); // BAD + *m1 = 2; // MISSING: deref=? Alert[rust/dangling-ptr] + *m2 = 3; // MISSING: deref=? Alert[rust/dangling-ptr] + std::ptr::write::(m1, 4); // MISSING: deref=? Alert[rust/dangling-ptr] + std::ptr::write::(m2, 5); // MISSING: deref=? Alert[rust/dangling-ptr] } } } @@ -520,23 +520,23 @@ fn test_alloc_array(do_dangerous_writes: bool) { (*m2)[0] = 1; (*m2)[1] = 2; - let v1 = (*m2)[0]; - let v2 = (*m2)[1]; + let v1 = (*m2)[0]; // MISSING: deref=? + let v2 = (*m2)[1]; // MISSING: deref=? println!(" v1 = {v1}"); println!(" v2 = {v2}"); std::alloc::dealloc(m2 as *mut u8, layout); // m1, m2 are now dangling - let v3 = (*m2)[0]; // BAD - let v4 = (*m2)[1]; // BAD + let v3 = (*m2)[0]; // MISSING: deref=? Alert[rust/dangling-ptr] + let v4 = (*m2)[1]; // MISSING: deref=? Alert[rust/dangling-ptr] println!(" v3 = {v3} (!)"); // corrupt println!(" v4 = {v4} (!)"); // corrupt if do_dangerous_writes { - (*m2)[0] = 3; // BAD - (*m2)[1] = 4; // BAD - std::ptr::write::(m1, 5); // BAD - std::ptr::write::<[u8; 10]>(m2, [6; 10]); // BAD + (*m2)[0] = 3; // MISSING: deref=? Alert[rust/dangling-ptr] + (*m2)[1] = 4; // MISSING: deref=? Alert[rust/dangling-ptr] + std::ptr::write::(m1, 5); // MISSING: deref=? Alert[rust/dangling-ptr] + std::ptr::write::<[u8; 10]>(m2, [6; 10]); // MISSING: deref=? Alert[rust/dangling-ptr] } } } @@ -546,12 +546,12 @@ fn test_libc() { let my_ptr = libc::malloc(256) as *mut i64; *my_ptr = 1; - let v1 = *my_ptr; + let v1 = *my_ptr; // MISSING: deref=? println!(" v1 = {v1}"); libc::free(my_ptr as *mut libc::c_void); - let v2 = *my_ptr; // BAD + let v2 = *my_ptr; // MISSING: deref=? Alert[rust/dangling-ptr] println!(" v2 = {v2} (!)"); // corrupt } } @@ -570,9 +570,9 @@ fn test_loop() { use_the_stack(); unsafe { - let v1 = *first; - let v2 = *ours; - let v3 = *prev; // BAD + let v1 = *first; // $ deref=my_local1 + let v2 = *ours; // $ deref=my_local2 + let v3 = *prev; // $ deref=my_local1 deref=my_local2 MISSING: Alert[rust/dangling-ptr] println!(" v1 = {v1}"); println!(" v2 = {v2}"); println!(" v3 = {v3} (!)"); // corrupt @@ -602,7 +602,7 @@ fn test_enum() { unsafe { // not sure if this is OK or BAD. Seen in real world code. - let v1 = *result; + let v1 = *result; // $ deref=x println!(" v1 = {v1}"); } } @@ -610,7 +610,7 @@ fn test_enum() { use_the_stack(); unsafe { - let v2 = *result; // BAD + let v2 = *result; // $ deref=x MISSING: Alert[rust/dangling-ptr] println!(" v2 = {v2}"); } } From 8747fb4795a35e406a90dd1802cfe83a6470fe6c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:23:27 +0000 Subject: [PATCH 03/10] Rust: Initial query implementation. --- .../rust/elements/internal/AstNodeImpl.qll | 11 ++ .../rust/elements/internal/VariableImpl.qll | 11 ++ .../security/DanglingPointerExtensions.qll | 27 +++ .../security/CWE-825/DanglingPointerAccess.ql | 34 ++++ .../CWE-825/DanglingPointerAccess.expected | 162 ++++++++++++++++++ .../test/query-tests/security/CWE-825/main.rs | 46 ++--- 6 files changed, 268 insertions(+), 23 deletions(-) create mode 100644 rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql create mode 100644 rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected diff --git a/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll index c33b1f3dd6eb..0e9d52b31f87 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll @@ -57,6 +57,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() { this = any(MacroCall mc).getExpanded() diff --git a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll index b21cf924204e..0eed68d8be56 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll @@ -123,6 +123,17 @@ module Impl { /** Gets an access to this variable. */ VariableAccess getAnAccess() { result.getVariable() = this } + /** Gets the block that encloses this variable, if any. */ + cached + BlockExpr getEnclosingBlock() { + exists(AstNode p | p = definingNode.getParentNode() | + result = p + or + not p instanceof BlockExpr and + result = p.getEnclosingBlock() + ) + } + /** Gets the `self` parameter that declares this variable, if one exists. */ SelfParam getSelfParam() { variableDecl(definingNode, result, name) } diff --git a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll index 7457d73b26aa..0e99341ce130 100644 --- a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll @@ -29,3 +29,30 @@ module PointerDereferenceConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node node) { dereferencesPointer(node) } } + +/** + * Holds if `value` accesses a variable with scope `scope`. + */ +predicate valueScope(Expr value, BlockExpr scope) { + // variable access + scope = value.(VariableAccess).getVariable().getEnclosingBlock() +} + +/** + * 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 because we don't account for the actual call + * context that got us to `b` (for example if `f` and `g` both call `b`, then + * then depending on the caller a variable in `f` may or may-not be on the + * stack during `b`). + */ +predicate maybeOnStack(BlockExpr a, BlockExpr b) { + // `b` is a child of `a` + a = b.getEnclosingBlock*() + or + // propagage through function calls + exists(CallExprBase ce | + maybeOnStack(a, ce.getEnclosingBlock()) and + ce.getStaticTarget() = b.getEnclosingCallable() + ) +} diff --git a/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql b/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql new file mode 100644 index 000000000000..0728cfea59b3 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql @@ -0,0 +1,34 @@ +/** + * @name Access to dangling pointer. + * @description Accessing a dangling pointer is undefined behavior. + * @kind path-problem + * @problem.severity error + * @security-severity TODO + * @precision TODO + * @id rust/dangling-ptr + * @tags security + * reliability + * correctness + * external/cwe/cwe-825 + */ + +import rust +import codeql.rust.dataflow.DataFlow +import codeql.rust.dataflow.TaintTracking +import codeql.rust.security.DanglingPointerExtensions + +module PointerDereferenceFlow = TaintTracking::Global; + +import PointerDereferenceFlow::PathGraph + +from + PointerDereferenceFlow::PathNode sourceNode, PointerDereferenceFlow::PathNode sinkNode, + DataFlow::Node targetValue, BlockExpr valueScope, BlockExpr accessScope +where + PointerDereferenceFlow::flowPath(sourceNode, sinkNode) and + createsPointer(sourceNode.getNode(), targetValue) and + valueScope(targetValue.asExpr().getExpr(), valueScope) and + accessScope = sinkNode.getNode().asExpr().getExpr().getEnclosingBlock() and + not maybeOnStack(valueScope, accessScope) +select sinkNode.getNode(), sourceNode, sinkNode, + "Access of a pointer to $@ after the end of it's lifetime.", targetValue, targetValue.toString() diff --git a/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected b/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected new file mode 100644 index 000000000000..5cf5475cc30d --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected @@ -0,0 +1,162 @@ +#select +| main.rs:77:13:77:14 | p1 | main.rs:29:9:29:18 | &my_local1 | main.rs:77:13:77:14 | p1 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:29:10:29:18 | my_local1 | my_local1 | +| main.rs:78:13:78:14 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:78:13:78:14 | p2 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:35:14:35:22 | my_local2 | my_local2 | +| main.rs:79:13:79:14 | p3 | main.rs:41:9:41:28 | &raw const my_local3 | main.rs:79:13:79:14 | p3 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:41:20:41:28 | my_local3 | my_local3 | +| main.rs:80:13:80:14 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:80:13:80:14 | p4 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:47:18:47:26 | my_local4 | my_local4 | +| main.rs:83:13:83:14 | p7 | main.rs:71:8:71:27 | &raw const my_local7 | main.rs:83:13:83:14 | p7 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:71:19:71:27 | my_local7 | my_local7 | +| main.rs:84:4:84:5 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:84:4:84:5 | p2 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:35:14:35:22 | my_local2 | my_local2 | +| main.rs:85:4:85:5 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:85:4:85:5 | p4 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:47:18:47:26 | my_local4 | my_local4 | +| main.rs:200:13:200:15 | ptr | main.rs:215:12:215:22 | &my_local40 | main.rs:200:13:200:15 | ptr | Access of a pointer to $@ after the end of it's lifetime. | main.rs:215:13:215:22 | my_local40 | my_local40 | +| main.rs:613:13:613:18 | result | main.rs:598:26:598:27 | &x | main.rs:613:13:613:18 | result | Access of a pointer to $@ after the end of it's lifetime. | main.rs:598:27:598:27 | x | x | +edges +| main.rs:29:2:29:18 | return ... | main.rs:62:11:62:30 | get_local_dangling(...) | provenance | | +| main.rs:29:9:29:18 | &my_local1 | main.rs:29:2:29:18 | return ... | provenance | | +| main.rs:35:2:35:22 | return ... | main.rs:63:11:63:34 | get_local_dangling_mut(...) | provenance | | +| main.rs:35:9:35:22 | &mut my_local2 | main.rs:35:2:35:22 | return ... | provenance | | +| main.rs:41:2:41:28 | return ... | main.rs:64:11:64:40 | get_local_dangling_raw_const(...) | provenance | | +| main.rs:41:9:41:28 | &raw const my_local3 | main.rs:41:2:41:28 | return ... | provenance | | +| main.rs:47:2:47:26 | return ... | main.rs:65:11:65:38 | get_local_dangling_raw_mut(...) | provenance | | +| main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:47:2:47:26 | return ... | provenance | | +| main.rs:51:2:51:15 | return ... | main.rs:66:11:66:31 | get_param_dangling(...) | provenance | | +| main.rs:51:9:51:15 | ¶m5 | main.rs:51:2:51:15 | return ... | provenance | | +| main.rs:58:2:58:18 | return ... | main.rs:67:11:67:36 | get_local_field_dangling(...) | provenance | | +| main.rs:58:9:58:18 | &... | main.rs:58:2:58:18 | return ... | provenance | | +| main.rs:62:11:62:30 | get_local_dangling(...) | main.rs:77:13:77:14 | p1 | provenance | | +| main.rs:63:11:63:34 | get_local_dangling_mut(...) | main.rs:78:13:78:14 | p2 | provenance | | +| main.rs:63:11:63:34 | get_local_dangling_mut(...) | main.rs:84:4:84:5 | p2 | provenance | | +| main.rs:64:11:64:40 | get_local_dangling_raw_const(...) | main.rs:79:13:79:14 | p3 | provenance | | +| main.rs:65:11:65:38 | get_local_dangling_raw_mut(...) | main.rs:80:13:80:14 | p4 | provenance | | +| main.rs:65:11:65:38 | get_local_dangling_raw_mut(...) | main.rs:85:4:85:5 | p4 | provenance | | +| main.rs:66:11:66:31 | get_param_dangling(...) | main.rs:81:13:81:14 | p5 | provenance | | +| main.rs:67:11:67:36 | get_local_field_dangling(...) | main.rs:82:13:82:14 | p6 | provenance | | +| main.rs:71:8:71:27 | &raw const my_local7 | main.rs:83:13:83:14 | p7 | provenance | | +| main.rs:99:17:99:30 | ...: ... | main.rs:107:13:107:14 | p1 | provenance | | +| main.rs:99:33:99:44 | ...: ... | main.rs:108:13:108:14 | p2 | provenance | | +| main.rs:99:33:99:44 | ...: ... | main.rs:110:4:110:5 | p2 | provenance | | +| main.rs:102:7:102:17 | &my_local10 | main.rs:109:13:109:14 | p3 | provenance | | +| main.rs:121:15:121:25 | &my_local11 | main.rs:99:17:99:30 | ...: ... | provenance | | +| main.rs:121:28:121:46 | &mut my_local_mut12 | main.rs:99:33:99:44 | ...: ... | provenance | | +| main.rs:129:2:129:24 | return ... | main.rs:141:11:141:21 | get_const(...) | provenance | | +| main.rs:129:9:129:24 | &MY_GLOBAL_CONST | main.rs:129:2:129:24 | return ... | provenance | | +| main.rs:136:3:136:30 | return ... | main.rs:142:11:142:26 | get_static_mut(...) | provenance | | +| main.rs:136:10:136:30 | &mut MY_GLOBAL_STATIC | main.rs:136:3:136:30 | return ... | provenance | | +| main.rs:141:11:141:21 | get_const(...) | main.rs:147:13:147:14 | p1 | provenance | | +| main.rs:142:11:142:26 | get_static_mut(...) | main.rs:148:13:148:14 | p2 | provenance | | +| main.rs:142:11:142:26 | get_static_mut(...) | main.rs:149:4:149:5 | p2 | provenance | | +| main.rs:161:23:161:32 | &... | main.rs:178:13:178:14 | p1 | provenance | | +| main.rs:164:23:164:32 | &... | main.rs:179:13:179:14 | p2 | provenance | | +| main.rs:169:23:169:32 | &... | main.rs:180:13:180:14 | p3 | provenance | | +| main.rs:189:17:189:31 | ...: ... | main.rs:192:13:192:15 | ptr | provenance | | +| main.rs:197:17:197:31 | ...: ... | main.rs:200:13:200:15 | ptr | provenance | | +| main.rs:205:17:205:31 | ...: ... | main.rs:208:13:208:15 | ptr | provenance | | +| main.rs:215:12:215:22 | &my_local40 | main.rs:217:15:217:17 | ptr | provenance | | +| main.rs:215:12:215:22 | &my_local40 | main.rs:218:15:218:17 | ptr | provenance | | +| main.rs:215:12:215:22 | &my_local40 | main.rs:220:2:220:11 | return ptr | provenance | | +| main.rs:217:15:217:17 | ptr | main.rs:189:17:189:31 | ...: ... | provenance | | +| main.rs:218:15:218:17 | ptr | main.rs:205:17:205:31 | ...: ... | provenance | | +| main.rs:220:2:220:11 | return ptr | main.rs:224:12:224:36 | access_and_get_dangling(...) | provenance | | +| main.rs:224:12:224:36 | access_and_get_dangling(...) | main.rs:228:15:228:17 | ptr | provenance | | +| main.rs:224:12:224:36 | access_and_get_dangling(...) | main.rs:229:15:229:17 | ptr | provenance | | +| main.rs:228:15:228:17 | ptr | main.rs:197:17:197:31 | ...: ... | provenance | | +| main.rs:229:15:229:17 | ptr | main.rs:205:17:205:31 | ...: ... | provenance | | +| main.rs:234:19:234:36 | ...: ... | main.rs:244:16:244:21 | ptr_up | provenance | | +| main.rs:236:17:236:29 | &my_local_rec | main.rs:239:33:239:40 | ptr_ours | provenance | | +| main.rs:236:17:236:29 | &my_local_rec | main.rs:245:18:245:25 | ptr_ours | provenance | | +| main.rs:236:17:236:29 | &my_local_rec | main.rs:253:2:253:16 | return ptr_ours | provenance | | +| main.rs:239:18:239:52 | access_ptr_rec(...) | main.rs:246:18:246:25 | ptr_down | provenance | | +| main.rs:239:33:239:40 | ptr_ours | main.rs:234:19:234:36 | ...: ... | provenance | | +| main.rs:253:2:253:16 | return ptr_ours | main.rs:239:18:239:52 | access_ptr_rec(...) | provenance | | +| main.rs:258:18:258:31 | &my_local_rec2 | main.rs:260:21:260:29 | ptr_start | provenance | | +| main.rs:260:21:260:29 | ptr_start | main.rs:234:19:234:36 | ...: ... | provenance | | +| main.rs:563:26:563:35 | &my_local1 | main.rs:573:14:573:18 | first | provenance | | +| main.rs:564:29:564:38 | &my_local1 | main.rs:575:14:575:17 | prev | provenance | | +| main.rs:568:26:568:35 | &my_local2 | main.rs:574:14:574:17 | ours | provenance | | +| main.rs:568:26:568:35 | &my_local2 | main.rs:575:14:575:17 | prev | provenance | | +| main.rs:598:26:598:27 | &x | main.rs:605:14:605:19 | result | provenance | | +| main.rs:598:26:598:27 | &x | main.rs:613:13:613:18 | result | provenance | | +nodes +| main.rs:29:2:29:18 | return ... | semmle.label | return ... | +| main.rs:29:9:29:18 | &my_local1 | semmle.label | &my_local1 | +| main.rs:35:2:35:22 | return ... | semmle.label | return ... | +| main.rs:35:9:35:22 | &mut my_local2 | semmle.label | &mut my_local2 | +| main.rs:41:2:41:28 | return ... | semmle.label | return ... | +| main.rs:41:9:41:28 | &raw const my_local3 | semmle.label | &raw const my_local3 | +| main.rs:47:2:47:26 | return ... | semmle.label | return ... | +| main.rs:47:9:47:26 | &raw mut my_local4 | semmle.label | &raw mut my_local4 | +| main.rs:51:2:51:15 | return ... | semmle.label | return ... | +| main.rs:51:9:51:15 | ¶m5 | semmle.label | ¶m5 | +| main.rs:58:2:58:18 | return ... | semmle.label | return ... | +| main.rs:58:9:58:18 | &... | semmle.label | &... | +| main.rs:62:11:62:30 | get_local_dangling(...) | semmle.label | get_local_dangling(...) | +| main.rs:63:11:63:34 | get_local_dangling_mut(...) | semmle.label | get_local_dangling_mut(...) | +| main.rs:64:11:64:40 | get_local_dangling_raw_const(...) | semmle.label | get_local_dangling_raw_const(...) | +| main.rs:65:11:65:38 | get_local_dangling_raw_mut(...) | semmle.label | get_local_dangling_raw_mut(...) | +| main.rs:66:11:66:31 | get_param_dangling(...) | semmle.label | get_param_dangling(...) | +| main.rs:67:11:67:36 | get_local_field_dangling(...) | semmle.label | get_local_field_dangling(...) | +| main.rs:71:8:71:27 | &raw const my_local7 | semmle.label | &raw const my_local7 | +| main.rs:77:13:77:14 | p1 | semmle.label | p1 | +| main.rs:78:13:78:14 | p2 | semmle.label | p2 | +| main.rs:79:13:79:14 | p3 | semmle.label | p3 | +| main.rs:80:13:80:14 | p4 | semmle.label | p4 | +| main.rs:81:13:81:14 | p5 | semmle.label | p5 | +| main.rs:82:13:82:14 | p6 | semmle.label | p6 | +| main.rs:83:13:83:14 | p7 | semmle.label | p7 | +| main.rs:84:4:84:5 | p2 | semmle.label | p2 | +| main.rs:85:4:85:5 | p4 | semmle.label | p4 | +| main.rs:99:17:99:30 | ...: ... | semmle.label | ...: ... | +| main.rs:99:33:99:44 | ...: ... | semmle.label | ...: ... | +| main.rs:102:7:102:17 | &my_local10 | semmle.label | &my_local10 | +| main.rs:107:13:107:14 | p1 | semmle.label | p1 | +| main.rs:108:13:108:14 | p2 | semmle.label | p2 | +| main.rs:109:13:109:14 | p3 | semmle.label | p3 | +| main.rs:110:4:110:5 | p2 | semmle.label | p2 | +| main.rs:121:15:121:25 | &my_local11 | semmle.label | &my_local11 | +| main.rs:121:28:121:46 | &mut my_local_mut12 | semmle.label | &mut my_local_mut12 | +| main.rs:129:2:129:24 | return ... | semmle.label | return ... | +| main.rs:129:9:129:24 | &MY_GLOBAL_CONST | semmle.label | &MY_GLOBAL_CONST | +| main.rs:136:3:136:30 | return ... | semmle.label | return ... | +| main.rs:136:10:136:30 | &mut MY_GLOBAL_STATIC | semmle.label | &mut MY_GLOBAL_STATIC | +| main.rs:141:11:141:21 | get_const(...) | semmle.label | get_const(...) | +| main.rs:142:11:142:26 | get_static_mut(...) | semmle.label | get_static_mut(...) | +| main.rs:147:13:147:14 | p1 | semmle.label | p1 | +| main.rs:148:13:148:14 | p2 | semmle.label | p2 | +| main.rs:149:4:149:5 | p2 | semmle.label | p2 | +| main.rs:161:23:161:32 | &... | semmle.label | &... | +| main.rs:164:23:164:32 | &... | semmle.label | &... | +| main.rs:169:23:169:32 | &... | semmle.label | &... | +| main.rs:178:13:178:14 | p1 | semmle.label | p1 | +| main.rs:179:13:179:14 | p2 | semmle.label | p2 | +| main.rs:180:13:180:14 | p3 | semmle.label | p3 | +| main.rs:189:17:189:31 | ...: ... | semmle.label | ...: ... | +| main.rs:192:13:192:15 | ptr | semmle.label | ptr | +| main.rs:197:17:197:31 | ...: ... | semmle.label | ...: ... | +| main.rs:200:13:200:15 | ptr | semmle.label | ptr | +| main.rs:205:17:205:31 | ...: ... | semmle.label | ...: ... | +| main.rs:208:13:208:15 | ptr | semmle.label | ptr | +| main.rs:215:12:215:22 | &my_local40 | semmle.label | &my_local40 | +| main.rs:217:15:217:17 | ptr | semmle.label | ptr | +| main.rs:218:15:218:17 | ptr | semmle.label | ptr | +| main.rs:220:2:220:11 | return ptr | semmle.label | return ptr | +| main.rs:224:12:224:36 | access_and_get_dangling(...) | semmle.label | access_and_get_dangling(...) | +| main.rs:228:15:228:17 | ptr | semmle.label | ptr | +| main.rs:229:15:229:17 | ptr | semmle.label | ptr | +| main.rs:234:19:234:36 | ...: ... | semmle.label | ...: ... | +| main.rs:236:17:236:29 | &my_local_rec | semmle.label | &my_local_rec | +| main.rs:239:18:239:52 | access_ptr_rec(...) | semmle.label | access_ptr_rec(...) | +| main.rs:239:33:239:40 | ptr_ours | semmle.label | ptr_ours | +| main.rs:244:16:244:21 | ptr_up | semmle.label | ptr_up | +| main.rs:245:18:245:25 | ptr_ours | semmle.label | ptr_ours | +| main.rs:246:18:246:25 | ptr_down | semmle.label | ptr_down | +| main.rs:253:2:253:16 | return ptr_ours | semmle.label | return ptr_ours | +| main.rs:258:18:258:31 | &my_local_rec2 | semmle.label | &my_local_rec2 | +| main.rs:260:21:260:29 | ptr_start | semmle.label | ptr_start | +| main.rs:563:26:563:35 | &my_local1 | semmle.label | &my_local1 | +| main.rs:564:29:564:38 | &my_local1 | semmle.label | &my_local1 | +| main.rs:568:26:568:35 | &my_local2 | semmle.label | &my_local2 | +| main.rs:573:14:573:18 | first | semmle.label | first | +| main.rs:574:14:574:17 | ours | semmle.label | ours | +| main.rs:575:14:575:17 | prev | semmle.label | prev | +| main.rs:598:26:598:27 | &x | semmle.label | &x | +| main.rs:605:14:605:19 | result | semmle.label | result | +| main.rs:613:13:613:18 | result | semmle.label | result | +subpaths 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 a068aec65c09..3442f5644674 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -26,30 +26,30 @@ impl Drop for MyValue { fn get_local_dangling() -> *const i64 { let my_local1: i64 = 1; - return &my_local1; // immediately becomes dangling -} + return &my_local1; // $ Source +} // (return value immediately becomes dangling) fn get_local_dangling_mut() -> *mut i64 { let mut my_local2: i64 = 2; - return &mut my_local2; // immediately becomes dangling -} + return &mut my_local2; // $ Source +} // (return value immediately becomes dangling) fn get_local_dangling_raw_const() -> *const i64 { let my_local3: i64 = 3; - return &raw const my_local3; // immediately becomes dangling -} + return &raw const my_local3; // $ Source +} // (return value immediately becomes dangling) fn get_local_dangling_raw_mut() -> *mut i64 { let mut my_local4: i64 = 4; - return &raw mut my_local4; // immediately becomes dangling -} + return &raw mut my_local4; // $ Source +} // (return value immediately becomes dangling) fn get_param_dangling(param5: i64) -> *const i64 { - return ¶m5; // immediately becomes dangling -} + return ¶m5; +} // (return value immediately becomes dangling) fn get_local_field_dangling() -> *const i64 { let val: MyValue; @@ -68,21 +68,21 @@ fn test_local_dangling() { let p7: *const i64; { let my_local7 = 7; - p7 = &raw const my_local7; - } // my_local goes out of scope, thus p7 is dangling + p7 = &raw const my_local7; // $ Source + } // (my_local goes out of scope, thus p7 is dangling) use_the_stack(); unsafe { - let v1 = *p1; // $ deref=my_local1 MISSING: Alert[rust/dangling-ptr] - let v2 = *p2; // $ deref=my_local2 MISSING: Alert[rust/dangling-ptr] - let v3 = *p3; // $ deref=my_local3 MISSING: Alert[rust/dangling-ptr] - let v4 = *p4; // $ deref=my_local4 MISSING: Alert[rust/dangling-ptr] + let v1 = *p1; // $ deref=my_local1 Alert[rust/dangling-ptr] + let v2 = *p2; // $ deref=my_local2 Alert[rust/dangling-ptr] + let v3 = *p3; // $ deref=my_local3 Alert[rust/dangling-ptr] + let v4 = *p4; // $ deref=my_local4 Alert[rust/dangling-ptr] let v5 = *p5; // $ deref=param5 MISSING: Alert[rust/dangling-ptr] let v6 = *p6; // $ deref=val.value MISSING: Alert[rust/dangling-ptr] - let v7 = *p7; // $ deref=my_local7 MISSING: Alert[rust/dangling-ptr] - *p2 = 8; // $ deref=my_local2 MISSING: Alert[rust/dangling-ptr] - *p4 = 9; // $ deref=my_local4 MISSING: Alert[rust/dangling-ptr] + let v7 = *p7; // $ deref=my_local7 Alert[rust/dangling-ptr] + *p2 = 8; // $ deref=my_local2 Alert[rust/dangling-ptr] + *p4 = 9; // $ deref=my_local4 Alert[rust/dangling-ptr] println!(" v1 = {v1} (!)"); // corrupt println!(" v2 = {v2} (!)"); // corrupt @@ -197,7 +197,7 @@ fn access_ptr_1(ptr: *const i64) { fn access_ptr_2(ptr: *const i64) { // only called with `ptr` dangling unsafe { - let v2 = *ptr; // $ deref=my_local40 MISSING: Alert[rust/dangling-ptr] + let v2 = *ptr; // $ deref=my_local40 Alert[rust/dangling-ptr] println!(" v2 = {v2} (!)"); // corrupt } } @@ -212,7 +212,7 @@ fn access_ptr_3(ptr: *const i64) { fn access_and_get_dangling() -> *const i64 { let my_local40 = 40; - let ptr = &my_local40; + let ptr = &my_local40; // $ Source access_ptr_1(ptr); access_ptr_3(ptr); @@ -595,7 +595,7 @@ fn test_enum() { let e1 = MyEnum::Value(1); result = match e1 { - MyEnum::Value(x) => { &x } + MyEnum::Value(x) => { &x } // $ Source }; use_the_stack(); @@ -610,7 +610,7 @@ fn test_enum() { use_the_stack(); unsafe { - let v2 = *result; // $ deref=x MISSING: Alert[rust/dangling-ptr] + let v2 = *result; // $ deref=x Alert[rust/dangling-ptr] println!(" v2 = {v2}"); } } From 90785f9753fb674b45172e6b5a51f3343b93a8a0 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:51:07 +0000 Subject: [PATCH 04/10] Rust: Add support for field accesses. --- .../ql/lib/codeql/rust/security/DanglingPointerExtensions.qll | 3 +++ .../security/CWE-825/DanglingPointerAccess.expected | 1 + rust/ql/test/query-tests/security/CWE-825/main.rs | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll index 0e99341ce130..d3a37a15abe7 100644 --- a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll @@ -36,6 +36,9 @@ module PointerDereferenceConfig implements DataFlow::ConfigSig { predicate valueScope(Expr value, BlockExpr scope) { // variable access scope = value.(VariableAccess).getVariable().getEnclosingBlock() + or + // field access + valueScope(value.(FieldExpr).getExpr(), scope) } /** diff --git a/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected b/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected index 5cf5475cc30d..51552d9d96ec 100644 --- a/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected +++ b/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected @@ -3,6 +3,7 @@ | main.rs:78:13:78:14 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:78:13:78:14 | p2 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:35:14:35:22 | my_local2 | my_local2 | | main.rs:79:13:79:14 | p3 | main.rs:41:9:41:28 | &raw const my_local3 | main.rs:79:13:79:14 | p3 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:41:20:41:28 | my_local3 | my_local3 | | main.rs:80:13:80:14 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:80:13:80:14 | p4 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:47:18:47:26 | my_local4 | my_local4 | +| main.rs:82:13:82:14 | p6 | main.rs:58:9:58:18 | &... | main.rs:82:13:82:14 | p6 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:58:10:58:18 | val.value | val.value | | main.rs:83:13:83:14 | p7 | main.rs:71:8:71:27 | &raw const my_local7 | main.rs:83:13:83:14 | p7 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:71:19:71:27 | my_local7 | my_local7 | | main.rs:84:4:84:5 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:84:4:84:5 | p2 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:35:14:35:22 | my_local2 | my_local2 | | main.rs:85:4:85:5 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:85:4:85:5 | p4 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:47:18:47:26 | my_local4 | my_local4 | 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 3442f5644674..4014958218c8 100644 --- a/rust/ql/test/query-tests/security/CWE-825/main.rs +++ b/rust/ql/test/query-tests/security/CWE-825/main.rs @@ -55,7 +55,7 @@ fn get_local_field_dangling() -> *const i64 { let val: MyValue; val = MyValue { value: 6 }; - return &val.value; + return &val.value; // $ Source } fn test_local_dangling() { @@ -79,7 +79,7 @@ fn test_local_dangling() { let v3 = *p3; // $ deref=my_local3 Alert[rust/dangling-ptr] let v4 = *p4; // $ deref=my_local4 Alert[rust/dangling-ptr] let v5 = *p5; // $ deref=param5 MISSING: Alert[rust/dangling-ptr] - let v6 = *p6; // $ deref=val.value MISSING: Alert[rust/dangling-ptr] + let v6 = *p6; // $ deref=val.value Alert[rust/dangling-ptr] let v7 = *p7; // $ deref=my_local7 Alert[rust/dangling-ptr] *p2 = 8; // $ deref=my_local2 Alert[rust/dangling-ptr] *p4 = 9; // $ deref=my_local4 Alert[rust/dangling-ptr] From 4b2e1c0ee3a748d9e6a13f27c12b9f231f30afc3 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:55:48 +0000 Subject: [PATCH 05/10] Rust: Complete query metadata. --- .../src/queries/security/CWE-825/DanglingPointerAccess.ql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql b/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql index 0728cfea59b3..2e94a641c0de 100644 --- a/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql +++ b/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql @@ -1,10 +1,10 @@ /** * @name Access to dangling pointer. - * @description Accessing a dangling pointer is undefined behavior. + * @description Accessing a pointer after the lifetime of it's target has ended is undefined behavior. * @kind path-problem * @problem.severity error - * @security-severity TODO - * @precision TODO + * @security-severity 9.8 + * @precision high * @id rust/dangling-ptr * @tags security * reliability From d7ce2073d62ba4110ac45d393134f6ce6642f324 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:03:47 +0000 Subject: [PATCH 06/10] Rust: Simplify Variable.getEnclosingBlock. --- .../lib/codeql/rust/elements/internal/VariableImpl.qll | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll index 0eed68d8be56..06d3da916918 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll @@ -125,14 +125,7 @@ module Impl { /** Gets the block that encloses this variable, if any. */ cached - BlockExpr getEnclosingBlock() { - exists(AstNode p | p = definingNode.getParentNode() | - result = p - or - not p instanceof BlockExpr and - result = p.getEnclosingBlock() - ) - } + BlockExpr getEnclosingBlock() { result = definingNode.getEnclosingBlock() } /** Gets the `self` parameter that declares this variable, if one exists. */ SelfParam getSelfParam() { variableDecl(definingNode, result, name) } From cadb84a19d98c5deb4233e100eec51890c64c30d Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:06:43 +0000 Subject: [PATCH 07/10] Rust: Better and more accurate QLDoc. --- .../lib/codeql/rust/security/DanglingPointerExtensions.qll | 7 ++++--- .../src/queries/security/CWE-825/DanglingPointerAccess.ql | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll index d3a37a15abe7..ec0c98d4462b 100644 --- a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll @@ -2,7 +2,7 @@ import rust private import codeql.rust.dataflow.DataFlow /** - * Holds if `createsPointer` creates a pointer pointing at `targetValue`. + * Holds if `createsPointer` creates a pointer or reference pointing at `targetValue`. */ predicate createsPointer(DataFlow::Node createsPointer, DataFlow::Node targetValue) { exists(RefExpr re | @@ -12,7 +12,7 @@ predicate createsPointer(DataFlow::Node createsPointer, DataFlow::Node targetVal } /** - * Holds if `derefPointer` dereferences a pointer (in unsafe code). + * Holds if `derefPointer` dereferences a pointer. */ predicate dereferencesPointer(DataFlow::Node derefPointer) { exists(PrefixExpr pe | @@ -22,7 +22,8 @@ predicate dereferencesPointer(DataFlow::Node derefPointer) { } /** - * A taint configuration for a pointer that is created and later dereferenced. + * A taint configuration for a pointer or reference that is created and later + * dereferenced. */ module PointerDereferenceConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { createsPointer(node, _) } diff --git a/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql b/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql index 2e94a641c0de..81b7ece6a8ed 100644 --- a/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql +++ b/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql @@ -25,8 +25,12 @@ from PointerDereferenceFlow::PathNode sourceNode, PointerDereferenceFlow::PathNode sinkNode, DataFlow::Node targetValue, BlockExpr valueScope, BlockExpr accessScope where + // flow from a pointer or reference to the dereference PointerDereferenceFlow::flowPath(sourceNode, sinkNode) and createsPointer(sourceNode.getNode(), targetValue) and + // check that the dereference is outside the lifetime of the target; in + // practice this is only possible for a pointer, and the dereference has to + // be in unsafe code, though we don't explicitly check for either. valueScope(targetValue.asExpr().getExpr(), valueScope) and accessScope = sinkNode.getNode().asExpr().getExpr().getEnclosingBlock() and not maybeOnStack(valueScope, accessScope) From 5bd86558997d05c5633b01803494cb8947b2540d Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:18:15 +0000 Subject: [PATCH 08/10] Rust: Better phrasing I think. --- .../security/CWE-825/DanglingPointerAccess.ql | 2 +- .../CWE-825/DanglingPointerAccess.expected | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql b/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql index 81b7ece6a8ed..972a2533eb6a 100644 --- a/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql +++ b/rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql @@ -35,4 +35,4 @@ where accessScope = sinkNode.getNode().asExpr().getExpr().getEnclosingBlock() and not maybeOnStack(valueScope, accessScope) select sinkNode.getNode(), sourceNode, sinkNode, - "Access of a pointer to $@ after the end of it's lifetime.", targetValue, targetValue.toString() + "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/DanglingPointerAccess.expected b/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected index 51552d9d96ec..3433f847e1c6 100644 --- a/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected +++ b/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.expected @@ -1,14 +1,14 @@ #select -| main.rs:77:13:77:14 | p1 | main.rs:29:9:29:18 | &my_local1 | main.rs:77:13:77:14 | p1 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:29:10:29:18 | my_local1 | my_local1 | -| main.rs:78:13:78:14 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:78:13:78:14 | p2 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:35:14:35:22 | my_local2 | my_local2 | -| main.rs:79:13:79:14 | p3 | main.rs:41:9:41:28 | &raw const my_local3 | main.rs:79:13:79:14 | p3 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:41:20:41:28 | my_local3 | my_local3 | -| main.rs:80:13:80:14 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:80:13:80:14 | p4 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:47:18:47:26 | my_local4 | my_local4 | -| main.rs:82:13:82:14 | p6 | main.rs:58:9:58:18 | &... | main.rs:82:13:82:14 | p6 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:58:10:58:18 | val.value | val.value | -| main.rs:83:13:83:14 | p7 | main.rs:71:8:71:27 | &raw const my_local7 | main.rs:83:13:83:14 | p7 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:71:19:71:27 | my_local7 | my_local7 | -| main.rs:84:4:84:5 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:84:4:84:5 | p2 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:35:14:35:22 | my_local2 | my_local2 | -| main.rs:85:4:85:5 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:85:4:85:5 | p4 | Access of a pointer to $@ after the end of it's lifetime. | main.rs:47:18:47:26 | my_local4 | my_local4 | -| main.rs:200:13:200:15 | ptr | main.rs:215:12:215:22 | &my_local40 | main.rs:200:13:200:15 | ptr | Access of a pointer to $@ after the end of it's lifetime. | main.rs:215:13:215:22 | my_local40 | my_local40 | -| main.rs:613:13:613:18 | result | main.rs:598:26:598:27 | &x | main.rs:613:13:613:18 | result | Access of a pointer to $@ after the end of it's lifetime. | main.rs:598:27:598:27 | x | x | +| main.rs:77:13:77:14 | p1 | main.rs:29:9:29:18 | &my_local1 | main.rs:77:13:77:14 | p1 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:29:10:29:18 | my_local1 | my_local1 | +| main.rs:78:13:78:14 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:78:13:78:14 | p2 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:35:14:35:22 | my_local2 | my_local2 | +| main.rs:79:13:79:14 | p3 | main.rs:41:9:41:28 | &raw const my_local3 | main.rs:79:13:79:14 | p3 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:41:20:41:28 | my_local3 | my_local3 | +| main.rs:80:13:80:14 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:80:13:80:14 | p4 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:47:18:47:26 | my_local4 | my_local4 | +| main.rs:82:13:82:14 | p6 | main.rs:58:9:58:18 | &... | main.rs:82:13:82:14 | p6 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:58:10:58:18 | val.value | val.value | +| main.rs:83:13:83:14 | p7 | main.rs:71:8:71:27 | &raw const my_local7 | main.rs:83:13:83:14 | p7 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:71:19:71:27 | my_local7 | my_local7 | +| main.rs:84:4:84:5 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:84:4:84:5 | p2 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:35:14:35:22 | my_local2 | my_local2 | +| main.rs:85:4:85:5 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:85:4:85:5 | p4 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:47:18:47:26 | my_local4 | my_local4 | +| main.rs:200:13:200:15 | ptr | main.rs:215:12:215:22 | &my_local40 | main.rs:200:13:200:15 | ptr | Access of a pointer to $@ after it's lifetime has ended. | main.rs:215:13:215:22 | my_local40 | my_local40 | +| main.rs:613:13:613:18 | result | main.rs:598:26:598:27 | &x | main.rs:613:13:613:18 | result | Access of a pointer to $@ after it's lifetime has ended. | main.rs:598:27:598:27 | x | x | edges | main.rs:29:2:29:18 | return ... | main.rs:62:11:62:30 | get_local_dangling(...) | provenance | | | main.rs:29:9:29:18 | &my_local1 | main.rs:29:2:29:18 | return ... | provenance | | From 22c7e79a6563a6413f5733cd356f060bde39ffa4 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:25:11 +0000 Subject: [PATCH 09/10] Rust: Add missing QLDoc. --- .../lib/codeql/rust/security/DanglingPointerExtensions.qll | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll index ec0c98d4462b..11ffd0b29056 100644 --- a/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll @@ -1,3 +1,8 @@ +/** + * Provides classes and predicates for reasoning about dangling pointer + * accesses. + */ + import rust private import codeql.rust.dataflow.DataFlow From 56c339077b843aad72460803a65a3df4fa3330a3 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:44:45 +0000 Subject: [PATCH 10/10] Rust: Add the missing .qlref. --- .../query-tests/security/CWE-825/DanglingPointerAccess.qlref | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.qlref diff --git a/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.qlref b/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.qlref new file mode 100644 index 000000000000..f778c95b0d66 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-825/DanglingPointerAccess.qlref @@ -0,0 +1,4 @@ +query: queries/security/CWE-825/DanglingPointerAccess.ql +postprocess: + - utils/PrettyPrintModels.ql + - utils/InlineExpectationsTestQuery.ql