Skip to content

Commit 78fd4b6

Browse files
georgepisaltuluminitavoicu
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 3d5def3 commit 78fd4b6

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
@@ -260,9 +260,10 @@ impl Block {
260260
e.status()
261261
}
262262
};
263-
// We use unwrap because the request parsing process already checked that the
264-
// status_addr was valid.
265-
mem.write_obj(status, request.status_addr).unwrap();
263+
264+
if let Err(e) = mem.write_obj(status, request.status_addr) {
265+
error!("Failed to write virtio block status: {:?}", e)
266+
}
266267
}
267268
Err(e) => {
268269
error!("Failed to parse available descriptor chain: {:?}", e);
@@ -614,6 +615,39 @@ pub(crate) mod tests {
614615
assert_eq!(vq.used.ring[0].get().len, 0);
615616
}
616617

618+
#[test]
619+
fn test_addr_out_of_bounds() {
620+
let mut block = default_block();
621+
// Default mem size is 0x10000
622+
let mem = default_mem();
623+
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
624+
block.set_queue(0, vq.create_queue());
625+
block.activate(mem.clone()).unwrap();
626+
initialize_virtqueue(&vq);
627+
vq.dtable[1].set(0xff00, 0x1000, VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE, 2);
628+
629+
let request_type_addr = GuestAddress(vq.dtable[0].addr.get());
630+
631+
// Mark the next available descriptor.
632+
vq.avail.idx.set(1);
633+
// Read.
634+
{
635+
vq.used.idx.set(0);
636+
637+
mem.write_obj::<u32>(VIRTIO_BLK_T_IN, request_type_addr)
638+
.unwrap();
639+
vq.dtable[1]
640+
.flags
641+
.set(VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE);
642+
643+
check_metric_after_block!(
644+
&METRICS.block.invalid_reqs_count,
645+
1,
646+
invoke_handler_for_queue_event(&mut block)
647+
);
648+
}
649+
}
650+
617651
#[test]
618652
fn test_request_execute_failures() {
619653
let mut block = default_block();
@@ -701,6 +735,39 @@ pub(crate) mod tests {
701735
VIRTIO_BLK_S_UNSUPP
702736
);
703737
}
738+
#[test]
739+
fn test_end_of_region() {
740+
let mut block = default_block();
741+
let mem = default_mem();
742+
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
743+
block.set_queue(0, vq.create_queue());
744+
block.activate(mem.clone()).unwrap();
745+
initialize_virtqueue(&vq);
746+
vq.dtable[1].set(0xf000, 0x1000, VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE, 2);
747+
748+
let request_type_addr = GuestAddress(vq.dtable[0].addr.get());
749+
let status_addr = GuestAddress(vq.dtable[2].addr.get());
750+
751+
vq.used.idx.set(0);
752+
753+
mem.write_obj::<u32>(VIRTIO_BLK_T_IN, request_type_addr)
754+
.unwrap();
755+
vq.dtable[1]
756+
.flags
757+
.set(VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE);
758+
759+
check_metric_after_block!(
760+
&METRICS.block.read_count,
761+
1,
762+
invoke_handler_for_queue_event(&mut block)
763+
);
764+
765+
assert_eq!(vq.used.idx.get(), 1);
766+
assert_eq!(vq.used.ring[0].get().id, 0);
767+
// Added status byte length.
768+
assert_eq!(vq.used.ring[0].get().len, vq.dtable[1].len.get() + 1);
769+
assert_eq!(mem.read_obj::<u32>(status_addr).unwrap(), VIRTIO_BLK_S_OK);
770+
}
704771

705772
#[test]
706773
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, Read, Seek, SeekFrom, Write};
10-
use std::mem;
1110
use std::result;
1211

1312
use logger::{Metric, 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::{Error, SECTOR_SHIFT, SECTOR_SIZE};
@@ -158,13 +157,6 @@ impl Request {
158157
return Err(Error::UnexpectedReadOnlyDescriptor);
159158
}
160159

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

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

191175
Ok(req)
@@ -253,7 +237,7 @@ impl Request {
253237
mod tests {
254238
use super::*;
255239
use crate::virtio::queue::tests::*;
256-
use vm_memory::{Address, GuestAddress};
240+
use vm_memory::{Address, GuestAddress, GuestMemory};
257241

258242
#[test]
259243
fn test_read_request_header() {
@@ -436,27 +420,25 @@ mod tests {
436420
// Fix status descriptor length.
437421
vq.dtable[status_descriptor].len.set(0x1000);
438422
// Invalid guest address for the status descriptor.
423+
// Parsing will still succeed as the operation that
424+
// will fail happens when executing the request.
439425
vq.dtable[status_descriptor]
440426
.addr
441427
.set(m.last_addr().raw_value());
442-
assert!(match Request::parse(&q.pop(m).unwrap(), m) {
443-
Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(_))) => true,
444-
_ => false,
445-
});
428+
assert!(Request::parse(&q.pop(m).unwrap(), m).is_ok());
446429
}
447430

448431
{
449432
let mut q = vq.create_queue();
450433
// Restore status descriptor.
451434
vq.dtable[status_descriptor].set(0x3000, 0x1000, VIRTQ_DESC_F_WRITE, 0);
452435
// Invalid guest address for the data descriptor.
436+
// Parsing will still succeed as the operation that
437+
// will fail happens when executing the request.
453438
vq.dtable[data_descriptor]
454439
.addr
455440
.set(m.last_addr().raw_value());
456-
assert!(match Request::parse(&q.pop(m).unwrap(), m) {
457-
Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(_))) => true,
458-
_ => false,
459-
});
441+
assert!(Request::parse(&q.pop(m).unwrap(), m).is_ok());
460442
}
461443

462444
{

0 commit comments

Comments
 (0)