Skip to content

Commit 214671c

Browse files
dianpopalikebreath
andcommitted
virtio-queue: panic on invalid avail ring index
The number of descriptor chain heads to process should always be smaller or equal to the queue size, as the driver should never ask the VMM to process a available ring entry more than once. Checking and reporting such incorrect driver behavior can prevent potential hanging and Denial-of-Service from happening on the VMM side. Issue reported in rust-vmm/vm-virtio and fixed in rust-vmm/vm-virtio#196. Signed-off-by: Diana Popa <dpopa@amazon.com> Co-authored-by: Bo Chen <chen.bo@intel.com>
1 parent 1285e4a commit 214671c

File tree

2 files changed

+119
-10
lines changed

2 files changed

+119
-10
lines changed

src/devices/src/virtio/block/device.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,6 @@ pub(crate) mod tests {
14801480
add_flush_requests_batch(&mut block, &mem, &vq, 1);
14811481
simulate_queue_event(&mut block, Some(false));
14821482
assert_eq!(block.is_io_engine_throttled, true);
1483-
assert_eq!(block.queues[0].len(&mem), 1);
14841483

14851484
simulate_async_completion_event(&mut block, true);
14861485
assert_eq!(block.is_io_engine_throttled, false);

src/devices/src/virtio/queue.rs

Lines changed: 119 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl fmt::Display for QueueError {
4848
}
4949
}
5050

