Skip to content

Commit edd1731

Browse files
virtio-queue: additional proofs around notification logic
1. Verify enable_notification logic 2. Verify when driver should send notification to device 3. Verify when driver should not send notification to device 4. Verify that set_next_used method actually changes the next_used field in memory Signed-off-by: Siddharth Priya <[email protected]>
1 parent 79eea6e commit edd1731

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

virtio-queue/src/queue/verification.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,139 @@ fn verify_add_used() {
565565
kani::cover!();
566566
}
567567
}
568+
569+
/// This proof checks that after setting the `next_used` field of the queue
570+
/// using `set_next_used(x)`, reading back the value of `next_used` returns the
571+
/// same value `x`.
572+
#[kani::proof]
573+
#[kani::unwind(0)]
574+
fn verify_used_ring_avail_event() {
575+
let ProofContext {
576+
mut queue,
577+
memory: _,
578+
} = kani::any();
579+
let x = kani::any();
580+
queue.set_next_used(x);
581+
assert_eq!(x, queue.next_used.0);
582+
}
583+
584+
/// # Specification (VirtIO 1.3, Section 2.7.6.1: "Driver Requirements: The Virtqueue Available Ring")
585+
///
586+
/// This proof checks that:
587+
/// - If there are pending entries in the avail ring (avail_idx != next_avail),
588+
/// `enable_notification` returns true.
589+
/// - If there are no pending entries (avail_idx == next_avail), it returns false.
590+
/// This matches the monotonicity property of the avail ring in VirtIO 1.3 Section 2.7.6.1.
591+
#[kani::proof]
592+
#[kani::unwind(0)]
593+
fn verify_enable_driver_to_device_notification() {
594+
let ProofContext { mut queue, memory } = kani::any();
595+
596+
// The enable_notification method sets notification to true and returns
597+
// - true, if there are pending entries in the `idx` field of the
598+
// avail ring
599+
// - false, if there are no pending entries in the `idx` field of the
600+
// avail ring The check for pending entries is done by comparing the
601+
// current `avail_idx` with the `next_avail` field of the queue. If they
602+
// are different, there are pending entries, otherwise there are no
603+
// pending entries. The equality check is a consequence of the
604+
// monotonicity property of `idx` (2.7.6.1) that it cannot be decreased
605+
// by the driver.
606+
if queue.enable_notification(&memory).unwrap() {
607+
assert_ne!(
608+
queue.avail_idx(&memory, Ordering::Relaxed).unwrap(),
609+
queue.next_avail
610+
);
611+
} else {
612+
assert_eq!(
613+
queue.avail_idx(&memory, Ordering::Relaxed).unwrap(),
614+
queue.next_avail
615+
);
616+
}
617+
}
618+
619+
// Helper method that reads `val` from the `avail_event` field of the used ring, using
620+
// the provided ordering. Takes used_ring address and queue size directly.
621+
fn get_avail_event<M: GuestMemory>(
622+
used_ring_addr: GuestAddress,
623+
queue_size: u16,
624+
mem: &M,
625+
order: Ordering,
626+
) -> Result<u16, Error> {
627+
// This can not overflow an u64 since it is working with relatively small numbers compar
628+
// to u64::MAX.
629+
let avail_event_offset =
630+
VIRTQ_USED_RING_HEADER_SIZE + VIRTQ_USED_ELEMENT_SIZE * u64::from(queue_size);
631+
let addr = used_ring_addr
632+
.checked_add(avail_event_offset)
633+
.ok_or(Error::AddressOverflow)?;
634+
635+
mem.load::<u16>(addr, order).map_err(Error::GuestMemory)
636+
}
637+
638+
// Get the value of the `flags` field of the used ring, applying the specified ordering.
639+
fn get_used_flags<M: GuestMemory>(queue: &Queue, mem: &M, order: Ordering) -> Result<u16, Error> {
640+
mem.load::<u16>(queue.used_ring, order)
641+
.map(u16::from_le)
642+
.map_err(Error::GuestMemory)
643+
}
644+
/// # Specification (VirtIO 1.3, Section 2.7.7: "Used Buffer Notification Suppression")
645+
///
646+
/// This proof checks:
647+
/// - If event_idx is not enabled, `set_notification(false)` sets used.flags to 1 (NO_NOTIFY).
648+
/// - If event_idx is enabled, the call is a no-op.
649+
#[kani::proof]
650+
#[kani::unwind(0)]
651+
fn verify_set_notification_true() {
652+
let ProofContext { mut queue, memory } = kani::any();
653+
if queue
654+
.set_notification(&memory, true /* enable notification */)
655+
.is_ok()
656+
{
657+
if queue.event_idx_enabled {
658+
// Since VIRTIO_F_EVENT_IDX is negotiated, we make sure that set_notification
659+
// has updated the used.avail_event field with the tail position of
660+
// the avail ring.
661+
let used_ring_addr = queue.used_ring;
662+
let queue_size = queue.size();
663+
kani::cover!();
664+
assert_eq!(
665+
get_avail_event(used_ring_addr, queue_size, &memory, Ordering::Relaxed).unwrap(),
666+
queue.next_avail.0
667+
);
668+
} else {
669+
// If VIRTIO_F_EVENT_IDX is not negotiated, we make sure that the
670+
// used.flags field is set to 0, meaning that the driver should not
671+
// send notifications to the device.
672+
kani::cover!();
673+
assert_eq!(
674+
get_used_flags(&queue, &memory, Ordering::Relaxed).unwrap(),
675+
0
676+
);
677+
}
678+
}
679+
}
680+
681+
/// # Specification (VirtIO 1.3, Section 2.7.7: "Used Buffer Notification Suppression")
682+
///
683+
/// This proof checks:
684+
/// - If event_idx is not enabled, `set_notification(false)` sets used.flags to 1 (NO_NOTIFY).
685+
/// - If event_idx is enabled, the call is a no-op.
686+
#[kani::proof]
687+
#[kani::unwind(0)]
688+
fn verify_set_notification_false() {
689+
let ProofContext { mut queue, memory } = kani::any();
690+
let result = queue.set_notification(&memory, false /* disable notification */);
691+
if !queue.event_idx_enabled {
692+
// Check for Sec 2.7.10
693+
assert_eq!(
694+
get_used_flags(&queue, &memory, Ordering::Relaxed).unwrap(),
695+
1
696+
);
697+
// don't check Ok() result since that is a property of the
698+
// underlying mem system and out of scope. E.g., it is stubbed for
699+
// this proof and we always expect it to succeed.
700+
} else {
701+
assert!(result.is_ok());
702+
}
703+
}

0 commit comments

Comments
 (0)