Skip to content

Commit fe0b5a2

Browse files
roypatManciukic
authored andcommitted
refactor: eliminate Queue::is_valid
Vring validation was a bit awkwardly split across two functions which did overlapping sets of checks: Queue::initialize verified alignment and memory accesses, while Queue::is_valid additionally checked Queue::ready and Queue::size. However, on the activation path, both were called, meanign we checked alignment twice (.initialize() is called in .activate(), but we only call .activate() if .is_valid() returned true). This is confusing at best, and at worst made us potentially virtio spec incompliant: If the quest tried to activate a virtio device, but this failed because some vring was not valid (in terms of Queue::is_valid), then Firecracker would silently ignore the activation request. Now, it instead marks the device as needing reset, and notifies the guest of its failure to properly configure the vrings. While we're at it, also remove some duplicated checks from the vring restoration code: .initialize() is called for activated devices, so there's no need to later validate the size specifically again, and also no need for the additional call to is_valid(). Fix up some unit tests that activate virtio devices where some queues do not satisfy the old Queue::is_valid() checks, as now these checks must pass for activation to succeed. The only interesting fix here is in test_virtiodev_sanity_checks in virtio/persist.rs, which can be seen as a symptom of a bug fix: Previously, restoration code refused to load snapshots that had their queue size set to a value larger than Queue::max_size, even if a device was not activated. This is arguably wrong, as The guest can configure a queue to have a size greater than max size no problem, and never activate the device for example, in which case prior to this commit Firecracker would refuse to resume snapshots taken of such VMs. Signed-off-by: Patrick Roy <[email protected]>
1 parent 2f8155c commit fe0b5a2

File tree

10 files changed

+109
-114
lines changed

10 files changed

+109
-114
lines changed

