Skip to content

verify add_used operation using guestmemory stubs #346

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

priyasiddharth
Copy link

Summary of the PR

We need to stub out or provide fake implementation for GuestMemory read/write operations since they are too expensive for kani to execute as-is.

This problem was faces by firekracker also and solved by using a fake impl (link).

in vmm-virtio GuestMemory is hardcoded to use the default impl (link) so it is not easy to fake it. There are a few options i see.

  1. conditionally compile a fake version of Bytes trait for GuestMemory for kani - needs changes to vm-memory crate
  2. All vm-virtio functions use GuestMemory in production and FakeGuestMemory in kani - needs changes to vm-virtio to conditionally compile and use the right GuestMemory
  3. Use per unit-proof stubbing provided by kani

This PR provides the third option. However, 1 or 2 maybe a better engineering solution. Looking for inputs.

Requirements

Before submitting your PR, please make sure you addressed the following
requirements:

  • All commits in this PR have Signed-Off-By trailers (with
    git commit -s), and the commit message has max 60 characters for the
    summary and max 75 characters for each description line.
  • All added/changed functionality has a corresponding unit/integration
    test.
  • All added/changed public-facing functionality has entries in the "Upcoming
    Release" section of CHANGELOG.md (if no such section exists, please create one).
  • Any newly added unsafe code is properly documented.

Run kani as a part of the CI pipeline. In particular, run the proofs for
virtio-queue. In some cases, kani may not finish so set a twenty minutes
timeout.

Signed-off-by: Matias Ezequiel Vara Larsen <[email protected]>
Signed-off-by: Siddharth Priya <[email protected]>
Add the verify_spec_2_7_7_2() proof to verify that the implementation of
queue satisfies 2.7.7.2 requirement. The proof relies on whether the
EVENT_IDX feature has been negotiated. Conversely with
`test_needs_notification()` test, this proof `tests` for all possible
values of the queue structure.

Signed-off-by: Matias Ezequiel Vara Larsen <[email protected]>
Signed-off-by: Siddharth Priya <[email protected]>
@priyasiddharth
Copy link
Author

@roypat Hi Patrick,
I am looking for some guidance on how best to fake GuestMemory read/writes for kani. The PR contains a detailed description of the problem. Hoping you can help.

thanks
Siddharth

MatiasVara and others added 2 commits June 18, 2025 20:17
Add the verify_spec_2_7_7_2() proof to verify that the implementation of
queue satisfies 2.7.7.2 requirement. The proof relies on whether the
EVENT_IDX feature has been negotiated. Conversely with
`test_needs_notification()` test, this proof `tests` for all possible
values of the queue structure.

Signed-off-by: Matias Ezequiel Vara Larsen <[email protected]>
GuestMemory trait brings in Bytes trait and therefore hardcodes impl for write_obj and store methods.
This does not work well with Kani which now has to process these methods symbolically.
The solution is to provide NOP impl for these methods.
My current solution is to use kani stub macro to replace these methods by local stubs.
Another solution can be to provide the proof writer a way to construct GuestMemory such that they can stub out methods as needed.

Signed-off-by: Siddharth Priya <[email protected]>
#[kani::unwind(0)]
#[kani::stub(write_elem, stub_write_elem)]
#[kani::stub(store_elem, stub_store_elem)]
fn verify_add_used() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the proof

@@ -466,20 +774,12 @@ impl QueueT for Queue {
.used_ring
.checked_add(offset)
.ok_or(Error::AddressOverflow)?;
mem.write_obj(VirtqUsedElem::new(head_index.into(), len), addr)
.map_err(Error::GuestMemory)?;
write_elem(mem, addr, head_index, len)?;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delegating to local functions that can be stubbed by kani

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the only way to make this proof passes is by stubbing. I would like to understand better why write_obj() is taking so long.

Copy link
Author

@priyasiddharth priyasiddharth Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can be multiple problems:

  1. State space can explode
  2. There can be implicit loops (depending on semantics of copy) ... since we are writing a many-byte buffer .. and in the proof we say don't unroll/unroll once so all bytes may not be written, or other problems

Likely Flow:
The write_obj method delegates to write_slice :https://sourcegraph.com/github.com/rust-vmm/vm-memory/-/blob/src/mmap/mod.rs?L190-192
This copies bytes from src to dst: https://sourcegraph.com/github.com/rust-vmm/vm-memory/-/blob/src/bytes.rs?L538
Both problems can occur with this code.
See the commit from firecracker: https://sourcegraph.com/github.com/firecracker-microvm/firecracker/-/commit/1e680c64ae6a6f86f207d3b4e7be3a72fb95fc76
In firecracker it looks like problem 2 does not occur since we still have loops/array-copy in the fake implementation.

Ordering::Release,
)
.map_err(Error::GuestMemory)
store_elem(mem, self.used_ring, self.next_used.0)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

impl GuestMemory for ProofGuestMemory {
type R = vm_memory::GuestRegionMmap;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced this with something like type R = MyOptimizedRegion; and then this leaded me to implement Bytes<MemoryRegionAddress> and GuestMemoryRegion for MyOptimizedRegion. I also struggled to implemented new(), which looks like:

    impl MyOptimizedRegion {
        pub fn new(start: GuestAddress, len: u64) -> Self {
            // Allocate the memory region and bitmap
            Self {
                region_start: start,
                region_len: len,
                data: vec![0; len as usize],
                region_bitmap: AtomicBitmap::new(len as usize, NonZero::new(4096).unwrap()),
            }
        }
    }

I am worried that this approach would add a lot of boiler plate code and thus code duplication. Any comments @roypat ? Thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI the error during cargo kani is:

Check 4516: <std::ops::Range<usize> as std::iter::Iterator>::fold::<(), {closure@std::iter::adapters::map::map_fold<usize, std::sync::atomic::AtomicU64, (), {closure@vm_memory::bitmap::AtomicBitmap::new::{closure#0}}, {closure@std::iter::Iterator::for_each::call<std::sync::atomic::AtomicU64, {closure@std::vec::Vec<std::sync::atomic::AtomicU64>::extend_trusted<std::iter::Map<std::ops::Range<usize>, {closure@vm_memory::bitmap::AtomicBitmap::new::{closure#0}}>>::{closure#0}}>::{closure#0}}>::{closure#0}}>.unwind.0
         - Status: FAILURE
         - Description: "unwinding assertion loop 0"
         - Location: ../../../../../github/home/.rustup/toolchains/nightly-2024-10-03-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:2583:9 in function <std::ops::Range<usize> as std::iter::Iterator>::fold::<(), {closure@std::iter::adapters::map::map_fold<usize, std::sync::atomic::AtomicU64, (), {closure@vm_memory::bitmap::AtomicBitmap::new::{closure#0}}, {closure@std::iter::Iterator::for_each::call<std::sync::atomic::AtomicU64, {closure@std::vec::Vec<std::sync::atomic::AtomicU64>::extend_trusted<std::iter::Map<std::ops::Range<usize>, {closure@vm_memory::bitmap::AtomicBitmap::new::{closure#0}}>>::{closure#0}}>::{closure#0}}>::{closure#0}}>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants