Skip to content

Commit f3cce68

Browse files
georgepisaltualindima
authored andcommitted
block: fix off-by-one error in addr validation
The block device validated addresses from virtio descriptors before trying to execute requests. In this validation, the `checked_offset` function of guest memory was used to determine if the slice defined by the sum of the address and length of the virtio descriptor was within the guest memory bounds. However, this sum is greater than the last valid offset, `addr + len - 1`, by 1. This made the block device mark descriptors with slices at the very end of a region as invalid, making the last byte of a memory region unusable by the block device. This check was performed as the previous guest memory model did not have it built in. This commit removes the checks as the current guest memory model validates the addresses before operations. Also added regression tests for this case. Signed-off-by: George Pisaltu <[email protected]>
1 parent 2896480 commit f3cce68

File tree

2 files changed

+78
-29
lines changed

2 files changed

+78
-29
lines changed

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

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,10 @@ impl Block {
301301
e.status()
302302
}
303303
};
304-
// We use unwrap because the request parsing process already checked that the
305-
// status_addr was valid.
306-
mem.write_obj(status, request.status_addr).unwrap();
304+
305+
if let Err(e) = mem.write_obj(status, request.status_addr) {
306+
error!("Failed to write virtio block status: {:?}", e)
307+
}
307308
}
308309
Err(e) => {
309310
error!("Failed to parse available descriptor chain: {:?}", e);
@@ -603,6 +604,39 @@ pub(crate) mod tests {
603604
assert_eq!(vq.used.ring[0].get().len, 0);
604605
}
605606

607+
#[test]
608+
fn test_addr_out_of_bounds() {
609+
let mut block = default_block();
610+
// Default mem size is 0x10000
611+
let mem = default_mem();
612+
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
613+
set_queue(&mut block, 0, vq.create_queue());
614+
block.activate(mem.clone()).unwrap();
615+
initialize_virtqueue(&vq);
616+
vq.dtable[1].set(0xff00, 0x1000, VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE, 2);
617+
618+
let request_type_addr = GuestAddress(vq.dtable[0].addr.get());
619+
620+
// Mark the next available descriptor.
621+
vq.avail.idx.set(1);
622+
// Read.
623+
{
624+
vq.used.idx.set(0);
625+
626+
mem.write_obj::<u32>(VIRTIO_BLK_T_IN, request_type_addr)
627+
.unwrap();
628+
vq.dtable[1]
629+
.flags
630+
.set(VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE);
631+
632+
check_metric_after_block!(
633+
&METRICS.block.invalid_reqs_count,
634+
1,
635+
invoke_handler_for_queue_event(&mut block)
636+
);
637+
}
638+
}
639+
606640
#[test]
607641
fn test_request_execute_failures() {
608642
let mut block = default_block();
@@ -690,6 +724,39 @@ pub(crate) mod tests {
690724
VIRTIO_BLK_S_UNSUPP
691725
);
692726
}
727+
#[test]
728+
fn test_end_of_region() {
729+
let mut block = default_block();
730+
let mem = default_mem();
731+
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
732+
set_queue(&mut block, 0, vq.create_queue());
733+
block.activate(mem.clone()).unwrap();
734+
initialize_virtqueue(&vq);
735+
vq.dtable[1].set(0xf000, 0x1000, VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE, 2);
736+
737+
let request_type_addr = GuestAddress(vq.dtable[0].addr.get());
738+
let status_addr = GuestAddress(vq.dtable[2].addr.get());
739+
740+
vq.used.idx.set(0);
741+
742+
mem.write_obj::<u32>(VIRTIO_BLK_T_IN, request_type_addr)
743+
.unwrap();
744+
vq.dtable[1]
745+
.flags
746+
.set(VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE);
747+
748+
check_metric_after_block!(
749+
&METRICS.block.read_count,
750+
1,
751+
invoke_handler_for_queue_event(&mut block)
752+
);
753+
754+
assert_eq!(vq.used.idx.get(), 1);
755+
assert_eq!(vq.used.ring[0].get().id, 0);
756+
// Added status byte length.
757+
assert_eq!(vq.used.ring[0].get().len, vq.dtable[1].len.get() + 1);
758+
assert_eq!(mem.read_obj::<u32>(status_addr).unwrap(), VIRTIO_BLK_S_OK);
759+
}
693760

694761
#[test]
695762
fn test_read_write() {

src/devices/src/virtio/block/request.rs

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77

88
use std::convert::From;
99
use std::io::{self, Seek, SeekFrom, Write};
10-
use std::mem;
1110
use std::result;
1211

1312
use logger::{IncMetric, METRICS};
1413
use virtio_gen::virtio_blk::*;
15-
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
14+
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap};
1615

1716
use super::super::DescriptorChain;
1817
use super::device::DiskProperties;
@@ -159,13 +158,6 @@ impl Request {
159158
return Err(Error::UnexpectedReadOnlyDescriptor);
160159
}
161160

162-
// Check that the address of the data descriptor is valid in guest memory.
163-
let _ = mem
164-
.checked_offset(data_desc.addr, data_desc.len as usize)
165-
.ok_or(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
166-
data_desc.addr,
167-
)))?;
168-
169161
req.data_addr = data_desc.addr;
170162
req.data_len = data_desc.len;
171163
}
@@ -179,14 +171,6 @@ impl Request {
179171
return Err(Error::DescriptorLengthTooSmall);
180172
}
181173

182-
// Check that the address of the status descriptor is valid in guest memory.
183-
// We will write an u32 status here after executing the request.
184-
let _ = mem
185-
.checked_offset(status_desc.addr, mem::size_of::<u32>())
186-
.ok_or(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
187-
status_desc.addr,
188-
)))?;
189-
190174
req.status_addr = status_desc.addr;
191175

192176
Ok(req)
@@ -257,7 +241,7 @@ mod tests {
257241

258242
use crate::virtio::queue::tests::*;
259243
use crate::virtio::test_utils::VirtQueue;
260-
use vm_memory::{Address, GuestAddress};
244+
use vm_memory::{Address, GuestAddress, GuestMemory};
261245

262246
#[test]
263247
fn test_read_request_header() {
@@ -440,27 +424,25 @@ mod tests {
440424
// Fix status descriptor length.
441425
vq.dtable[status_descriptor].len.set(0x1000);
442426
// Invalid guest address for the status descriptor.
427+
// Parsing will still succeed as the operation that
428+
// will fail happens when executing the request.
443429
vq.dtable[status_descriptor]
444430
.addr
445431
.set(m.last_addr().raw_value());
446-
assert!(matches!(
447-
Request::parse(&q.pop(m).unwrap(), m),
448-
Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(_)))
449-
));
432+
assert!(Request::parse(&q.pop(m).unwrap(), m).is_ok());
450433
}
451434

452435
{
453436
let mut q = vq.create_queue();
454437
// Restore status descriptor.
455438
vq.dtable[status_descriptor].set(0x3000, 0x1000, VIRTQ_DESC_F_WRITE, 0);
456439
// Invalid guest address for the data descriptor.
440+
// Parsing will still succeed as the operation that
441+
// will fail happens when executing the request.
457442
vq.dtable[data_descriptor]
458443
.addr
459444
.set(m.last_addr().raw_value());
460-
assert!(matches!(
461-
Request::parse(&q.pop(m).unwrap(), m),
462-
Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(_)))
463-
));
445+
assert!(Request::parse(&q.pop(m).unwrap(), m).is_ok());
464446
}
465447

466448
{

0 commit comments

Comments
 (0)