Skip to content

Commit ce77c81

Browse files
add QueueState & mark queue fields as private
To make sure that the queue cannot be misused, we are only allowing alteration of the Queue by using the setters & other methods that are part of the public interface. To be able to save & restore the state of the Queue, we are introducing a `QueueState` structure. This structure represents the pure state of the Queue in the sense that it does not hold any information about the implementation of the queue. If the VMM wants this state to be serializable/deserializable, the structure needs to be duplicated in the VMM code and the appropriate ser/deser traits need to be implemented. Alternatively, VMMs can use directly the virtio-queue-ser crate that uses serde & versionize for this purpose. Signed-off-by: Andreea Florescu <[email protected]>
1 parent da0c7f7 commit ce77c81

File tree

5 files changed

+190
-57
lines changed

5 files changed

+190
-57
lines changed

coverage_config_x86_64.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"coverage_score": 85.5,
2+
"coverage_score": 87.2,
33
"exclude_path": "crates/virtio-bindings,crates/virtio-queue/src/mock.rs",
44
"crate_features": "virtio-blk/backend-stdio"
55
}

crates/virtio-queue-ser/src/state.rs

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
//
33
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
4-
5-
use std::num::Wrapping;
6-
74
use serde::{Deserialize, Serialize};
85
use versionize::{VersionMap, Versionize, VersionizeResult};
96
use versionize_derive::Versionize;
10-
use virtio_queue::Queue;
11-
use vm_memory::GuestAddress;
7+
use virtio_queue::QueueState;
128

