Skip to content

Commit 534fa58

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 3ededbc commit 534fa58

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
@@ -300,9 +300,10 @@ impl Block {
300300
e.status()
301301
}
302302
};
303-
// We use unwrap because the request parsing process already checked that the
304-
// status_addr was valid.
305-
mem.write_obj(status, request.status_addr).unwrap();
303+
304+
if let Err(e) = mem.write_obj(status, request.status_addr) {
305+
error!("Failed to write virtio block status: {:?}", e)
306+
}
306307
}
307308
Err(e) => {
308309
error!("Failed to parse available descriptor chain: {:?}", e);
@@ -679,6 +680,39 @@ pub(crate) mod tests {
679680
assert_eq!(vq.used.ring[0].get().len, 0);
680681
}
681682

683+
#[test]
684+
fn test_addr_out_of_bounds() {
685+
let mut block = default_block();
686+
// Default mem size is 0x10000
687+
let mem = default_mem();
688+
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
689+
block.set_queue(0, vq.create_queue());
690+
block.activate(mem.clone()).unwrap();
691+
initialize_virtqueue(&vq);
692+
vq.dtable[1].set(0xff00, 0x1000, VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE, 2);
693+
694+
let request_type_addr = GuestAddress(vq.dtable[0].addr.get());
695+
696+
// Mark the next available descriptor.
697+
vq.avail.idx.set(1);
698+
// Read.
699+
{
700+
vq.used.idx.set(0);
701+
702+
mem.write_obj::<u32>(VIRTIO_BLK_T_IN, request_type_addr)
703+
.unwrap();
704+
vq.dtable[1]
705+
.flags
706+
.set(VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE);
707+
708+
check_metric_after_block!(
709+
&METRICS.block.invalid_reqs_count,
710+
1,
711+
invoke_handler_for_queue_event(&mut block)
712+
);
713+
}
714+
}
715+
682716
#[test]
683717
fn test_request_execute_failures() {
684718
let mut block = default_block();
@@ -766,6 +800,39 @@ pub(crate) mod tests {
766800
VIRTIO_BLK_S_UNSUPP
767801
);
768802
}
803+
#[test]
804+
fn test_end_of_region() {
805+
let mut block = default_block();
806+
let mem = default_mem();
807+
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
808+
block.set_queue(0, vq.create_queue());
809+
block.activate(mem.clone()).unwrap();
810+
initialize_virtqueue(&vq);
811+
vq.dtable[1].set(0xf000, 0x1000, VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE, 2);
812+
813+
let request_type_addr = GuestAddress(vq.dtable[0].addr.get());
814+
let status_addr = GuestAddress(vq.dtable[2].addr.get());
815+
816+
vq.used.idx.set(0);
817+
818+
mem.write_obj::<u32>(VIRTIO_BLK_T_IN, request_type_addr)
819+
.unwrap();
820+
vq.dtable[1]
821+
.flags
822+
.set(VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE);
823+
824+
check_metric_after_block!(
825+
&METRICS.block.read_count,
826+
1,
827+
invoke_handler_for_queue_event(&mut block)
828+
);
829+
830+
assert_eq!(vq.used.idx.get(), 1);
831+
assert_eq!(vq.used.ring[0].get().id, 0);
832+
// Added status byte length.
833+
assert_eq!(vq.used.ring[0].get().len, vq.dtable[1].len.get() + 1);
834+
assert_eq!(mem.read_obj::<u32>(status_addr).unwrap(), VIRTIO_BLK_S_OK);
835+
}
769836

770837
#[test]
771838
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::{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::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)
@@ -255,7 +239,7 @@ impl Request {
255239
mod tests {
256240
use super::*;
257241
use crate::virtio::queue::tests::*;
258-
use vm_memory::{Address, GuestAddress};
242+
use vm_memory::{Address, GuestAddress, GuestMemory};
259243

260244
#[test]
261245
fn test_read_request_header() {
@@ -438,27 +422,25 @@ mod tests {
438422
// Fix status descriptor length.
439423
vq.dtable[status_descriptor].len.set(0x1000);
440424
// Invalid guest address for the status descriptor.
425+
// Parsing will still succeed as the operation that
426+
// will fail happens when executing the request.
441427
vq.dtable[status_descriptor]
442428
.addr
443429
.set(m.last_addr().raw_value());
444-
assert!(match Request::parse(&q.pop(m).unwrap(), m) {
445-
Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(_))) => true,
446-
_ => false,
447-
});
430+
assert!(Request::parse(&q.pop(m).unwrap(), m).is_ok());
448431
}
449432

450433
{
451434
let mut q = vq.create_queue();
452435
// Restore status descriptor.
453436
vq.dtable[status_descriptor].set(0x3000, 0x1000, VIRTQ_DESC_F_WRITE, 0);
454437
// Invalid guest address for the data descriptor.
438+
// Parsing will still succeed as the operation that
439+
// will fail happens when executing the request.
455440
vq.dtable[data_descriptor]
456441
.addr
457442
.set(m.last_addr().raw_value());
458-
assert!(match Request::parse(&q.pop(m).unwrap(), m) {
459-
Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(_))) => true,
460-
_ => false,
461-
});
443+
assert!(Request::parse(&q.pop(m).unwrap(), m).is_ok());
462444
}
463445

464446
{

0 commit comments

Comments
 (0)