Introduction
UaF (Use-after-free), UaR (Use-after-return) or dangling pointer/reference are common in C++, often leading to crashes and security vulnerabilities. Clang already provides two mechanisms for detecting temporal memory safety violations: [[clang::lifetimebound]] annotation and [[gsl::Owner/Pointer]] (a partial implementation of P1179).
In the past few months, we’ve been working on improving Clang’s lifetime analysis to increase the detection of UaFs and UaRs.
This thread is to share our ongoing progress and plans in this area, and we’re interested to hear the feedback from the community as we continue improving these features.
(This work is a collaborative effort by @usx95, @ilya, @kinu, @higher-performance and myself. Special thanks to @Xazax-hun for their thorough code reviews and valuable feedback.)
Recent Progress
We’ve made a series of improvements to Clang’s lifetime analysis to enhance its ability to effectively detect and diagnose more lifetime-related bugs. Here’s what we’ve accomplished:
- Detect Dangling References in Assignments: Two new diagnostics,
-Wdangling-assignment
and-Wdangling-assignment-gsl
, have been implemented and enabled by default, helping to catch more dangling reference issues in assignments (#63310, #54492). - Align lifetimebound and GSL Analysis: We’ve fixed subtle issues caused from the interaction between
[[clang::lifetimebound]]
and[[gsl::Owner/Pointer]]
annotations, making their combined behavior more consistent and easier to reason about (#100549, #93386, #108272). - Beyond the specific points mentioned above, we’ve also made multiple enhancements, bug fixes, and cleanups (e.g. #100384, #106372, #100567, #81589).
Ongoing Work
Better support for nested container case
Containers of pointers like std::vector<std::string_view>
, std::optional<std::string_view>
present a common source of dangling reference issues. They can happen during initialization, assignment or other setter operations. For example:
std::vector<std::string_view> v = {std::string();} // dangling
v = {std::string()}; // dangling ref
std::optional<std::string_view> o = std::string(); // dangling
o = std::string(); // dangling ref
We are working on extending Clang’s analysis to detect dangling references in these cases (see the working PRs).
Extension for more general capture case via a new attribute [[clang::lifetime_capture_by(X)]]
The current [[clang::lifetimebound]]
annotation is limited in scope:
- it only expresses a lifetime relationship between a function argument and the return value (or this object).
- it cannot express more general lifetime relationships, such as expressing a connection of the lifetime for a function argument to this object if a function is not a constructor.
To address this, we propose the [[clang::lifetime_capture_by(X)]]
annotation, which allows developers to express more general lifetime relationships beyond the simple-to-return-value connection. This would enable Clang to detect UaFs in cases like the following:
std::string StrCat(std::string_view, std::string_view);
void test() {
std::vector<std::string_view> v;
v.push_back(StrCat("foo", "bar")); // now v holds a dangling string_view after the statement.
}
Please see the RFC for more details.
Improve Documentation for [[clang::lifetimebound]] and GSL Annotations
While documentation exists for these annotations, it does not provide a high-level overview. From our experience, developers can easily be confused by the overlap between [[clang::lifetimebound]] and GSL Owner/Pointer annotations. We plan to consolidate all related content into a single user-facing documentation (e.g. “Clang’s Lifetime Analysis”) that will provide a comprehensive overview and explanation of how to use these features effectively.
Possible future directions
We are still discussing what future improvements we could make. There are a few possibilities that were brought up in the discussions. However, we have not finalized our commitments to any of those, so we would welcome alternative suggestions and we also want to stress that it’s not a given that we will be necessarily working on that.
Beyond Statement-Local Analysis
Currently, Clang’s analysis is limited to a single statement. One potential direction is to extend the analysis to function-local based on control-flow-graphs (CFG), providing more comprehensive coverage of lifetime-related issues.
An alternative is performing a full dataflow analysis based on the dataflow framework, which could be implemented in CalngTidy. However, expanding the scope of the analysis directly in Clang remains valuable, especially given Clang’s existing support for CFG-based diagnostics.
Rust-style lifetime annotations
The Rust-style lifetime annotation (proposal) is a general-purpose approach. It can express more advanced lifetime contracts for arbitrary numbers of function parameters and the return value. However, implementing this right away in Clang would significantly increase the complexity, far beyond what is available in Clang today.
Improvements to thread-safety annotations
Although it isn’t directly related to memory safety, it is another important area where we can improve overall program safety. The current analysis has some known limitations (e.g. #97550) that we could address to improve thread-safety detection.
Additional ideas are welcome.