Skip to content

Commit cdc628f

Browse files
alexandruagacatangiu
authored andcommitted
Fixed a logical error in bounds checking performed on vsock virtio descriptors. (CVE-2019-18960) Signed-off-by: Alexandru Agache <[email protected]>
1 parent 847b155 commit cdc628f

File tree

3 files changed

+107
-16
lines changed

3 files changed

+107
-16
lines changed

devices/src/virtio/queue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl<'a> DescriptorChain<'a> {
108108
fn is_valid(&self) -> bool {
109109
!(self
110110
.mem
111-
.checked_offset(self.addr, self.len as usize)
111+
.checked_range_offset(self.addr, self.len as usize)
112112
.is_none()
113113
|| (self.has_next() && self.next >= self.queue_size))
114114
}

devices/src/virtio/vsock/packet.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ impl VsockPacket {
118118
let mut pkt = Self {
119119
hdr: head
120120
.mem
121-
.get_host_address(head.addr)
121+
.get_host_address(head.addr, VSOCK_PKT_HDR_SIZE)
122122
.map_err(VsockError::GuestMemory)? as *mut u8,
123123
buf: None,
124124
buf_size: 0,
@@ -153,7 +153,7 @@ impl VsockPacket {
153153
pkt.buf = Some(
154154
buf_desc
155155
.mem
156-
.get_host_address(buf_desc.addr)
156+
.get_host_address(buf_desc.addr, pkt.buf_size)
157157
.map_err(VsockError::GuestMemory)? as *mut u8,
158158
);
159159

@@ -182,19 +182,20 @@ impl VsockPacket {
182182
return Err(VsockError::BufDescMissing);
183183
}
184184
let buf_desc = head.next_descriptor().ok_or(VsockError::BufDescMissing)?;
185+
let buf_size = buf_desc.len as usize;
185186

186187
Ok(Self {
187188
hdr: head
188189
.mem
189-
.get_host_address(head.addr)
190+
.get_host_address(head.addr, VSOCK_PKT_HDR_SIZE)
190191
.map_err(VsockError::GuestMemory)? as *mut u8,
191192
buf: Some(
192193
buf_desc
193194
.mem
194-
.get_host_address(buf_desc.addr)
195+
.get_host_address(buf_desc.addr, buf_size)
195196
.map_err(VsockError::GuestMemory)? as *mut u8,
196197
),
197-
buf_size: buf_desc.len as usize,
198+
buf_size,
198199
})
199200
}
200201

@@ -378,7 +379,9 @@ mod tests {
378379

379380
fn set_pkt_len(len: u32, guest_desc: &GuestQDesc, mem: &GuestMemory) {
380381
let hdr_gpa = guest_desc.addr.get() as usize;
381-
let hdr_ptr = mem.get_host_address(GuestAddress(hdr_gpa)).unwrap() as *mut u8;
382+
let hdr_ptr = mem
383+
.get_host_address(GuestAddress(hdr_gpa), VSOCK_PKT_HDR_SIZE)
384+
.unwrap() as *mut u8;
382385
let len_ptr = unsafe { hdr_ptr.add(HDROFF_LEN) };
383386

384387
LittleEndian::write_u32(unsafe { std::slice::from_raw_parts_mut(len_ptr, 4) }, len);

memory_model/src/guest_memory.rs

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ impl GuestMemory {
122122
false
123123
}
124124

125-
/// Returns the address plus the offset if it is in range.
125+
/// Returns the address plus the offset if the result falls within a valid memory region. The
126+
/// resulting address and base address may belong to different memory regions, and the base
127+
/// might not even exist in a valid region.
126128
pub fn checked_offset(&self, base: GuestAddress, offset: usize) -> Option<GuestAddress> {
127129
if let Some(addr) = base.checked_add(offset) {
128130
for region in self.regions.iter() {
@@ -134,6 +136,25 @@ impl GuestMemory {
134136
None
135137
}
136138

139+
/// Returns the address plus the offset if base and the result belong to the same memory
140+
/// region (Firecracker currently does not use adjacent memory regions, so distinct regions
141+
/// always have a gap in-between).
142+
pub fn checked_range_offset(&self, base: GuestAddress, offset: usize) -> Option<GuestAddress> {
143+
if let Some(addr) = base.checked_add(offset) {
144+
for region in self.regions.iter() {
145+
let region_end = region_end(region);
146+
if base >= region.guest_base
147+
&& base < region_end
148+
&& addr >= region.guest_base
149+
&& addr < region_end
150+
{
151+
return Some(addr);
152+
}
153+
}
154+
}
155+
None
156+
}
157+
137158
/// Returns the size of the memory region.
138159
pub fn num_regions(&self) -> usize {
139160
self.regions.len()
@@ -366,10 +387,14 @@ impl GuestMemory {
366387
/// Converts a GuestAddress into a pointer in the address space of this
367388
/// process. This should only be necessary for giving addresses to the
368389
/// kernel, as with vhost ioctls. Normal reads/writes to guest memory should
369-
/// be done through `write_from_memory`, `read_obj_from_addr`, etc.
390+
/// be done through `write_from_memory`, `read_obj_from_addr`, etc. This method
391+
/// also checks whether the provided GuestAddress and size define a valid range
392+
/// in the guest memory region, which is helpful to ensure the operation that
393+
/// uses the result does not access memory outside the guest memory mappings.
370394
///
371395
/// # Arguments
372396
/// * `guest_addr` - Guest address to convert.
397+
/// * `size` - The size of the range to validate starting at `guest_addr`.
373398
///
374399
/// # Examples
375400
///
@@ -378,13 +403,13 @@ impl GuestMemory {
378403
/// # fn test_host_addr() -> Result<(), ()> {
379404
/// let start_addr = GuestAddress(0x1000);
380405
/// let mut gm = GuestMemory::new(&vec![(start_addr, 0x500)]).map_err(|_| ())?;
381-
/// let addr = gm.get_host_address(GuestAddress(0x1200)).unwrap();
406+
/// let addr = gm.get_host_address(GuestAddress(0x1200), 1).unwrap();
382407
/// println!("Host address is {:p}", addr);
383408
/// Ok(())
384409
/// # }
385410
/// ```
386-
pub fn get_host_address(&self, guest_addr: GuestAddress) -> Result<*const u8> {
387-
self.do_in_region(guest_addr, 1, |mapping, offset| {
411+
pub fn get_host_address(&self, guest_addr: GuestAddress, size: usize) -> Result<*const u8> {
412+
self.do_in_region(guest_addr, size, |mapping, offset| {
388413
// This is safe; `do_in_region` already checks that offset is in
389414
// bounds.
390415
Ok(unsafe { mapping.as_ptr().add(offset) } as *const u8)
@@ -486,9 +511,48 @@ mod tests {
486511
let end_addr = GuestAddress(0xc00);
487512
assert!(!guest_mem.address_in_range(end_addr));
488513
assert_eq!(guest_mem.end_addr(), end_addr);
489-
assert!(guest_mem.checked_offset(start_addr1, 0x900).is_some());
514+
515+
// Begins and ends within first region.
516+
assert_eq!(
517+
guest_mem.checked_offset(start_addr1, 0x300),
518+
Some(GuestAddress(0x300))
519+
);
520+
assert_eq!(
521+
guest_mem.checked_range_offset(start_addr1, 0x300),
522+
Some(GuestAddress(0x300))
523+
);
524+
525+
// Begins in the first region, and ends in the second, crossing the gap.
526+
assert_eq!(
527+
guest_mem.checked_offset(start_addr1, 0x900),
528+
Some(GuestAddress(0x900))
529+
);
530+
assert!(guest_mem.checked_range_offset(start_addr1, 0x900).is_none());
531+
532+
// Goes past the end of the first region, into the gap.
490533
assert!(guest_mem.checked_offset(start_addr1, 0x700).is_none());
534+
assert!(guest_mem.checked_range_offset(start_addr1, 0x700).is_none());
535+
536+
// Starts in the second region, and goes past the end of it.
491537
assert!(guest_mem.checked_offset(start_addr2, 0xc00).is_none());
538+
assert!(guest_mem.checked_range_offset(start_addr2, 0xc00).is_none());
539+
540+
// Exists entirely within the gap.
541+
assert!(guest_mem
542+
.checked_offset(GuestAddress(0x500), 0x100)
543+
.is_none());
544+
assert!(guest_mem
545+
.checked_range_offset(GuestAddress(0x500), 0x100)
546+
.is_none());
547+
548+
// Starts inside the gap, crosses into the second region.
549+
assert_eq!(
550+
guest_mem.checked_offset(GuestAddress(0x500), 0x400),
551+
Some(GuestAddress(0x900))
552+
);
553+
assert!(guest_mem
554+
.checked_range_offset(GuestAddress(0x500), 0x400)
555+
.is_none());
492556
}
493557

494558
#[test]
@@ -614,17 +678,41 @@ mod tests {
614678
let start_addr2 = GuestAddress(0x100);
615679
let mem = GuestMemory::new(&[(start_addr1, 0x100), (start_addr2, 0x400)]).unwrap();
616680

681+
assert!(mem.get_host_address(start_addr1, 0x100).is_ok());
682+
// Error because we go past the end of the first region.
683+
assert!(mem.get_host_address(start_addr1, 0x101).is_err());
684+
685+
assert!(mem
686+
.get_host_address(start_addr2.checked_add(0x100).unwrap(), 0x300)
687+
.is_ok());
688+
689+
// Error because we go past the end of the second region.
690+
assert!(mem
691+
.get_host_address(start_addr2.checked_add(0x100).unwrap(), 0x301)
692+
.is_err());
693+
694+
// Error because we start in the gap between regions.
695+
assert!(mem
696+
.get_host_address(start_addr2.checked_sub(1).unwrap(), 0x100)
697+
.is_err());
698+
699+
// Error because we start in the first region, but when also adding the size we end
700+
// up in the second region.
701+
assert!(mem
702+
.get_host_address(start_addr1, start_addr2.0 + 1)
703+
.is_err());
704+
617705
// Verify the host addresses match what we expect from the mappings.
618706
let addr1_base = get_mapping(&mem, start_addr1).unwrap();
619707
let addr2_base = get_mapping(&mem, start_addr2).unwrap();
620-
let host_addr1 = mem.get_host_address(start_addr1).unwrap();
621-
let host_addr2 = mem.get_host_address(start_addr2).unwrap();
708+
let host_addr1 = mem.get_host_address(start_addr1, 1).unwrap();
709+
let host_addr2 = mem.get_host_address(start_addr2, 1).unwrap();
622710
assert_eq!(host_addr1, addr1_base);
623711
assert_eq!(host_addr2, addr2_base);
624712

625713
// Check that a bad address returns an error.
626714
let bad_addr = GuestAddress(0x12_3456);
627-
assert!(mem.get_host_address(bad_addr).is_err());
715+
assert!(mem.get_host_address(bad_addr, 1).is_err());
628716
}
629717

630718
#[test]

0 commit comments

Comments
 (0)