139
/// Wrapper over a `QueueState` that has serialization capabilities.
1410
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Versionize)]
@@ -46,78 +42,79 @@ pub struct QueueStateSer {
4642
// Nevertheless, we don't make any assumptions in the virtio-queue code about the queue's state that
4743
// would otherwise result in a panic, when initialized with random data, so from this point of view
4844
// these conversions are safe to use.
49-
impl From<&QueueStateSer> for Queue {
45+
impl From<&QueueStateSer> for QueueState {
5046
fn from(state: &QueueStateSer) -> Self {
51-
Queue {
47+
QueueState {
5248
max_size: state.max_size,
53-
next_avail: Wrapping(state.next_avail),
54-
next_used: Wrapping(state.next_used),
49+
next_avail: state.next_avail,
50+
next_used: state.next_used,
5551
event_idx_enabled: state.event_idx_enabled,
56-
num_added: Wrapping(0),
5752
size: state.size,
5853
ready: state.ready,
59-
desc_table: GuestAddress(state.desc_table),
60-
avail_ring: GuestAddress(state.avail_ring),
61-
used_ring: GuestAddress(state.used_ring),
54+
desc_table: state.desc_table,
55+
avail_ring: state.avail_ring,
56+
used_ring: state.used_ring,
6257
}
6358
}
6459
}
6560

66-
impl From<&Queue> for QueueStateSer {
67-
fn from(state: &Queue) -> Self {
61+
impl From<&QueueState> for QueueStateSer {
62+
fn from(state: &QueueState) -> Self {
6863
QueueStateSer {
6964
max_size: state.max_size,
70-
next_avail: state.next_avail.0,
71-
next_used: state.next_used.0,
65+
next_avail: state.next_avail,
66+
next_used: state.next_used,
7267
event_idx_enabled: state.event_idx_enabled,
7368
size: state.size,
7469
ready: state.ready,
75-
desc_table: state.desc_table.0,
76-
avail_ring: state.avail_ring.0,
77-
used_ring: state.used_ring.0,
70+
desc_table: state.desc_table,
71+
avail_ring: state.avail_ring,
72+
used_ring: state.used_ring,
7873
}
7974
}
8075
}
8176

8277
impl Default for QueueStateSer {
8378
fn default() -> Self {
84-
QueueStateSer::from(&Queue::default())
79+
QueueStateSer::from(&QueueState::default())
8580
}
8681
}
8782

8883
#[cfg(test)]
8984
mod tests {
9085
use super::*;
91-
use virtio_queue::{mock::MockSplitQueue, Descriptor, QueueT};
92-
use vm_memory::GuestMemoryMmap;
86+
use std::convert::TryFrom;
87+
use virtio_queue::{Error, Queue};
9388

9489
#[test]
9590
fn test_state_ser() {
9691
const SOME_VALUE: u16 = 16;
9792

98-
let state = Queue {
93+
let state = QueueState {
9994
max_size: SOME_VALUE * 2,
100-
next_avail: Wrapping(SOME_VALUE - 1),
101-
next_used: Wrapping(SOME_VALUE + 1),
95+
next_avail: SOME_VALUE - 1,
96+
next_used: SOME_VALUE + 1,
10297
event_idx_enabled: false,
103-
num_added: Wrapping(0),
10498
size: SOME_VALUE,
10599
ready: true,
106-
desc_table: GuestAddress(SOME_VALUE as u64),
107-
avail_ring: GuestAddress(SOME_VALUE as u64 * 2),
108-
used_ring: GuestAddress(SOME_VALUE as u64 * 4),
100+
desc_table: SOME_VALUE as u64,
101+
avail_ring: SOME_VALUE as u64 * 2,
102+
used_ring: SOME_VALUE as u64 * 4,
109103
};
110104

111105
let ser_state = QueueStateSer::from(&state);
112-
let state_from_ser = Queue::from(&ser_state);
106+
let state_from_ser = QueueState::from(&ser_state);
113107

114108
// Check that the old and the new state are identical when using the intermediate
115109
// `QueueStateSer` object.
116110
assert_eq!(state, state_from_ser);
117111

118112
// Test the `Default` implementation of `QueueStateSer`.
119113
let default_queue_state_ser = QueueStateSer::default();
120-
assert_eq!(Queue::from(&default_queue_state_ser), Queue::default());
114+
assert_eq!(
115+
QueueState::from(&default_queue_state_ser),
116+
QueueState::default()
117+
);
121118
}
122119

123120
#[test]
@@ -129,8 +126,6 @@ mod tests {
129126
// public, and the way to obtain a Queue from a serialized Queue is by using a `try_from`
130127
// function which then makes sure that the deserialized values are valid before creating
131128
// a queue that might be invalid.
132-
let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
133-
let vq = MockSplitQueue::new(m, 16);
134129
let queue_ser = QueueStateSer {
135130
max_size: 16,
136131
next_avail: 0,
@@ -143,9 +138,8 @@ mod tests {
143138
used_ring: 276,
144139
};
145140

146-
let mut queue = Queue::from(&queue_ser);
147-
let desc_chain = vec![Descriptor::new(0x0, 0x100, 0, 0)];
148-
vq.build_desc_chain(&desc_chain).unwrap();
149-
assert!(queue.pop_descriptor_chain(m).is_none());
141+
let queue_state = QueueState::from(&queue_ser);
142+
let err = Queue::try_from(queue_state).unwrap_err();
143+
assert_eq!(err.to_string(), Error::InvalidSize.to_string());
150144
}
151145
}

crates/virtio-queue/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub use self::chain::{DescriptorChain, DescriptorChainRwIter};
2626
pub use self::descriptor::{Descriptor, VirtqUsedElem};
2727
pub use self::queue::{AvailIter, Queue};
2828
pub use self::queue_sync::QueueSync;
29+
pub use self::state::QueueState;
2930

3031
pub mod defs;
3132
#[cfg(any(test, feature = "test-utils"))]
@@ -35,6 +36,7 @@ mod chain;
3536
mod descriptor;
3637
mod queue;
3738
mod queue_sync;
39+
mod state;
3840

3941
/// Virtio Queue related errors.
4042
#[derive(Debug)]

crates/virtio-queue/src/queue.rs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ use crate::defs::{
2020
VIRTQ_USED_ELEMENT_SIZE, VIRTQ_USED_RING_HEADER_SIZE, VIRTQ_USED_RING_META_SIZE,
2121
};
2222
use crate::{
23-
error, Descriptor, DescriptorChain, Error, QueueGuard, QueueOwnedT, QueueT, VirtqUsedElem,
23+
error, Descriptor, DescriptorChain, Error, QueueGuard, QueueOwnedT, QueueState, QueueT,
24+
VirtqUsedElem,
2425
};
2526
use virtio_bindings::bindings::virtio_ring::VRING_USED_F_NO_NOTIFY;
2627

@@ -75,45 +76,38 @@ pub const MAX_QUEUE_SIZE: u16 = 32768;
7576
/// // The queue should not be ready after reset.
7677
/// assert!(!queue.ready());
7778
/// ```
78-
///
79-
/// WARNING: The current implementation allows setting up and using an invalid queue
80-
/// (initialized with random data since the `Queue`'s fields are public). When fixing
81-
/// <https://github.com/rust-vmm/vm-virtio/issues/172>, we plan to define a `QueueState` that
82-
/// represents the actual state of the queue (no `Wrapping`s in it, for example). This way, we
83-
/// will also be able to do the checks that we normally do in the queue's field setters when
84-
/// starting from scratch, when trying to create a `Queue` from a `QueueState`.
8579
#[derive(Debug, Default, PartialEq)]
8680
pub struct Queue {
8781
/// The maximum size in elements offered by the device.
88-
pub max_size: u16,
82+
max_size: u16,
8983

9084
/// Tail position of the available ring.
91-
pub next_avail: Wrapping<u16>,
85+
next_avail: Wrapping<u16>,
9286

9387
/// Head position of the used ring.
94-
pub next_used: Wrapping<u16>,
88+
next_used: Wrapping<u16>,
9589

9690
/// VIRTIO_F_RING_EVENT_IDX negotiated.
97-
pub event_idx_enabled: bool,
91+
event_idx_enabled: bool,
9892

9993
/// The number of descriptor chains placed in the used ring via `add_used`
10094
/// since the last time `needs_notification` was called on the associated queue.
101-
pub num_added: Wrapping<u16>,
95+
num_added: Wrapping<u16>,
10296

10397
/// The queue size in elements the driver selected.
104-
pub size: u16,
98+
size: u16,
10599

106100
/// Indicates if the queue is finished with configuration.
107-
pub ready: bool,
101+
ready: bool,
108102

109103
/// Guest physical address of the descriptor table.
110-
pub desc_table: GuestAddress,
104+
desc_table: GuestAddress,
111105

112106
/// Guest physical address of the available ring.
113-
pub avail_ring: GuestAddress,
107+
avail_ring: GuestAddress,
114108

115109
/// Guest physical address of the used ring.
116-
pub used_ring: GuestAddress,
110+
used_ring: GuestAddress,
117111
}
118112

119113
impl Queue {
@@ -172,6 +166,29 @@ impl Queue {
172166
Ok(())
173167
}
174168

169+
/// Returns the state of the `Queue`.
170+
///
171+
/// This is useful for implementing save/restore capabilities.
172+
/// The state does not have support for serialization, but this can be
173+
/// added by VMMs locally through the use of a
174+
/// [remote type](https://serde.rs/remote-derive.html).
175+
///
176+
/// Alternatively, a version aware and serializable/deserializable QueueState
177+
/// is available in the `virtio-queue-ser` crate.
178+
pub fn state(&self) -> QueueState {
179+
QueueState {
180+
max_size: self.max_size,
181+
next_avail: self.next_avail(),
182+
next_used: self.next_used(),
183+
event_idx_enabled: self.event_idx_enabled,
184+
size: self.size,
185+
ready: self.ready,
186+
desc_table: self.desc_table(),
187+
avail_ring: self.avail_ring(),
188+
used_ring: self.used_ring(),
189+
}
190+
}
191+
175192
// Helper method that writes `val` to the `avail_event` field of the used ring, using
176193
// the provided ordering.
177194
fn set_avail_event<M: GuestMemory>(

crates/virtio-queue/src/state.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use crate::{Error, Queue, QueueT};
2+
use std::convert::TryFrom;
3+
use vm_memory::GuestAddress;
4+
5+
/// Representation of the `Queue` state.
6+
///
7+
/// The `QueueState` represents the pure state of the `queue` without tracking any implementation
8+
/// details of the queue. The goal with this design is to minimize the changes required to the
9+
/// state, and thus the required transitions between states when upgrading or downgrading.
10+
///
11+
/// In practice this means that the `QueueState` consists solely of POD (Plain Old Data).
12+
///
13+
/// As this structure has all the fields public it is consider to be untrusted. A validated
14+
/// queue can be created from the state by calling the associated `try_from` function.
15+
#[derive(Clone, Copy, Debug, Default, PartialEq)]
16+
pub struct QueueState {
17+
/// The maximum size in elements offered by the device.
18+
pub max_size: u16,
19+
/// Tail position of the available ring.
20+
pub next_avail: u16,
21+
/// Head position of the used ring.
22+
pub next_used: u16,
23+
/// VIRTIO_F_RING_EVENT_IDX negotiated.
24+
pub event_idx_enabled: bool,
25+
/// The queue size in elements the driver selected.
26+
pub size: u16,
27+
/// Indicates if the queue is finished with configuration.
28+
pub ready: bool,
29+
/// Guest physical address of the descriptor table.
30+
pub desc_table: u64,
31+
/// Guest physical address of the available ring.
32+
pub avail_ring: u64,
33+
/// Guest physical address of the used ring.
34+
pub used_ring: u64,
35+
}
36+
37+
impl TryFrom<QueueState> for Queue {
38+
type Error = Error;
39+
40+
fn try_from(q_state: QueueState) -> Result<Self, Self::Error> {
41+
let mut q = Queue::new(q_state.max_size)?;
42+
43+
q.set_next_avail(q_state.next_avail);
44+
q.set_next_used(q_state.next_used);
45+
q.set_event_idx(q_state.event_idx_enabled);
46+
q.try_set_size(q_state.size)?;
47+
q.set_ready(q_state.ready);
48+
q.try_set_desc_table_address(GuestAddress(q_state.desc_table))?;
49+
q.try_set_avail_ring_address(GuestAddress(q_state.avail_ring))?;
50+
q.try_set_used_ring_address(GuestAddress(q_state.used_ring))?;
51+
52+
Ok(q)
53+
}
54+
}
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use super::*;
59+
60+
fn create_valid_queue_state() -> QueueState {
61+
let queue = Queue::new(16).unwrap();
62+
queue.state()
63+
}
64+
65+
#[test]
66+
fn test_empty_queue_state() {
67+
let max_size = 16;
68+
let queue = Queue::new(max_size).unwrap();
69+
70+
// Saving the state of a queue on which we didn't do any operation is ok.
71+
// Same for restore.
72+
let queue_state = queue.state();
73+
let restored_q = Queue::try_from(queue_state).unwrap();
74+
assert_eq!(queue, restored_q);
75+
}
76+
77+
#[test]
78+
fn test_invalid_queue_state() {
79+
// Let's generate a state that we know is valid so we can just alter one field at a time.
80+
let mut q_state = create_valid_queue_state();
81+
82+
// Test invalid max_size.
83+
// Size too small.
84+
q_state.max_size = 0;
85+
assert!(Queue::try_from(q_state).is_err());
86+
// Size too big.
87+
q_state.max_size = u16::MAX;
88+
assert!(Queue::try_from(q_state).is_err());
89+
// Size not a power of 2.
90+
q_state.max_size = 15;
91+
assert!(Queue::try_from(q_state).is_err());
92+
93+
// Test invalid size.
94+
let mut q_state = create_valid_queue_state();
95+
// Size too small.
96+
q_state.size = 0;
97+
assert!(Queue::try_from(q_state).is_err());
98+
// Size too big.
99+
q_state.size = u16::MAX;
100+
assert!(Queue::try_from(q_state).is_err());
101+
// Size not a power of 2.
102+
q_state.size = 15;
103+
assert!(Queue::try_from(q_state).is_err());
104+
105+
// Test invalid desc_table.
106+
let mut q_state = create_valid_queue_state();
107+
q_state.desc_table = 0xf;
108+
assert!(Queue::try_from(q_state).is_err());
109+
110+
// Test invalid avail_ring.
111+
let mut q_state = create_valid_queue_state();
112+
q_state.avail_ring = 0x1;
113+
assert!(Queue::try_from(q_state).is_err());
114+
115+
// Test invalid used_ring.
116+
let mut q_state = create_valid_queue_state();
117+
q_state.used_ring = 0x3;
118+
assert!(Queue::try_from(q_state).is_err());
119+
}
120+
}

0 commit comments

Comments
 (0)