Skip to content

Commit 8855356

Browse files
committed
Drop using container approach sponsored by WFs, settings just run it
1 parent 18ef7ca commit 8855356

31 files changed

+20383
-6
lines changed

.github/workflows/container-test.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,18 @@ jobs:
5151
needs: publish-test-image
5252
permissions:
5353
packages: read
54-
container:
55-
image: ${{ needs.publish-test-image.outputs.image-name }}:ubuntu-jammy-${{ needs.publish-test-image.outputs.kmir-version }}_${{ needs.publish-test-image.outputs.k-version }}-${{ needs.publish-test-image.outputs.short-sha }}
56-
credentials:
57-
username: ${{ github.repository_owner }}
58-
password: ${{ secrets.GITHUB_TOKEN }}
5954
steps:
6055
- name: Checkout code
6156
uses: actions/checkout@v4
6257
with:
6358
submodules: recursive
64-
ref: sample-challenge-11-proofs
59+
ref: sample-challenge-11-proofs
60+
- name: Start KMIR Container
61+
run: |
62+
docker run --detach --rm --name kmir --tty --interactive ${{ needs.publish-test-image.outputs.image-name }}:ubuntu-jammy-${{ needs.publish-test-image.outputs.kmir-version }}_${{ needs.publish-test-image.outputs.k-version }}-${{ needs.publish-test-image.outputs.short-sha }}
63+
- name: Stream files to Container
64+
run: |
65+
docker cp rust-verification-proofs kmir:/home/kmir/
6566
- name: kmir Prove
6667
run: |
6768
cd rust-verification-proofs/unchecked_add

