-
Notifications
You must be signed in to change notification settings - Fork 144
RFC for Rust UB checks #3092
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
RFC for Rust UB checks #3092
Changes from all commits
aef9cb8
5185c04
96f50da
deae5cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,187 @@ | ||||||||||||||||||||||
| - **Feature Name:** Rust UB Checks (`rust-ub-checks`) | ||||||||||||||||||||||
| - **Feature Request Issue:** [#3089](https://github.com/model-checking/kani/issues/3089) | ||||||||||||||||||||||
| - **RFC PR:** [#3091](https://github.com/model-checking/kani/pull/3091) | ||||||||||||||||||||||
| - **Status:** Unstable | ||||||||||||||||||||||
| - **Version:** 0 | ||||||||||||||||||||||
| - **Proof-of-concept:** | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ------------------- | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Summary | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Specify a consistent framework for Undefined Behavior (UB) checks in Kani. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## User Impact | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Provide a consistent user experience for all UB checks in Kani, while establishing the best mechanisms for | ||||||||||||||||||||||
| further instrumentation of undefined behavior in Kani. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## User Experience | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| A UB check should behave consistently independent on how and where it is implemented. | ||||||||||||||||||||||
| The detection of an undefined behavior should impact the status of any assertion that may be reachable from the | ||||||||||||||||||||||
| detected undefined behavior. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| For simplicity, we propose that all undefined behavior checks are modelled as assert-assume pairs. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this comment affect the validity of this approach at all?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, after finishing the RFC, I believe the demonic nondeterminism mention later addresses the soundness concerns from the linked comment. |
||||||||||||||||||||||
| We propose that the status of all passing assertions that may be affected by this check to be displayed as | ||||||||||||||||||||||
| `UNDETERMINED` if one or more UB checks fail. | ||||||||||||||||||||||
| For simplicity, Kani currently over-approximate this set by marking all passing assertions as `UNDETERMINED`. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Comment on lines
+26
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose after looking at this comment, maybe "reachable" isn't the right word here--you said "downstream," and I take the difference to be that "downstream" just means "after" whereas "reachable" is "after, and also reachable." So perhaps this section can say that we propose an assertion be undetermined if it's "downstream" of the failing UB check, and then later, as an example of future pruning, you could give the example of only reachable passing assertions being undetermined.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My overall motivation here is to try to be specific about what exactly we're proposing--we can always come back and update the RFC if we implement something different.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually don't want to be super specific at this point. Maybe let me change the proposal to say that we shall mark all passing assertions as |
||||||||||||||||||||||
| Future pruning of this set can be done with further analysis, but it should not affect soundness. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give examples of other pruning that we could do / it would make sense for us to do? |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| All failing assertions will have their status preserved. | ||||||||||||||||||||||
| This may create spurious counter examples for cases where the failing UB check is implemented using "demonic" | ||||||||||||||||||||||
| non-determinism.[^demonic] | ||||||||||||||||||||||
|
Comment on lines
+32
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain why? |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| [^demonic]: Some UB checks implementation use one tracker with non-deterministic assignment to track the | ||||||||||||||||||||||
| status of different objects. | ||||||||||||||||||||||
| This implementation relies on the fact that an assertion will fail if there is at least one possible | ||||||||||||||||||||||
| assignment to its tracker that violates the assertion. | ||||||||||||||||||||||
| Picking an assignment that fails can be seen as choosing the worst possible action, known as demonic nondeterminism. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| See [Open questions section](0010-rust-ub-checks.md#open-questions) for alternatives to be considered. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Concrete playback of UB checks | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| A counter example that triggers UB may require extra tooling for debugging, e.g.: MIRI or valgrind. | ||||||||||||||||||||||
| We propose that tests added by concrete playback in those cases should include a comment stating such limitation. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
| Other possibilities are discussed in [0010-rust-ub-checks.md#open-questions]. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Delayed undefined behavior | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| In some cases, undefined behavior may occur in safe code that interacts with unsafe code. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| For example, the code below shows how reading a boolean value in safe code may trigger UB due to an | ||||||||||||||||||||||
| invalid write: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```rust | ||||||||||||||||||||||
| #[kani::proof] | ||||||||||||||||||||||
| fn buggy_code() { | ||||||||||||||||||||||
| let mut value: bool = false; | ||||||||||||||||||||||
| let ptr: *mut bool = &mut value as *mut _; | ||||||||||||||||||||||
| let mut_ref = &mut value; | ||||||||||||||||||||||
| unsafe { | ||||||||||||||||||||||
| *ptr = true; // This assignment is safe if `mut_ref` is never used! | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| assert!(!*mut_ref); // This triggers UB ⚠️! | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| It may not be scalable for Kani to verify that every value produced is valid. | ||||||||||||||||||||||
| In cases like that, we may opt for implementing one of the following UB checks: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. Perform static analysis to first identify which code may be affected by unsafe code, and only instrument those that | ||||||||||||||||||||||
| are reachable from the unsafe code. | ||||||||||||||||||||||
| 2. Add eager UB detection in unsafe code that will fail verification if there are side effects that we may not be | ||||||||||||||||||||||
| able to detect efficiently. For example, we could fail if casting between types that have different value | ||||||||||||||||||||||
| requirements, i.e.: fail in the cast `*mut bool as *mut u8`. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| For option 2, Kani may fail verification even if the code does not have UB. | ||||||||||||||||||||||
| In those cases, Kani check must be explicit about this failure being an over-approximation. | ||||||||||||||||||||||
| For that, we propose that the check message is clear about this limitation, and potentially its status | ||||||||||||||||||||||
| (see [0010-rust-ub-checks.md#open-questions]). | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Opt-out of a class of UB checks | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Undefined behavior checks that have been stabilized should be added by default by Kani. | ||||||||||||||||||||||
| However, Kani should provide a fine-grained control for users to choose which checks to disable. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| For every class of UB checks to be added, we shall add a command line argument to disable the check for all harnesses, | ||||||||||||||||||||||
| as well as an attribute that would allow users to opt-out a check for specific harnesses. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Command line arguments should follow the existing name schema: | ||||||||||||||||||||||
| `--no-[CHECK-NAME]-checks` where the CHECK-NAME is kebab case. There is no need to add `--[CHECK-NAME]-checks` since | ||||||||||||||||||||||
| they are enabled by default. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| We propose to add the following attribute: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```rust | ||||||||||||||||||||||
| #[kani::proof] | ||||||||||||||||||||||
| #[kani::ignore(CHECK_NAMES)] | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "ignore" is a bit too general. Maybe use
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||||||||||||||||||||||
| fn harness() {} | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Where `CHECK_NAMES` is a list of check category names in snake case, such as `memory_safety`, `valid_values`. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| For checks that are unstable, they should only be included if their respective unstable flag are passed, i.e., | ||||||||||||||||||||||
| `-Z [CHECK-NAME]`. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Software Design | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| *To be filled in the next iteration* | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Rationale and alternatives | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Kani already has undefined behavior checks, some are implemented on the CBMC side, such as access out bounds, and | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
| arithmetic overflow. | ||||||||||||||||||||||
| While others are implemented on Kani's side, such as intrinsics validation checks, and some are a | ||||||||||||||||||||||
| result of instrumentation to the Rust standard library. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The behavior of these checks varies according to how they are added. | ||||||||||||||||||||||
| The ones on CBMC side do not interrupt the analysis of a path, while the ones on Kani / Rust std side will. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| For example, memory bound checks are implemented in CBMC, and the following harness today will trigger 2 different | ||||||||||||||||||||||
| failures: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```rust | ||||||||||||||||||||||
| #[kani::proof] | ||||||||||||||||||||||
| pub fn is_zero() { | ||||||||||||||||||||||
| let var = [0u32; 4]; | ||||||||||||||||||||||
| let ptr = &var as *const u32; | ||||||||||||||||||||||
| let idx: usize = 4; | ||||||||||||||||||||||
| assert_eq!(unsafe { ptr.add(idx).read() }, 0); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The failed checks are: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```plaintext | ||||||||||||||||||||||
| Failed Checks: assertion failed: unsafe { ptr.add(idx).read() } == 0 | ||||||||||||||||||||||
| Failed Checks: dereference failure: pointer outside object bounds | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| While the following harness only fails the standard library check: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```rust | ||||||||||||||||||||||
| #[kani::proof] | ||||||||||||||||||||||
| pub fn is_zero() { | ||||||||||||||||||||||
| let var = [0; 4]; | ||||||||||||||||||||||
| let idx: usize = 4; | ||||||||||||||||||||||
| assert_eq!(unsafe { *var.get_unchecked(idx) }, 0); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Failing due to an assumption check inside `get_unchecked` function. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```plaintext | ||||||||||||||||||||||
| Failed Checks: assumption failed | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
Comment on lines
+153
to
+157
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Just to make it a bit clearer that this is an assumption in the standard library itself, not some Kani model of |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| We would like to keep taking advantage of existing instrumentation to maximize the UB check coverage. | ||||||||||||||||||||||
| The mechanism used to build the check, however, shouldn't influence in the user experience. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section does a great job of explaining the current division of UB checks and how those manifest in different experiences for users, but I'm missing a call to action--what are you proposing we do about it? Should we add a model of |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Opt-out vs opt-in | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The default behavior matches the one that we believe is the recommended for most users due to soundness. | ||||||||||||||||||||||
| In some cases, users may need to disable some checks for reasons such as spurious CEX or proof scalability. | ||||||||||||||||||||||
| Thus, we suggest that every category of checks should have an opt-out mechanism that can either be global or local; | ||||||||||||||||||||||
| i.e.: via command argument or harness attribute. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The main disadvantage of this model is that stabilizing a new check category is a non-backward compatible change. | ||||||||||||||||||||||
| I.e.: It may impact the status of a previously passing harness if a new check fails. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Open questions | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - Should we add a new status for potentially spurious counter examples? Such as: | ||||||||||||||||||||||
| - Counter examples detected together with a non-deterministic UB check. | ||||||||||||||||||||||
| - Over-approximating UB checks. | ||||||||||||||||||||||
| - How should UB checks affect concrete playback? Is a comment enough? | ||||||||||||||||||||||
| - Another possibility would be to add a `unreachable!()` statement at the end of the test case with a message | ||||||||||||||||||||||
| explaining why the statement should be unreachable. | ||||||||||||||||||||||
| - Should we deprecate the `--no-default-checks` and all `--[CHECK-NAME]-checks` checks. | ||||||||||||||||||||||
| - Should we add an attribute that allows us to opt-out some UB checks for a function or some code block? | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
| - I think we should at least consider adding that option for `kani` library code. | ||||||||||||||||||||||
| - We've been adding more logic that is used by UB checks, and we may want to skip some checks for them. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Out of scope / Future Improvements | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - **List enabled checks:** Kani could include a command that list all the categories of undefined behaviors supported. | ||||||||||||||||||||||
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.