Skip to content

Commit 505f90b

Browse files
Merge branch 'daniel/byte_operation' of https://github.com/danielhumanmod/verify-rust-std into daniel/byte_operation
2 parents 670adef + d95ad3e commit 505f90b

File tree

17 files changed

+827
-95
lines changed

17 files changed

+827
-95
lines changed

.github/workflows/kani.yml

+37-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
# This workflow is responsible for verifying the standard library with Kani.
2-
31
name: Kani
2+
43
on:
54
workflow_dispatch:
65
pull_request:
@@ -9,30 +8,60 @@ on:
98
paths:
109
- 'library/**'
1110
- '.github/workflows/kani.yml'
12-
- 'scripts/check_kani.sh'
11+
- 'scripts/run-kani.sh'
1312

1413
defaults:
1514
run:
1615
shell: bash
1716

1817
jobs:
19-
build:
18+
check-kani-on-std:
19+
name: Verify std library
2020
runs-on: ${{ matrix.os }}
2121
strategy:
2222
matrix:
23-
# Kani does not support windows.
2423
os: [ubuntu-latest, macos-latest]
2524
include:
2625
- os: ubuntu-latest
2726
base: ubuntu
2827
- os: macos-latest
2928
base: macos
3029
steps:
31-
- name: Checkout Library
30+
# Step 1: Check out the repository
31+
- name: Checkout Repository
3232
uses: actions/checkout@v4
3333
with:
3434
path: head
3535
submodules: true
3636

37-
- name: Run Kani Script
38-
run: bash ./head/scripts/check_kani.sh ${{github.workspace}}/head
37+
# Step 2: Run Kani on the std library (default configuration)
38+
- name: Run Kani Verification
39+
run: head/scripts/run-kani.sh --path ${{github.workspace}}/head
40+
41+
test-kani-script:
42+
name: Test Kani script
43+
runs-on: ${{ matrix.os }}
44+
strategy:
45+
matrix:
46+
os: [ubuntu-latest, macos-latest]
47+
include:
48+
- os: ubuntu-latest
49+
base: ubuntu
50+
- os: macos-latest
51+
base: macos
52+
steps:
53+
# Step 1: Check out the repository
54+
- name: Checkout Repository
55+
uses: actions/checkout@v4
56+
with:
57+
path: head
58+
submodules: true
59+
60+
# Step 2: Test Kani verification script with specific arguments
61+
- name: Test Kani script (Custom Args)
62+
run: head/scripts/run-kani.sh -p ${{github.workspace}}/head --kani-args --harness ptr --output-format=terse
63+
64+
# Step 3: Test Kani verification script in the repository directory
65+
- name: Test Kani script (In Repo Directory)
66+
working-directory: ${{github.workspace}}/head
67+
run: scripts/run-kani.sh --kani-args --harness ptr::verify::check_read_u128 --harness ptr --output-format=terse

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Session.vim
1919
## Build
2020
/book/
2121
/build/
22+
/kani_build/
2223
/target
2324
library/target
2425
*.rlib