rust-verification-proofs/README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Formal Rust Code Verification Using KMIR
2+
3+
This subrepository contains a collection of programs and specifications that aim to illustrate how KMIR can be used to validate the properties of Rust programs and the Rust language itself. The code made available in this repository was developed taking as references the challenges present on the [Verify Rust Standard Library Effort](https://model-checking.github.io/verify-rust-std/intro.html).
4+
5+
## Table of Contents
6+
7+
8+
- [Project Setup](#project-setup)
9+
- [Proof 1: Proving a Maximum Finding Function That only Uses `lower-than (<)`](#proof-1-proving-a-maximum-finding-function-that-only-uses-lower-than)
10+
- [Proof 2: Proving Unsafe Arithmetic Operations](#proof-2-proving-unsafe-arithmetic-operations)
11+
12+
## Project Setup
13+
14+
In order to run and explore the proofs elaborated here, make sure that KMIR can be locally executed in your machine following the instructions available in [this repository's README file](https://github.com/runtimeverification/mir-semantics/tree/sample-challenge-11-proofs).
15+
16+
[Be sure to have Rust installed](https://www.rust-lang.org/tools/install) in your machine, have the specific components and toolchain necessary to run KMIR. To guarantee it, with `rustup` installed, run the following commands:
17+
18+
```bash
19+
rustup component add rust-src rustc-dev llvm-tools-preview
20+
rustup toolchain install nightly-2024-11-29
21+
rustup default nightly-2024-11-29
22+
```
23+
24+
**(Optional)** Additionally, if you would like to build your own code specifications to be proven with KMIR, install the [Rust Stable MIR Pretty Printing](https://github.com/runtimeverification/stable-mir-json/tree/20820cc6abd8fd22769931a3f8754ee35ab24c05) tool. It won't be necessary to install it if you'd like to understand how KMIR works and to execute its proofs, but it is needed currently to help us traverse program states, as seen in the [steps needed to achieve Proof 1's specification](https://github.com/runtimeverification/mir-semantics/tree/sample-challenge-11-proofs/rust-verification-proofs/maximum-proof). To install the Rust Stable MIR Pretty Printing tool, in the root of this project, run:
25+
26+
```bash
27+
git submodule update --init --recursive
28+
make stable-mir-json
29+
```
30+
31+
The usage of this tool will be abstracted in the future, removing the need to construct claims manually.
32+
33+
## Proof 1: Proving a Maximum Finding Function That only Uses `lower-than`
34+
35+
Considering a function that receives three integer arguments, this function should return the highest value among them. Assertions can be used to enforce this condition, and an example code that tests this function can be seen below:
36+
37+
```Rust
38+
fn main() {
39+
40+
let a:usize = 42;
41+
let b:usize = -43;
42+
let c:usize = 0;
43+
44+
let result = maximum(a, b, c);
45+
46+
assert!(result >= a && result >= b && result >= c
47+
&& (result == a || result == b || result == c ) );
48+
}
49+
50+
fn maximum(a: usize, b: usize, c: usize) -> usize {
51+
// max(a, max(b, c))
52+
let max_ab = if a < b {b} else {a};
53+
if max_ab < c {c} else {max_ab}
54+
}
55+
```
56+
57+
Notice in this case that `a`, `b`, and `c` are concrete, fixed values. To turn the parameters of `maximum` into symbolic variables, we can obtain the representation of the function call to `maximum` executed using KMIR and then replace the concrete values of these variables with symbolic values. Furthermore, the assertion specified in the code can be manually translated as a requirement that should be met by the symbolic variables, meaning that any value that they can assume must respect the conditions contained in the specification. Following this approach, we can utilize KMIR to give us formal proof that, for any valid `isize` input, the maximum value among the three parameters will be returned.
58+
59+
Information on how the specification was created can be found in the longer [description of `maximum-proof`](https://github.com/runtimeverification/mir-semantics/tree/sample-challenge-11-proofs/rust-verification-proofs/maximum-proof).
60+
61+
To run this proof in your terminal from this folder, execute:
62+
63+
```Bash
64+
cd maximum-proof
65+
poetry -C ../../kmir/ run -- kmir prove run $PWD/maximum-spec.k --proof-dir $PWD/proof
66+
```
67+
68+
## Proof 2: Proving Unsafe Arithmetic Operations
69+
70+
The proofs in this section concern a section of the challenge of securing [Safety of Methods for Numeric Primitive Types](https://model-checking.github.io/verify-rust-std/challenges/0011-floats-ints.html#challenge-11-safety-of-methods-for-numeric-primitive-types) of the Verify Rust Standard Library Effort. Here, we implement proof of concepts of how KMIR can be used to prove the following unsafe methods according to their undefined behaviors: `unchecked_add`, `unchecked_sub`, `unchecked_mul`, `unchecked_shl`, `unchecked_shr`, and `unchecked_neg`.
71+
72+
For these functions, the proofs were carried out using variables of the `i16` integer type, and the criteria for triggering undefined behaviors for these methods were obtained in the [i16 type documentation page](https://doc.rust-lang.org/std/primitive.i16.html).
73+
74+
To obtain the specifications that prove the presence/absence of undefined behavior for these functions, analogous processes to the ones discussed in [Proof 1](#proof-1-proving-a-maximum-finding-function-that-only-uses-lower-than) were performed.
75+
76+
For an illustration of how we specify the requirements of the proof, which, in this case, are the assertions that would help us detect the presence/absence of undefined behavior in the unsafe methods, let's explore how we can prove safety conditions for the `unchecked_add` operation. Consider the following function:
77+
78+
https://github.com/runtimeverification/mir-semantics/blob/e2de329d009cde25f505819d7c8c9815571db9e7/rust-verification-proofs/unchecked_add/unchecked-add.rs#L11-L14
79+
80+
`unchecked_op` is a function that receives two `i16` arguments and executes an `unchecked_add` of the first parameter by the second, returning an `i16` value resulting from this operation. According to the [documentation of the unchecked_add function for the i16 primitive type](https://doc.rust-lang.org/std/primitive.i16.html#method.unchecked_add), considering the safety of this function "This results in undefined behavior when `self + rhs > i16::MAX or self + rhs < i16::MIN`, i.e. when `checked_add` would return `None`". By the process further disclosed in Proof 1, we can obtain a valid representation of a function call for `unchecked_op` and modify the variable values to be symbolic. The next step is to define the conditions these values should meet to verify safety conditions elaborated for `unchecked_add`. To this goal, see the following code snippet:
81+
82+
https://github.com/runtimeverification/mir-semantics/blob/e2de329d009cde25f505819d7c8c9815571db9e7/rust-verification-proofs/unchecked_add/unchecked-op-spec.k#L66-L73
83+
84+
The parameters for `unchecked_add` in this specification for KMIR are represented as A and B, which now are symbolic values. To specify the goal of our verification process, we implemented the above code snippet into the specification, which adds a requirement to the execution of our symbolic execution engine. In other words, our proof will only be successful if the specified requirements above are respected.
85+
86+
In this `requires` clause, first, we use the semantics of K to specify A and B's boundaries, as `i16`s: `0 -Int (1 <<Int 15) <=Int A andBool A <Int (1 <<Int 15) andBool 0 -Int (1 <<Int 15) <=Int B andBool B <Int (1 <<Int 15)`. The next section of this `requires` clause specifies the safety conditions for the `unchecked_add` method: `andBool A +Int B <Int (1 <<Int 15) andBool 0 -Int (1 <<Int 15) <=Int A +Int B`.
87+
88+
To run the proofs for these functions, run the commands below, replacing `$METHOD_NAME` with the desired unsafe method name:
89+
90+
```Bash
91+
cd $METHOD_NAME
92+
poetry -C ../../kmir/ run -- kmir prove run $PWD/unchecked-op-spec.k --proof-dir $PWD/proof
93+
```
94+
95+
The proof for `unchecked_add` is expected to work as described. Currently, we expect some of the other unsafe arithmetic proofs to manifest unpredicted behavior and end its execution prior to providing the proof's result. This behavior is due to an unfinished implementation of the operations under test and will be addressed in due course.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Turning the max-with-lt program into a property proof
2+
3+
## Example program that we start from
4+
5+
```rust
6+
fn main() {
7+
8+
let a:usize = 42;
9+
let b:usize = 22;
10+
let c:usize = 0;
11+
12+
let result = maximum(a, b, c);
13+
14+
assert!(result >= a && result >= b && result >= c
15+
&& (result == a || result == b || result == c ) );
16+
}
17+
18+
fn maximum(a: usize, b: usize, c: usize) -> usize {
19+
// max(a, max(b, c))
20+
let max_ab = if a < b {b} else {a};
21+
if max_ab < c {c} else {max_ab}
22+
}
23+
```
24+
25+
We want to prove a property of `maximum`:
26+
- When called with `a`, `b`, and `c`,
27+
- the `result` will be greater or equal all of the arguments,
28+
- and equal to one (or more) of them.
29+
30+
The `main` program above states this using some concrete values of `a`, `b`, and `c`. We will run this program to construct a general symbolic claim and prove it.
31+
32+
In a future version, we will be able to start directly with the `maximum` function call and provide symbolic arguments to it. This will save some manual work setting up the claim file and fits the target of proving based on property tests.
33+
34+
## Extracting Stable MIR for the program
35+
36+
Before we can run the program using the MIR semantics, we have to compile it with a special compiler to extract Stable MIR from it. This step differs a bit depending on whether the program has multiple crates, in our case it is just a simple `rustc` invocation. This creates `main-max-with-lt.smir.json`. (Run the below commmands from the `mir-semantics/rust-verification-proofs/maximum-proof/` directory).
37+
38+
```shell
39+
cargo -Z unstable-options -C ../../deps/stable-mir-json/ run -- -Zno-codegen --out-dir $PWD $PWD/main-max-with-lt.rs
40+
```
41+
The Stable MIR for the program can also be rendered as a graph, using the `--dot` option. This creates `main-max-with-lt.smir.dot`.
42+
43+
```shell
44+
cargo -Z unstable-options -C ../../deps/stable-mir-json/ run -- --dot -Zno-codegen --out-dir $PWD $PWD/main-max-with-lt.rs
45+
```
46+
## Constructing the claim by executing `main` to certain points
47+
Through concrete execution of the parsed K program we can interrupt the execution after a given number of rewrite steps to inspect the intermediate state. This will help us with writing our claim manually until the process is automated.
48+
49+
1. The program (`main`) reaches the call to `maximum` after 22 steps.
50+
The following command runs it and displays the resulting program state.
51+
52+
```shell
53+
poetry -C ../../kmir/ run -- kmir run $PWD/main-max-with-lt.smir.json --depth 22 | less -S
54+
```
55+
- Arguments `a`, `b`, and `c` are initialised to `Integer`s as `locals[1]` to `locals[3]`
56+
- A `call` terminator calling function `ty(25)` is executed next (front of the `k` cell)
57+
- The function table contains `ty(25) -> "maximum" code.
58+
- Other state (how `main` continues, its other local variables, and some internal functions) is relevant to the proof we want to perform.
59+
2. The program executes for a total of 92 steps to reach the point where it `return`s from `maximum`.
60+
The following command runs it and displays the resulting program state.
61+
62+
```shell
63+
poetry -C ../../kmir/ run -- kmir run $PWD/main-max-with-lt.smir.json --depth 92 | less -S
64+
```
65+
- The value `locals[0]` is now set to an `Integer`. This will be the target of our assertions.
66+
- A `return` terminator is executed next (front of the `k` cell), it will return `locals[0]`
67+
- It should be an `Integer` with the desired properties as stated above
68+
69+
State 1. defines our start state for the claim. Irrelevant parts are elided (replaced by variables).
70+
* The code of the `maximum` function in the `functions` table needs to be kept. We also keep its identifier `ty(25)`. Other functions can be removed (we won't perform a return).
71+
* The `call` terminator is kept, calling `ty(25)` with arguments from `locals[1,2,3]`. `target` is modified to be `noBasicBlockIdx` to force termination of the prover (no block to jump back to).
72+
* The four locals `0` - `3` are required in their original order to provide the function arguments. The values of `a`, `b`, and `c` in locals `1` - `3` are replaced with symbolic variables used in the proof.
73+
* We could keep all other locals but do not have to (however it is important that the list of locals has a known length).
74+
* `main`s other details in `currentFrame` are irrelevant and elided.
75+
76+
77+
State 2. is the end state, where all that matters is the returned value.
78+
79+
* The `locals` list should contain this `?RESULT` value at index `0`
80+
* The `?RESULT` value should have the properties stated (equivalent to the assertion in `main`)
81+
* Because of the modified `target`, the program should end, i.e., have an `#EndProgram` in the `k` cell.
82+
83+
The above is written as a _claim_ in K framework language into a `maximum-spec.k` file.
84+
Most of the syntax can be copied from the output of the `kmir run` commands above, and irrelevant parts replaced by `_` (LHS) or `?_` (RHS).
85+
86+
Alternatively, it is possible to construct a claim that the entire rest of the program after initialising the variables will result in the desired `?RESULT`, i.e., the assertion in `main` is executed successfully and the program ends in `#EndProgram` after checking it. This would require more steps.
87+
88+
## Running the prover on the claim and viewing the proof
89+
Now that we have constructed claim, we can run use the KMIR verifier to perform symbollic execution, and can view the state of proof through the KMIR proof viewer.
90+
```shell
91+
poetry -C ../../kmir/ run -- kmir prove run $PWD/maximum-spec.k --proof-dir $PWD/proof
92+
```
93+
94+
The proof steps are saved in the `$PWD/proof` directory for later inspection using `kmir prove view`. This is especially important when the proof does _not_ succeed immediately.
95+
96+
```shell
97+
poetry -C ../../kmir/ run -- kmir prove view MAXIMUM-SPEC.maximum-spec --proof-dir $PWD/proof
98+
```
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
fn main() {
3+
4+
let a:usize = 42;
5+
let b:usize = 22;
6+
let c:usize = 0;
7+
8+
let result = maximum(a, b, c);
9+
10+
assert!(result >= a && result >= b && result >= c
11+
&& (result == a || result == b || result == c ) );
12+
}
13+
14+
fn maximum(a: usize, b: usize, c: usize) -> usize {
15+
// max(a, max(b, c))
16+
let max_ab = if a < b {b} else {a};
17+
if max_ab < c {c} else {max_ab}
18+
}

0 commit comments

Comments
 (0)