src/vmm/src/devices/virtio/balloon/device.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,7 @@ pub(crate) mod tests {
822822
// Only initialize the inflate queue to demonstrate invalid request handling.
823823
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
824824
balloon.set_queue(INFLATE_INDEX, infq.create_queue());
825+
balloon.set_queue(DEFLATE_INDEX, infq.create_queue());
825826
balloon.activate(mem.clone()).unwrap();
826827

827828
// Fill the second page with non-zero bytes.
@@ -880,6 +881,7 @@ pub(crate) mod tests {
880881
let mem = default_mem();
881882
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
882883
balloon.set_queue(INFLATE_INDEX, infq.create_queue());
884+
balloon.set_queue(DEFLATE_INDEX, infq.create_queue());
883885
balloon.activate(mem.clone()).unwrap();
884886

885887
// Fill the third page with non-zero bytes.
@@ -949,6 +951,7 @@ pub(crate) mod tests {
949951
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
950952
let mem = default_mem();
951953
let defq = VirtQueue::new(GuestAddress(0), &mem, 16);
954+
balloon.set_queue(INFLATE_INDEX, defq.create_queue());
952955
balloon.set_queue(DEFLATE_INDEX, defq.create_queue());
953956
balloon.activate(mem.clone()).unwrap();
954957

@@ -997,6 +1000,8 @@ pub(crate) mod tests {
9971000
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
9981001
let mem = default_mem();
9991002
let statsq = VirtQueue::new(GuestAddress(0), &mem, 16);
1003+
balloon.set_queue(INFLATE_INDEX, statsq.create_queue());
1004+
balloon.set_queue(DEFLATE_INDEX, statsq.create_queue());
10001005
balloon.set_queue(STATS_INDEX, statsq.create_queue());
10011006
balloon.activate(mem.clone()).unwrap();
10021007

@@ -1097,6 +1102,9 @@ pub(crate) mod tests {
10971102
fn test_update_stats_interval() {
10981103
let mut balloon = Balloon::new(0, true, 0, false).unwrap();
10991104
let mem = default_mem();
1105+
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
1106+
balloon.set_queue(INFLATE_INDEX, q.create_queue());
1107+
balloon.set_queue(DEFLATE_INDEX, q.create_queue());
11001108
balloon.activate(mem).unwrap();
11011109
assert_eq!(
11021110
format!("{:?}", balloon.update_stats_polling_interval(1)),
@@ -1106,6 +1114,10 @@ pub(crate) mod tests {
11061114

11071115
let mut balloon = Balloon::new(0, true, 1, false).unwrap();
11081116
let mem = default_mem();
1117+
let q = VirtQueue::new(GuestAddress(0), &mem, 16);
1118+
balloon.set_queue(INFLATE_INDEX, q.create_queue());
1119+
balloon.set_queue(DEFLATE_INDEX, q.create_queue());
1120+
balloon.set_queue(STATS_INDEX, q.create_queue());
11091121
balloon.activate(mem).unwrap();
11101122
assert_eq!(
11111123
format!("{:?}", balloon.update_stats_polling_interval(0)),

src/vmm/src/devices/virtio/balloon/event_handler.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ pub mod tests {
146146
let mem = default_mem();
147147
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
148148
balloon.set_queue(INFLATE_INDEX, infq.create_queue());
149+
balloon.set_queue(DEFLATE_INDEX, infq.create_queue());
150+
balloon.set_queue(STATS_INDEX, infq.create_queue());
149151

150152
let balloon = Arc::new(Mutex::new(balloon));
151153
let _id = event_manager.add_subscriber(balloon.clone());

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ mod tests {
376376
use super::*;
377377
use crate::devices::virtio::block::virtio::device::FileEngineType;
378378
use crate::devices::virtio::mmio::VIRTIO_MMIO_INT_CONFIG;
379+
use crate::devices::virtio::test_utils::VirtQueue;
379380
use crate::devices::virtio::vhost_user::tests::create_mem;
380381
use crate::test_utils::create_tmp_socket;
381382
use crate::vstate::memory::GuestAddress;
@@ -780,6 +781,8 @@ mod tests {
780781
file.set_len(region_size as u64).unwrap();
781782
let regions = vec![(GuestAddress(0x0), region_size)];
782783
let guest_memory = create_mem(file, &regions);
784+
let q = VirtQueue::new(GuestAddress(0), &guest_memory, 16);
785+
vhost_block.queues[0] = q.create_queue();
783786

784787
// During actiavion of the device features, memory and queues should be set and activated.
785788
vhost_block.activate(guest_memory).unwrap();

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,7 @@ mod tests {
15701570

15711571
let mem = default_mem();
15721572
let vq = VirtQueue::new(GuestAddress(0), &mem, IO_URING_NUM_ENTRIES * 4);
1573+
block.queues[0] = vq.create_queue();
15731574
block.activate(mem.clone()).unwrap();
15741575

15751576
// Run scenario that doesn't trigger FullSq BlockError: Add sq_size flush requests.
@@ -1603,6 +1604,7 @@ mod tests {
16031604

16041605
let mem = default_mem();
16051606
let vq = VirtQueue::new(GuestAddress(0), &mem, IO_URING_NUM_ENTRIES * 4);
1607+
block.queues[0] = vq.create_queue();
16061608
block.activate(mem.clone()).unwrap();
16071609

16081610
// Run scenario that triggers FullCqError. Push 2 * IO_URING_NUM_ENTRIES and wait for
@@ -1632,6 +1634,7 @@ mod tests {
16321634

16331635
let mem = default_mem();
16341636
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
1637+
block.queues[0] = vq.create_queue();
16351638
block.activate(mem.clone()).unwrap();
16361639

16371640
// Add a batch of flush requests.

src/vmm/src/devices/virtio/mmio.rs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,6 @@ impl MmioTransport {
9595
self.device_status & (set | clr) == set
9696
}
9797

98-
fn are_queues_valid(&self) -> bool {
99-
self.locked_device()
100-
.queues()
101-
.iter()
102-
.all(|q| q.is_valid(&self.mem))
103-
}
104-
10598
fn with_queue<U, F>(&self, d: U, f: F) -> U
10699
where
107100
F: FnOnce(&Queue) -> U,
@@ -185,7 +178,7 @@ impl MmioTransport {
185178
DRIVER_OK if self.device_status == (ACKNOWLEDGE | DRIVER | FEATURES_OK) => {
186179
self.device_status = status;
187180
let device_activated = self.locked_device().is_activated();
188-
if !device_activated && self.are_queues_valid() {
181+
if !device_activated {
189182
// temporary variable needed for borrow checker
190183
let activate_result = self.locked_device().activate(self.mem.clone());
191184
if let Err(err) = activate_result {
@@ -486,8 +479,6 @@ pub(crate) mod tests {
486479

487480
assert_eq!(d.locked_device().queue_events().len(), 2);
488481

489-
assert!(!d.are_queues_valid());
490-
491482
d.queue_select = 0;
492483
assert_eq!(d.with_queue(0, |q| q.max_size), 16);
493484
assert!(d.with_queue_mut(|q| q.size = 16));
@@ -501,8 +492,6 @@ pub(crate) mod tests {
501492
d.queue_select = 2;
502493
assert_eq!(d.with_queue(0, |q| q.max_size), 0);
503494
assert!(!d.with_queue_mut(|q| q.size = 16));
504-
505-
assert!(!d.are_queues_valid());
506495
}
507496

508497
#[test]
@@ -761,7 +750,6 @@ pub(crate) mod tests {
761750
let m = single_region_mem(0x1000);
762751
let mut d = MmioTransport::new(m, Arc::new(Mutex::new(DummyDevice::new())), false);
763752

764-
assert!(!d.are_queues_valid());
765753
assert!(!d.locked_device().is_activated());
766754
assert_eq!(d.device_status, device_status::INIT);
767755

@@ -800,7 +788,6 @@ pub(crate) mod tests {
800788
write_le_u32(&mut buf[..], 1);
801789
d.bus_write(0x44, &buf[..]);
802790
}
803-
assert!(d.are_queues_valid());
804791
assert!(!d.locked_device().is_activated());
805792

806793
// Device should be ready for activation now.
@@ -860,7 +847,6 @@ pub(crate) mod tests {
860847
write_le_u32(&mut buf[..], 1);
861848
d.bus_write(0x44, &buf[..]);
862849
}
863-
assert!(d.are_queues_valid());
864850
assert_eq!(
865851
d.locked_device().interrupt_status().load(Ordering::SeqCst),
866852
0
@@ -910,7 +896,6 @@ pub(crate) mod tests {
910896
write_le_u32(&mut buf[..], 1);
911897
d.bus_write(0x44, &buf[..]);
912898
}
913-
assert!(d.are_queues_valid());
914899
assert!(!d.locked_device().is_activated());
915900

916901
// Device should be ready for activation now.
@@ -937,7 +922,6 @@ pub(crate) mod tests {
937922
let mut d = MmioTransport::new(m, Arc::new(Mutex::new(DummyDevice::new())), false);
938923
let mut buf = [0; 4];
939924

940-
assert!(!d.are_queues_valid());
941925
assert!(!d.locked_device().is_activated());
942926
assert_eq!(d.device_status, 0);
943927
activate_device(&mut d);

src/vmm/src/devices/virtio/persist.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,7 @@ impl VirtioDeviceState {
184184

185185
for q in &queues {
186186
// Sanity check queue size and queue max size.
187-
if q.max_size != expected_queue_max_size || q.size > expected_queue_max_size {
188-
return Err(PersistError::InvalidInput);
189-
}
190-
// Snapshot can happen at any time, including during device configuration/activation
191-
// when fields are only partially configured.
192-
//
193-
// Only if the device was activated, check `q.is_valid()`.
194-
if self.activated && !q.is_valid(mem) {
187+
if q.max_size != expected_queue_max_size {
195188
return Err(PersistError::InvalidInput);
196189
}
197190
}
@@ -333,6 +326,7 @@ mod tests {
333326
..Default::default()
334327
};
335328
state.queues = vec![bad_q];
329+
state.activated = true;
336330
state
337331
.build_queues_checked(&mem, 0, state.queues.len(), max_size)
338332
.unwrap_err();
@@ -351,6 +345,8 @@ mod tests {
351345
let mem = default_mem();
352346

353347
let mut queue = Queue::new(128);
348+
queue.ready = true;
349+
queue.size = queue.max_size;
354350
queue.initialize(&mem).unwrap();
355351

356352
let mut bytes = vec![0; 4096];

0 commit comments

Comments
 (0)