virtio-devices: vsock: Support single descriptor

Since kernel v6.3 the vsock packet is not split over two descriptors
and is instead included in a single one.

This change is based on the discovery and fix identified by Stefano
Garzarella for the vm-virtio vsock implementation and adapted for our
very different codebase.

Fixes: #5691

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
This commit is contained in:
Rob Bradford 2023-08-21 16:22:52 +01:00
parent 9d5c5a6410
commit 4c22fa8df0

View File

@ -23,7 +23,7 @@ use super::defs;
use super::{Result, VsockError};
use crate::get_host_address_range;
use virtio_queue::DescriptorChain;
use vm_memory::GuestMemory;
use vm_memory::{Address, GuestMemory};
use vm_virtio::{AccessPlatform, Translatable};
// The vsock packet header is defined by the C struct:
@ -133,7 +133,7 @@ impl VsockPacket {
hdr: get_host_address_range(
desc_chain.memory(),
head.addr()
.translate_gva(access_platform, head.len() as usize),
.translate_gva(access_platform, VSOCK_PKT_HDR_SIZE),
VSOCK_PKT_HDR_SIZE,
)
.ok_or(VsockError::GuestMemory)?,
@ -152,32 +152,46 @@ impl VsockPacket {
return Err(VsockError::InvalidPktLen(pkt.len()));
}
// If the packet header showed a non-zero length, there should be a data descriptor here.
let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?;
// Prior to Linux v6.3 there are two descriptors
if head.has_next() {
let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?;
// TX data should be read-only.
if buf_desc.is_write_only() {
return Err(VsockError::UnreadableDescriptor);
// TX data should be read-only.
if buf_desc.is_write_only() {
return Err(VsockError::UnreadableDescriptor);
}
// The data buffer should be large enough to fit the size of the data, as described by
// the header descriptor.
if buf_desc.len() < pkt.len() {
return Err(VsockError::BufDescTooSmall);
}
let buf_size = buf_desc.len() as usize;
pkt.buf_size = buf_size;
pkt.buf = Some(
get_host_address_range(
desc_chain.memory(),
buf_desc.addr().translate_gva(access_platform, buf_size),
pkt.buf_size,
)
.ok_or(VsockError::GuestMemory)?,
);
} else {
let buf_size: usize = head.len() as usize - VSOCK_PKT_HDR_SIZE;
pkt.buf_size = buf_size;
pkt.buf = Some(
get_host_address_range(
desc_chain.memory(),
head.addr()
.checked_add(VSOCK_PKT_HDR_SIZE as u64)
.unwrap()
.translate_gva(access_platform, buf_size),
buf_size,
)
.ok_or(VsockError::GuestMemory)?,
);
}
// The data buffer should be large enough to fit the size of the data, as described by
// the header descriptor.
if buf_desc.len() < pkt.len() {
return Err(VsockError::BufDescTooSmall);
}
pkt.buf_size = buf_desc.len() as usize;
pkt.buf = Some(
get_host_address_range(
desc_chain.memory(),
buf_desc
.addr()
.translate_gva(access_platform, buf_desc.len() as usize),
pkt.buf_size,
)
.ok_or(VsockError::GuestMemory)?,
);
Ok(pkt)
}
@ -207,33 +221,53 @@ impl VsockPacket {
return Err(VsockError::HdrDescTooSmall(head.len()));
}
// All RX descriptor chains should have a header and a data descriptor.
if !head.has_next() {
return Err(VsockError::BufDescMissing);
}
let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?;
let buf_size = buf_desc.len() as usize;
// Prior to Linux v6.3 there are two descriptors
if head.has_next() {
let buf_desc = desc_chain.next().ok_or(VsockError::BufDescMissing)?;
let buf_size = buf_desc.len() as usize;
Ok(Self {
hdr: get_host_address_range(
desc_chain.memory(),
head.addr()
.translate_gva(access_platform, head.len() as usize),
VSOCK_PKT_HDR_SIZE,
)
.ok_or(VsockError::GuestMemory)?,
buf: Some(
get_host_address_range(
Ok(Self {
hdr: get_host_address_range(
desc_chain.memory(),
buf_desc
.addr()
.translate_gva(access_platform, buf_desc.len() as usize),
buf_size,
head.addr()
.translate_gva(access_platform, VSOCK_PKT_HDR_SIZE),
VSOCK_PKT_HDR_SIZE,
)
.ok_or(VsockError::GuestMemory)?,
),
buf_size,
})
buf: Some(
get_host_address_range(
desc_chain.memory(),
buf_desc.addr().translate_gva(access_platform, buf_size),
buf_size,
)
.ok_or(VsockError::GuestMemory)?,
),
buf_size,
})
} else {
let buf_size: usize = head.len() as usize - VSOCK_PKT_HDR_SIZE;
Ok(Self {
hdr: get_host_address_range(
desc_chain.memory(),
head.addr()
.translate_gva(access_platform, VSOCK_PKT_HDR_SIZE),
VSOCK_PKT_HDR_SIZE,
)
.ok_or(VsockError::GuestMemory)?,
buf: Some(
get_host_address_range(
desc_chain.memory(),
head.addr()
.checked_add(VSOCK_PKT_HDR_SIZE as u64)
.unwrap()
.translate_gva(access_platform, buf_size),
buf_size,
)
.ok_or(VsockError::GuestMemory)?,
),
buf_size,
})
}
}
/// Provides in-place, byte-slice, access to the vsock packet header.
@ -503,16 +537,6 @@ mod tests {
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::InvalidPktLen(_));
}
// Test case:
// - packet header advertises some data length; and
// - the data descriptor is missing.
{
create_context!(test_ctx, handler_ctx);
set_pkt_len(1024, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem);
handler_ctx.guest_txvq.dtable[0].flags.set(0);
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::BufDescMissing);
}
// Test case: error on write-only buf descriptor.
{
create_context!(test_ctx, handler_ctx);
@ -568,15 +592,6 @@ mod tests {
.set(VSOCK_PKT_HDR_SIZE as u32 - 1);
expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::HdrDescTooSmall(_));
}
// Test case: RX descriptor chain is missing the packet buffer descriptor.
{
create_context!(test_ctx, handler_ctx);
handler_ctx.guest_rxvq.dtable[0]
.flags
.set(VRING_DESC_F_WRITE.try_into().unwrap());
expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::BufDescMissing);
}
}
#[test]