-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Rust: New query rust/access-after-lifetime-ended #19702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…mixes (inspired by early real world results).
…erstand whether the alert is correct.
QHelp previews: rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelpAccess of a pointer after its lifetime has endedDereferencing 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. RecommendationWhen dereferencing a pointer in ExampleIn the following example, 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
}
// ...
} One way to fix this is to change the return type of the function from a pointer to a fn get_box() -> Box<i64> {
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
// ...
} References
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Adds a new CodeQL query to detect pointer dereferences after the lifetime of their target has ended, including examples, documentation, and scope‐tracking support.
- Introduce
AccessAfterLifetime.ql
with configuration, flow definition, and filtering logic - Add QL test harness entries (
.qlref
), examples (Good.rs
/Bad.rs
), and documentation (.qhelp
) - Extend the internal QL library to track enclosing blocks (
getEnclosingBlock
) and support the new query
Reviewed Changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
rust/ql/test/query-tests/security/CWE-825/options.yml | Add futures dependency for async tests |
rust/ql/test/query-tests/security/CWE-825/main.rs | Invoke newly added test cases in main() |
rust/ql/test/query-tests/security/CWE-825/deallocation.rs | Update existing invalid-pointer test annotations |
rust/ql/test/query-tests/security/CWE-825/AccessAfterLifetime.qlref | Register the new query with test harness |
rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeGood.rs | Add good example using Box |
rust/ql/src/queries/security/CWE-825/AccessAfterLifetimeBad.rs | Add bad example that returns a dangling pointer |
rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql | Implement the new query logic |
rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.qhelp | Add documentation and examples |
rust/ql/lib/codeql/rust/security/AccessAfterLifetimeExtensions.qll | Define sources, sinks, barriers, and scope checks |
rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll | Add Variable.getEnclosingBlock() |
rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll | Add AstNode.getEnclosingBlock() |
Comments suppressed due to low confidence (1)
rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql:44
- The predicate
isFromMacroExpansion()
does not exist; you likely meantisInMacroExpansion()
to filter out macro expansions.
not sinkNode.getNode().asExpr().getExpr().isFromMacroExpansion()
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
DCA:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@geoffw0 - LGTM ✨
Added a couple of very minor comments/suggestions.
Co-authored-by: mc <42146119+mchammer01@users.noreply.github.com>
Thanks for the review @mchammer01 , I've accepted both suggestions. :) |
@@ -127,6 +127,10 @@ module Impl { | |||
*/ | |||
Name getName() { variableDecl(definingNode, result, text) } | |||
|
|||
/** Gets the block that encloses this variable, if any. */ | |||
cached |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really great to me! Lots of true positives.
fn get_pointer() -> *const i64 { | ||
let val = 123; | ||
|
||
return &val; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return &val; | |
&val |
fn get_box() -> Box<i64> { | ||
let val = 123; | ||
|
||
return Box::new(val); // copies `val` onto the heap, where it remains for the lifetime of the `Box`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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`. |
|
||
use_the_stack(); | ||
|
||
let v3 = *r1; // $ SPURIOUS: Alert[rust/access-after-lifetime-ended]=v2_value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An idea for getting rid of this false positive could be to change DereferenceSink
such that only dereferences inside unsafe
blocks are considered.
* 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This predicate name threw me a bit off. I initially thought it would be something about heap allocated vs stack allocated variables.
This changes the name and tries to tweak the documentation a bit. Of course if you have a better idea then feel free to use that instead :)
* 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) { | |
* 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 blockStackEnclosing(BlockExpr a, BlockExpr b) { |
// ... | ||
|
||
unsafe { | ||
val = *ptr; // BAD: dereferences `ptr` after the lifetime of `val` has ended |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit confusing that there is two val
s here and the lifetime of the val
5 lines up has not ended. Just renaming one one them would help I think.
New query
rust/access-after-lifetime-ended
, for detecting pointer dereferences after the lifetime of the pointed-to object has ended. Makes use of some existing tests that were created forrust/access-invalid-pointer
(before I realized that the idea for that query needed breaking into two separate queries). Also adds quite a lot of new test cases as well.Note that the query is currently
@precision medium
due to several remaining false positive results in the tests (and in MRVA results). I made some effort to fix these, but I didn't get them all, I feel it's time to get what we have merged and plan further improvements as follow-up work.Before merging: