mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
virtio-devices: net: handle corrupted requests with NEEDS_RESET
A buggy or malicious guest may write an inappropriate value into virtqueue's next_avail field. This will result in an error when iterating over the queue:863837ef86/virtio-queue/src/queue.rs (L708)but this error is (logged and) ignored if pop_descriptor_chain() is used:863837ef86/virtio-queue/src/queue.rs (L583)A reasonable approach, implemented here, is to mark the device as NEEDS_RESET and ignore further queue events until the guest reinitializes the device. How this patch was tested: Linux kernel was patched to trigger a bad next_avail when the virtqueue queue counter reaches 5000: --------------- START OF LINUX KERNEL PATCH ---------- $ git diff diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index b784aab668670..989f2a0c64a77 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -15,6 +15,9 @@ #include <linux/spinlock.h> #include <xen/xen.h> + +void virtqueue_kick_always(struct virtqueue *vq); + #ifdef DEBUG /* For development, we want to crash whenever the ring is screwed. */ #define BAD_RING(_vq, fmt, args...) \ @@ -677,6 +680,12 @@ static inline int virtqueue_add_split( struct virtqueue *_vq, * new available array entries. */ virtio_wmb(vq->weak_barriers); vq->split.avail_idx_shadow++; + { + if ((vq->split.avail_idx_shadow % 100) == 0) + printk(KERN_ERR "avail idx: %d", + (int)vq->split.avail_idx_shadow); + if (vq->split.avail_idx_shadow == 5000) + vq->split.avail_idx_shadow = 0; + } vq->split.vring.avail->idx = cpu_to_virtio16(_vq->vdev, vq->split.avail_idx_shadow); vq->num_added++; @@ -689,6 +698,11 @@ static inline int virtqueue_add_split( struct virtqueue *_vq, if (unlikely(vq->num_added == (1 << 16) - 1)) virtqueue_kick(_vq); + { + if (unlikely(vq->split.avail_idx_shadow == 0)) + virtqueue_kick_always(_vq); + } + return 0; unmap_release: @@ -2515,6 +2529,11 @@ bool virtqueue_kick(struct virtqueue *vq) } EXPORT_SYMBOL_GPL(virtqueue_kick); +void virtqueue_kick_always(struct virtqueue *vq) +{ + virtqueue_kick_prepare(vq); + virtqueue_notify(vq); +} /** * virtqueue_get_buf_ctx - get the next used buffer * @_vq: the struct virtqueue we're talking about. --------------- END OF LINUX KERNEL PATCH ---------- Then the kernel was booted, and the host pinged until the nic became unresponsive: ping -i 0.002 192.168.4.1 Device status was confirmed using cat /sys/class/net/eth0/device/status (it was 0x4f). Then the device was re-initialized: DEV_NAME=$(basename $(readlink -f /sys/class/net/eth0/device)) echo $DEV_NAME | tee /sys/bus/virtio/drivers/virtio_net/unbind echo $DEV_NAME | tee /sys/bus/virtio/drivers/virtio_net/bind ip link set eth0 up At this point networking became healthly again. Signed-off-by: Peter Oskolkov <posk@google.com>
This commit is contained in:
@@ -51,7 +51,13 @@ impl TxVirtio {
|
||||
let mut retry_write = false;
|
||||
let mut rate_limit_reached = false;
|
||||
|
||||
while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) {
|
||||
loop {
|
||||
let mut iter = queue
|
||||
.iter(mem)
|
||||
.map_err(NetQueuePairError::QueueIteratorFailed)?;
|
||||
let Some(mut desc_chain) = iter.next() else {
|
||||
break;
|
||||
};
|
||||
if rate_limit_reached {
|
||||
queue.go_to_previous_position();
|
||||
break;
|
||||
@@ -180,7 +186,13 @@ impl RxVirtio {
|
||||
let mut exhausted_descs = true;
|
||||
let mut rate_limit_reached = false;
|
||||
|
||||
while let Some(mut desc_chain) = queue.pop_descriptor_chain(mem) {
|
||||
loop {
|
||||
let mut iter = queue
|
||||
.iter(mem)
|
||||
.map_err(NetQueuePairError::QueueIteratorFailed)?;
|
||||
let Some(mut desc_chain) = iter.next() else {
|
||||
break;
|
||||
};
|
||||
if rate_limit_reached {
|
||||
exhausted_descs = false;
|
||||
queue.go_to_previous_position();
|
||||
|
||||
Reference in New Issue
Block a user