51-
/// A virtio descriptor constraints with C representive.
51+
/// A virtio descriptor constraints with C representative.
5252
#[repr(C)]
5353
#[derive(Default, Clone, Copy)]
5454
struct Descriptor {
@@ -103,9 +103,9 @@ impl<'a> DescriptorChain<'a> {
103103
// These reads can't fail unless Guest memory is hopelessly broken.
104104
let desc = match mem.read_obj::<Descriptor>(desc_head) {
105105
Ok(ret) => ret,
106-
Err(_) => {
106+
Err(err) => {
107107
// TODO log address
108-
error!("Failed to read from memory");
108+
error!("Failed to read virtio descriptor from memory: {}", err);
109109
return None;
110110
}
111111
};
@@ -288,7 +288,7 @@ impl Queue {
288288
}
289289

290290
/// Returns the number of yet-to-be-popped descriptor chains in the avail ring.
291-
pub fn len(&self, mem: &GuestMemoryMmap) -> u16 {
291+
fn len(&self, mem: &GuestMemoryMmap) -> u16 {
292292
(self.avail_idx(mem) - self.next_avail).0
293293
}
294294

@@ -299,7 +299,21 @@ impl Queue {
299299

300300
/// Pop the first available descriptor chain from the avail ring.
301301
pub fn pop<'a, 'b>(&'a mut self, mem: &'b GuestMemoryMmap) -> Option<DescriptorChain<'b>> {
302-
if self.len(mem) == 0 {
302+
let len = self.len(mem);
303+
// The number of descriptor chain heads to process should always
304+
// be smaller or equal to the queue size, as the driver should
305+
// never ask the VMM to process a available ring entry more than
306+
// once. Checking and reporting such incorrect driver behavior
307+
// can prevent potential hanging and Denial-of-Service from
308+
// happening on the VMM side.
309+
if len > self.actual_size() {
310+
// We are choosing to interrupt execution since this could be a potential malicious
311+
// driver scenario. This way we also eliminate the risk of repeatedly
312+
// logging and potentially clogging the microVM through the log system.
313+
panic!("The number of available virtio descriptors is greater than queue size!");
314+
}
315+
316+
if len == 0 {
303317
return None;
304318
}
305319

@@ -424,9 +438,9 @@ impl Queue {
424438
fn avail_idx(&self, mem: &GuestMemoryMmap) -> Wrapping<u16> {
425439
// Bound checks for queue inner data have already been performed, at device activation time,
426440
// via `self.is_valid()`, so it's safe to unwrap and use unchecked offsets here.
427-
// Note: the `MmioTransport` code ensures that queue addresses cannot be changed by the guest
428-
// after device activation, so we can be certain that no change has occured since
429-
// the last `self.is_valid()` check.
441+
// Note: the `MmioTransport` code ensures that queue addresses cannot be changed by the
442+
// guest after device activation, so we can be certain that no change has
443+
// occurred since the last `self.is_valid()` check.
430444
let addr = self.avail_ring.unchecked_add(2);
431445
Wrapping(mem.read_obj::<u16>(addr).unwrap())
432446
}
@@ -462,7 +476,17 @@ impl Queue {
462476
return true;
463477
}
464478

465-
if self.len(mem) != 0 {
479+
let len = self.len(mem);
480+
if len != 0 {
481+
// The number of descriptor chain heads to process should always
482+
// be smaller or equal to the queue size.
483+
if len > self.actual_size() {
484+
// We are choosing to interrupt execution since this could be a potential malicious
485+
// driver scenario. This way we also eliminate the risk of
486+
// repeatedly logging and potentially clogging the microVM through
487+
// the log system.
488+
panic!("The number of available virtio descriptors is greater than queue size!");
489+
}
466490
return false;
467491
}
468492

@@ -741,6 +765,92 @@ pub(crate) mod tests {
741765
assert_eq!(q.avail_event(m), 2);
742766
}
743767

768+
#[test]
769+
#[should_panic(
770+
expected = "The number of available virtio descriptors is greater than queue size!"
771+
)]
772+
fn test_invalid_avail_idx_no_notification() {
773+
// This test ensures constructing a descriptor chain succeeds
774+
// with valid available ring indexes while it produces an error with invalid
775+
// indexes.
776+
// No notification suppression enabled.
777+
let m = &create_anon_guest_memory(&[(GuestAddress(0), 0x6000)], false).unwrap();
778+
779+
// We set up a queue of size 4.
780+
let vq = VirtQueue::new(GuestAddress(0), m, 4);
781+
let mut q = vq.create_queue();
782+
783+
for j in 0..4 {
784+
vq.dtable[j].set(
785+
0x1000 * (j + 1) as u64,
786+
0x1000,
787+
VIRTQ_DESC_F_NEXT,
788+
(j + 1) as u16,
789+
);
790+
}
791+
792+
// Create 2 descriptor chains.
793+
// the chains are (0, 1) and (2, 3)
794+
vq.dtable[1].flags.set(0);
795+
vq.dtable[3].flags.set(0);
796+
vq.avail.ring[0].set(0);
797+
vq.avail.ring[1].set(2);
798+
vq.avail.idx.set(2);
799+
800+
// We've just set up two chains.
801+
assert_eq!(q.len(m), 2);
802+
803+
// We process the first descriptor.
804+
let d = q.pop(m).unwrap().next_descriptor();
805+
assert!(matches!(d, Some(x) if !x.has_next()));
806+
// We confuse the device and set the available index as being 6.
807+
vq.avail.idx.set(6);
808+
809+
// We've actually just popped a descriptor so 6 - 1 = 5.
810+
assert_eq!(q.len(m), 5);
811+
812+
// However, since the apparent length set by the driver is more than the queue size,
813+
// we would be running the risk of going through some descriptors more than once.
814+
// As such, we expect to panic.
815+
q.pop(m);
816+
}
817+
818+
#[test]
819+
#[should_panic(
820+
expected = "The number of available virtio descriptors is greater than queue size!"
821+
)]
822+
fn test_invalid_avail_idx_with_notification() {
823+
// This test ensures constructing a descriptor chain succeeds
824+
// with valid available ring indexes while it produces an error with invalid
825+
// indexes.
826+
// Notification suppression is enabled.
827+
let m = &create_anon_guest_memory(&[(GuestAddress(0), 0x6000)], false).unwrap();
828+
829+
// We set up a queue of size 4.
830+
let vq = VirtQueue::new(GuestAddress(0), m, 4);
831+
let mut q = vq.create_queue();
832+
833+
q.uses_notif_suppression = true;
834+
835+
// Create 1 descriptor chain of 4.
836+
for j in 0..4 {
837+
vq.dtable[j].set(
838+
0x1000 * (j + 1) as u64,
839+
0x1000,
840+
VIRTQ_DESC_F_NEXT,
841+
(j + 1) as u16,
842+
);
843+
}
844+
// We need to clear the VIRTQ_DESC_F_NEXT for the last descriptor.
845+
vq.dtable[3].flags.set(0);
846+
vq.avail.ring[0].set(0);
847+
848+
// driver sets available index to suspicious value.
849+
vq.avail.idx.set(6);
850+
851+
q.pop_or_enable_notification(m);
852+
}
853+
744854
#[test]
745855
fn test_add_used() {
746856
let m = &create_anon_guest_memory(&[(GuestAddress(0), 0x10000)], false).unwrap();

0 commit comments

Comments
 (0)