Skip to content

Commit 87a03c7

Browse files
committed
feat(queue): more generic kani proofs
Use `kani::any` for more generic `ProofContext`. Add back proof for `add_used`. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 3ed55d3 commit 87a03c7

File tree

1 file changed

+43
-28
lines changed

1 file changed

+43
-28
lines changed

src/vmm/src/devices/virtio/queue.rs

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -826,15 +826,6 @@ mod verification {
826826
)
827827
}
828828

829-
fn setup_zeroed_guest_memory() -> ProofGuestMemory {
830-
guest_memory(unsafe {
831-
std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked(
832-
GUEST_MEMORY_SIZE,
833-
16,
834-
))
835-
})
836-
}
837-
838829
// Constants describing the in-memory layout of a queue of size FIRECRACKER_MAX_SIZE starting
839830
// at the beginning of guest memory. These are based on Section 2.6 of the VirtIO 1.1
840831
// specification.
@@ -882,27 +873,15 @@ mod verification {
882873

883874
ProofContext(queue, mem)
884875
}
885-
886-
/// Creates a [`ProofContext`] where the queue layout is fixed to a valid one and where
887-
/// guest memory is initialized to all zeros.
888-
pub fn bounded() -> Self {
889-
let mem = setup_zeroed_guest_memory();
890-
let mut queue = less_arbitrary_queue();
891-
queue.initialize(&mem).unwrap();
892-
893-
assert!(queue.is_valid(&mem));
894-
895-
ProofContext(queue, mem)
896-
}
897876
}
898877

899878
impl kani::Arbitrary for ProofContext {
900879
fn any() -> Self {
901880
let mem = setup_kani_guest_memory();
902881
let mut queue: Queue = kani::any();
903-
queue.initialize(&mem).unwrap();
904882

905883
kani::assume(queue.is_valid(&mem));
884+
queue.initialize(&mem).unwrap();
906885

907886
ProofContext(queue, mem)
908887
}
@@ -950,7 +929,7 @@ mod verification {
950929
// has been processed. This is done by the driver
951930
// defining a "used_event" index, which tells the device "please do not notify me until
952931
// used.ring[used_event] has been written to by you".
953-
let ProofContext(mut queue, _) = ProofContext::bounded_queue();
932+
let ProofContext(mut queue, _) = kani::any();
954933

955934
let num_added_old = queue.num_added.0;
956935
let needs_notification = queue.prepare_kick();
@@ -991,7 +970,7 @@ mod verification {
991970
// number of added descriptors being counted in Queue.num_added), and then use
992971
// "prepare_kick" to check if any of those descriptors should have triggered a
993972
// notification.
994-
let ProofContext(mut queue, _) = ProofContext::bounded_queue();
973+
let ProofContext(mut queue, _) = kani::any();
995974

996975
queue.enable_notif_suppression();
997976
assert!(queue.uses_notif_suppression);
@@ -1022,10 +1001,46 @@ mod verification {
10221001
assert_eq!(queue.prepare_kick(), needs_notification);
10231002
}
10241003

1004+
#[kani::proof]
1005+
#[kani::unwind(0)]
1006+
fn verify_add_used() {
1007+
let ProofContext(mut queue, _) = kani::any();
1008+
1009+
// The spec here says (2.6.8.2):
1010+
//
1011+
// The device MUST set len prior to updating the used idx.
1012+
// The device MUST write at least len bytes to descriptor, beginning at the first
1013+
// device-writable buffer, prior to updating the used idx.
1014+
// The device MAY write more than len bytes to descriptor.
1015+
//
1016+
// We can't really verify any of these. We can verify that guest memory is updated correctly
1017+
// though
1018+
1019+
// index into used ring at which the index of the descriptor to which
1020+
// the device wrote.
1021+
let used_idx = queue.next_used;
1022+
1023+
let used_desc_table_index = kani::any();
1024+
if queue.add_used(used_desc_table_index, kani::any()).is_ok() {
1025+
assert_eq!(queue.next_used, used_idx + Wrapping(1));
1026+
} else {
1027+
assert_eq!(queue.next_used, used_idx);
1028+
1029+
// Ideally, here we would want to actually read the relevant values from memory and
1030+
// assert they are unchanged. However, kani will run out of memory if we try to do so,
1031+
// so we instead verify the following "proxy property": If an error happened, then
1032+
// it happened at the very beginning of add_used, meaning no memory accesses were
1033+
// done. This is relying on implementation details of add_used, namely that
1034+
// the check for out-of-bounds descriptor index happens at the very beginning of the
1035+
// function.
1036+
assert!(used_desc_table_index >= queue.actual_size());
1037+
}
1038+
}
1039+
10251040
#[kani::proof]
10261041
#[kani::unwind(0)]
10271042
fn verify_is_empty() {
1028-
let ProofContext(queue, _) = ProofContext::bounded_queue();
1043+
let ProofContext(queue, _) = kani::any();
10291044

10301045
assert_eq!(queue.len() == 0, queue.is_empty());
10311046
}
@@ -1120,7 +1135,7 @@ mod verification {
11201135
#[kani::unwind(0)]
11211136
#[kani::solver(cadical)]
11221137
fn verify_pop() {
1123-
let ProofContext(mut queue, _) = ProofContext::bounded_queue();
1138+
let ProofContext(mut queue, _) = kani::any();
11241139

11251140
// This is an assertion in pop which we use to abort firecracker in a ddos scenario
11261141
// This condition being false means that the guest is asking us to process every element
@@ -1143,7 +1158,7 @@ mod verification {
11431158
#[kani::unwind(0)]
11441159
#[kani::solver(cadical)]
11451160
fn verify_undo_pop() {
1146-
let ProofContext(mut queue, _) = ProofContext::bounded_queue();
1161+
let ProofContext(mut queue, _) = kani::any();
11471162

11481163
// See verify_pop for explanation
11491164
kani::assume(queue.len() <= queue.actual_size());
@@ -1177,7 +1192,7 @@ mod verification {
11771192
#[kani::unwind(0)]
11781193
#[kani::solver(cadical)]
11791194
fn verify_checked_new() {
1180-
let ProofContext(queue, mem) = ProofContext::bounded_queue();
1195+
let ProofContext(queue, mem) = kani::any();
11811196

11821197
let index = kani::any();
11831198
let maybe_chain =

0 commit comments

Comments
 (0)