doc/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- [4: Memory safety of BTreeMap's `btree::node` module](./challenges/0004-btree-node.md)
2020
- [5: Verify functions iterating over inductive data type: `linked_list`](./challenges/0005-linked-list.md)
2121
- [6: Safety of `NonNull`](./challenges/0006-nonnull.md)
22+
- [7: Safety of Methods for Atomic Types and `ReentrantLock`](./challenges/0007-atomic-types.md)
2223
- [8: Contracts for SmallSort](./challenges/0008-smallsort.md)
2324
- [9: Safe abstractions for `core::time::Duration`](./challenges/0009-duration.md)
2425
- [10: Memory safety of String](./challenges/0010-string.md)
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Challenge 7: Safety of Methods for Atomic Types & Atomic Intrinsics
2+
3+
- **Status:** Open
4+
- **Tracking Issue:** [#83](https://github.com/model-checking/verify-rust-std/issues/83)
5+
- **Start date:** *2024-10-30*
6+
- **End date:** *2024-12-10*
7+
8+
-------------------
9+
10+
## Goal
11+
12+
[`core::sync::atomic`](https://doc.rust-lang.org/std/sync/atomic/index.html) provides methods that operate on atomic types.
13+
For example, `AtomicBool::store(&self, val: bool, order: Ordering)` stores `val` in the atomic boolean referenced by `self` according to the specified [atomic memory ordering](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html).
14+
15+
The goal of this challenge is to verify that these methods are safe.[^1]
16+
17+
### Success Criteria
18+
19+
#### Part 1: Unsafe Methods
20+
21+
First, verify that the unsafe `from_ptr` methods are safe, given that their safety preconditions are met.
22+
23+
Write safety contracts for each of the `from_ptr` methods:
24+
25+
- `AtomicBool::from_ptr`
26+
- `AtomicPtr::from_ptr`
27+
- `AtomicI8::from_ptr`
28+
- `AtomicU8::from_ptr`
29+
- `AtomicI16::from_ptr`
30+
- `AtomicU16::from_ptr`
31+
- `AtomicI32::from_ptr`
32+
- `AtomicU32::from_ptr`
33+
- `AtomicI64::from_ptr`
34+
- `AtomicU64::from_ptr`
35+
- `AtomicI128::from_ptr`
36+
- `AtomicU128::from_ptr`
37+
38+
Specifically, encode the conditions about `ptr`'s alignment and validity (marked `#Safety` in the methods' documentation) as preconditions.
39+
Then, verify that the methods are safe for all possible values for the type that `ptr` points to, given that `ptr` satisfies those preconditions.
40+
41+
For example, `AtomicI8::from_ptr` is defined as:
42+
```rust
43+
/// `ptr` must be [valid] ... (comment continues; omitted for brevity)
44+
pub const unsafe fn from_ptr<'a>(ptr: *mut i8) -> &'a AtomicI8 {
45+
// SAFETY: guaranteed by the caller
46+
unsafe { &*ptr.cast() }
47+
}
48+
```
49+
50+
To verify this method, first encode the safety comments (e.g., about pointer validity) as preconditions, then verify the absence of undefined behavior for all possible `i8` values.
51+
52+
For the `AtomicPtr` case only, we do not require that you verify safety for all possible types.
53+
Concretely, below is the type signature for `AtomicPtr::from_ptr`:
54+
55+
```rust
56+
pub const unsafe fn from_ptr<'a>(ptr: *mut *mut T) -> &'a AtomicPtr<T>
57+
```
58+
59+
The type pointed to is a `*mut T`.
60+
Verify that for any arbitrary value for this `*mut T` (i.e., any arbitrary memory address), the method is safe.
61+
However, you need not verify the method for all possible instantiations of `T`.
62+
It suffices to verify this method for `T` of byte sizes 0, 1, 2, 4, and at least one non-power of two.
63+
64+
#### Part 2: Safe Abstractions
65+
66+
The atomic types expose safe abstractions for atomic operations.
67+
In this part, you will work toward verifying that these abstractions are indeed safe by writing and verifying safety contracts for the unsafe code in their bodies.
68+
69+
For example, `AtomicBool::store` is the (public) safe method that atomically updates the boolean's value.
70+
This method invokes the unsafe function `atomic_store`, which in turn calls `intrinsics::atomic_store_relaxed`, `intrinsics::atomic_store_release`, or `intrinsics::atomic_store_seqcst`, depending on the provided ordering.
71+
72+
Write and verify safety contracts for the unsafe functions:
73+
74+
- `atomic_store`
75+
- `atomic_load`
76+
- `atomic_swap`
77+
- `atomic_add`
78+
- `atomic_sub`
79+
- `atomic_compare_exchange`
80+
- `atomic_compare_exchange_weak`
81+
- `atomic_and`
82+
- `atomic_nand`
83+
- `atomic_or`
84+
- `atomic_xor`
85+
- `atomic_max`
86+
- `atomic_umax`
87+
- `atomic_umin`
88+
89+
Then, for each of the safe abstractions that invoke the unsafe functions listed above, write contracts that ensure that they are not invoked with `order`s that would cause panics.
90+
91+
For example, `atomic_store` panics if invoked with `Acquire` or `AcqRel` ordering.
92+
In this case, you would write contracts on the safe `store` methods that enforce that they are not called with either of those `order`s.
93+
94+
#### Part 3: Atomic Intrinsics
95+
96+
Write and verify safety contracts for the intrinsics invoked by the unsafe functions from Part 2 (in `core::intrinsics`):
97+
98+
| Operations | Functions |
99+
|-----------------------|-------------|
100+
| Store | `atomic_store_relaxed`, `atomic_store_release`, `atomic_store_seqcst` |
101+
| Load | `atomic_load_relaxed`, `atomic_load_acquire`, `atomic_load_seqcst` |
102+
| Swap | `atomic_xchg_relaxed`, `atomic_xchg_acquire`, `atomic_xchg_release`, `atomic_xchg_acqrel`, `atomic_xchg_seqcst` |
103+
| Add | `atomic_xadd_relaxed`, `atomic_xadd_acquire`, `atomic_xadd_release`, `atomic_xadd_acqrel`, `atomic_xadd_seqcst` |
104+
| Sub | `atomic_xsub_relaxed`, `atomic_xsub_acquire`, `atomic_xsub_release`, `atomic_xsub_acqrel`, `atomic_xsub_seqcst` |
105+
| Compare Exchange | `atomic_cxchg_relaxed_relaxed`, `atomic_cxchg_relaxed_acquire`, `atomic_cxchg_relaxed_seqcst`, `atomic_cxchg_acquire_relaxed`, `atomic_cxchg_acquire_acquire`, `atomic_cxchg_acquire_seqcst`, `atomic_cxchg_release_relaxed`, `atomic_cxchg_release_acquire`, `atomic_cxchg_release_seqcst`, `atomic_cxchg_acqrel_relaxed`, `atomic_cxchg_acqrel_acquire`, `atomic_cxchg_acqrel_seqcst`, `atomic_cxchg_seqcst_relaxed`, `atomic_cxchg_seqcst_acquire`, `atomic_cxchg_seqcst_seqcst` |
106+
| Compare Exchange Weak | `atomic_cxchgweak_relaxed_relaxed`, `atomic_cxchgweak_relaxed_acquire`, `atomic_cxchgweak_relaxed_seqcst`, `atomic_cxchgweak_acquire_relaxed`, `atomic_cxchgweak_acquire_acquire`, `atomic_cxchgweak_acquire_seqcst`, `atomic_cxchgweak_release_relaxed`, `atomic_cxchgweak_release_acquire`, `atomic_cxchgweak_release_seqcst`, `atomic_cxchgweak_acqrel_relaxed`, `atomic_cxchgweak_acqrel_acquire`, `atomic_cxchgweak_acqrel_seqcst`, `atomic_cxchgweak_seqcst_relaxed`, `atomic_cxchgweak_seqcst_acquire`, `atomic_cxchgweak_seqcst_seqcst` |
107+
| And | `atomic_and_relaxed`, `atomic_and_acquire`, `atomic_and_release`, `atomic_and_acqrel`, `atomic_and_seqcst` |
108+
| Nand | `atomic_nand_relaxed`, `atomic_nand_acquire`, `atomic_nand_release`, `atomic_nand_acqrel`, `atomic_nand_seqcst` |
109+
| Or | `atomic_or_seqcst`, `atomic_or_acquire`, `atomic_or_release`, `atomic_or_acqrel`, `atomic_or_relaxed` |
110+
| Xor | `atomic_xor_seqcst`, `atomic_xor_acquire`, `atomic_xor_release`, `atomic_xor_acqrel`, `atomic_xor_relaxed` |
111+
| Max | `atomic_max_relaxed`, `atomic_max_acquire`, `atomic_max_release`, `atomic_max_acqrel`, `atomic_max_seqcst` |
112+
| Min | `atomic_min_relaxed`, `atomic_min_acquire`, `atomic_min_release`, `atomic_min_acqrel`, `atomic_min_seqcst` |
113+
| Umax | `atomic_umax_relaxed`, `atomic_umax_acquire`, `atomic_umax_release`, `atomic_umax_acqrel`, `atomic_umax_seqcst` |
114+
| Umin | `atomic_umin_relaxed`, `atomic_umin_acquire`, `atomic_umin_release`, `atomic_umin_acqrel`, `atomic_umin_seqcst` |
115+
116+
## List of UBs
117+
118+
In addition to any safety properties mentioned in the API documentation, all proofs must automatically ensure the absence of the following [undefined behaviors](https://github.com/rust-lang/reference/blob/142b2ed77d33f37a9973772bd95e6144ed9dce43/src/behavior-considered-undefined.md):
119+
120+
* Data races.
121+
* Accessing (loading from or storing to) a place that is dangling or based on a misaligned pointer.
122+
* Reading from uninitialized memory.
123+
* Invoking undefined behavior via compiler intrinsics.
124+
* Producing an invalid value.
125+
126+
Note: All solutions to verification challenges need to satisfy the criteria established in the [challenge book](../general-rules.md) in addition to the ones listed above.
127+
128+
[^1]: Throughout this challenge, when we say "safe", it is identical to saying "does not exhibit undefined behavior".

doc/src/general-rules.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
**Verification Target:** [Our repository](https://github.com/model-checking/verify-rust-std) is a fork of the original Rust repository,
66
and we kept a copy of the Rust standard library inside the `library/` folder that shall be used as the verification target for all our challenges.
77
We will periodically update the `library/` folder to track newer versions of the [official Rust standard library](https://github.com/rust-lang/rust/).
8-
NOTE: This work is not officially affiliated, or endorsed by the Rust project or Rust Foundation.
8+
9+
**NOTE:** This work is not officially affiliated, or endorsed by the Rust project or Rust Foundation.
10+
911
**Challenges:** Each individual verification effort will have a
1012
tracking issue where contributors can add comments and ask clarification questions.
1113
You can find the list of [open challenges here](https://github.com/model-checking/verify-rust-std/labels/Challenge).

doc/src/tools/kani.md

+24-18
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ Create a local copy of the [model-checking fork](https://github.com/model-checki
5959
`assert`, `assume`, `proof` and [function-contracts](https://github.com/model-checking/kani/blob/main/rfc/src/rfcs/0009-function-contracts.md) such as `modifies`, `requires` and `ensures`) directly.
6060

6161

62-
For example, insert this module into an existing file in the core library, like `library/core/src/hint.rs` or `library/core/src/error.rs` in your copy of the library. This is just for the purpose of getting started, so you can insert in any existing file in the core library if you have other preferences.
62+
For example, insert this module into an existing file in the core library, like `library/core/src/hint.rs` or `library/core/src/error.rs` in your copy of the library.
63+
This is just for the purpose of getting started, so you can insert it in a different (existing) file in the core library instead.
6364

6465
``` rust
6566
#[cfg(kani)]
@@ -84,22 +85,24 @@ pub mod verify {
8485
}
8586
```
8687

87-
### Step 2 - Run the Kani verify-std subcommand
88+
### Step 2 - Run the Kani script on the std library
8889

89-
To aid the Rust Standard Library verification effort, Kani provides a sub-command out of the box to help you get started.
90-
Run the following command in your local terminal (Replace "/path/to/library" and "/path/to/target" with your local paths) from the verify repository root:
90+
To aid the Rust Standard Library verification effort, Kani provides a script out of the box to help you get started.
91+
Run the following command in your local terminal from the verify repository root:
9192

9293
```
93-
kani verify-std -Z unstable-options "/path/to/library" --target-dir "/path/to/target" -Z function-contracts -Z mem-predicates
94+
./scripts/run-kani.sh --path .
9495
```
9596

96-
The command `kani verify-std` is a sub-command of the `kani`. This specific sub-command is used to verify the Rust Standard Library with the following arguments.
97+
To pass kani arguments such as `--harness`, you can run the script with `--kani-args` and continue passing in all the necessary arguments:
9798

98-
- `"path/to/library"`: This argument specifies the path to the modified Rust Standard Library that was prepared earlier in the script. For example, `./library` or `/home/ubuntu/verify-rust-std/library`
99-
- `--target-dir "path/to/target"`: This optional argument sets the target directory where Kani will store its output and intermediate files. For example, `/tmp` or `/tmp/verify-std`
99+
```
100+
./scripts/run-kani.sh --path . --kani-args --harness alloc::layout::verify::check_array_i32 --output-format=terse
101+
```
102+
103+
The script `run-kani` installs the right version of Kani for you, builds it and then finally runs the verify-std sub-command of the `kani` with some default flags.
100104

101-
Apart from these, you can use your regular `kani-args` such as `-Z function-contracts`, `-Z stubbing` and `-Z mem-predicates` depending on your verification needs. If you run into a Kani error that says `Use of unstable feature`, add the corresponding feature with `-Z` to the command line.
102-
For more details on Kani's features, refer to [the features section in the Kani Book](https://model-checking.github.io/kani/reference/attributes.html)
105+
**NOTE:** This script may crash due to linking issues. If the script fails with an error message related to linking, link the new CBMC version, delete the `./kani_build` directory and re-run.
103106

104107
### Step 3 - Check verification result
105108

@@ -122,7 +125,7 @@ You can specify a specific harness to be verified using the `--harness` flag.
122125
For example, in your local copy of the verify repo, run the following command.
123126

124127
```
125-
kani verify-std --harness harness_introduction -Z unstable-options "./library" --target-dir "/tmp" -Z function-contracts -Z mem-predicates
128+
./scripts/run-kani.sh --kani-args --harness harness_introduction
126129
```
127130

128131
This gives you the verification result for just `harness_introduction` from the aforementioned blob.
@@ -144,13 +147,16 @@ Verification Time: 0.01885804s
144147
Complete - 1 successfully verified harnesses, 0 failures, 1 total.
145148
```
146149

147-
Now you can write proof harnesses to verify specific functions in the library.
148-
The current convention is to keep proofs in the same module file of the verification target.
149-
To run Kani for an individual proof, use `--harness [harness_function_name]`.
150-
Note that Kani will batch run all proofs in the library folder if you do not supply the `--harness` flag.
151-
If Kani returns the error `no harnesses matched the harness filter`, you can give the full name of the harness.
152-
For example, to run the proof harness named `check_new` in `library/core/src/ptr/unique.rs`, use
153-
`--harness ptr::unique::verify::check_new`. To run all proofs in `unique.rs`, use `--harness ptr::unique::verify`.
150+
Now you can write proof harnesses to verify specific functions in the library.
151+
The current convention is to keep proofs in the same module file of the verification target.
152+
153+
To run Kani for an individual proof, use `--harness [harness_function_name]`.
154+
Note that Kani will batch run all proofs in the library folder if you do not supply the `--harness` flag.
155+
156+
If Kani returns the error `no harnesses matched the harness filter`, you can give the full name of the harness.
157+
For example, to run the proof harness named `check_new` in `library/core/src/ptr/unique.rs`, use
158+
`--harness ptr::unique::verify::check_new`. To run all proofs in `unique.rs`, use `--harness ptr::unique::verify`.
159+
154160
To find the full name of a harness, check the Kani output and find the line starting with `Checking harness [harness full name]`.
155161

156162
## More details

library/contracts/safety/src/kani.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use proc_macro::{TokenStream};
2-
use quote::{quote, format_ident};
3-
use syn::{ItemFn, parse_macro_input};
1+
use proc_macro::TokenStream;
2+
use quote::{format_ident, quote};
3+
use syn::{parse_macro_input, ItemFn, Stmt};
44

55
pub(crate) fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
66
rewrite_attr(attr, item, "requires")
@@ -10,12 +10,28 @@ pub(crate) fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream {
1010
rewrite_attr(attr, item, "ensures")
1111
}
1212

13+
pub(crate) fn loop_invariant(attr: TokenStream, stmt: TokenStream) -> TokenStream {
14+
rewrite_stmt_attr(attr, stmt, "loop_invariant")
15+
}
16+
17+
fn rewrite_stmt_attr(attr: TokenStream, stmt_stream: TokenStream, name: &str) -> TokenStream {
18+
let args = proc_macro2::TokenStream::from(attr);
19+
let stmt = parse_macro_input!(stmt_stream as Stmt);
20+
let attribute = format_ident!("{}", name);
21+
quote!(
22+
#[kani_core::#attribute(#args)]
23+
#stmt
24+
)
25+
.into()
26+
}
27+
1328
fn rewrite_attr(attr: TokenStream, item: TokenStream, name: &str) -> TokenStream {
1429
let args = proc_macro2::TokenStream::from(attr);
1530
let fn_item = parse_macro_input!(item as ItemFn);
1631
let attribute = format_ident!("{}", name);
1732
quote!(
1833
#[kani_core::#attribute(#args)]
1934
#fn_item
20-
).into()
35+
)
36+
.into()
2137
}

0 commit comments

Comments
 (0)