diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 77824039b..79581b52e 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -97,6 +97,8 @@ pub enum Error { ReadingRefCounts(#[source] io::Error), #[error("Failed to rebuild ref counts")] RebuildingRefCounts(#[source] io::Error), + #[error("Refcount overflow")] + RefcountOverflow(#[source] refcount::Error), #[error("Refcount table offset past file end")] RefcountTableOffEnd, #[error("Too many clusters specified for refcount")] @@ -932,6 +934,7 @@ impl QcowFile { refcount_clusters, refcount_block_entries, cluster_size, + refcount_bits, ) .map_err(Error::ReadingRefCounts)?; @@ -1119,18 +1122,36 @@ impl QcowFile { /// Rebuild the reference count tables. fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> Result<()> { - fn add_ref(refcounts: &mut [u64], cluster_size: u64, cluster_address: u64) -> Result<()> { + fn add_ref( + refcounts: &mut [u64], + cluster_size: u64, + cluster_address: u64, + max_refcount: u64, + refcount_bits: u64, + ) -> Result<()> { let idx = (cluster_address / cluster_size) as usize; if idx >= refcounts.len() { return Err(Error::InvalidClusterIndex); } + if refcounts[idx] >= max_refcount { + return Err(Error::RefcountOverflow(refcount::Error::RefcountOverflow { + value: refcounts[idx] + 1, + max: max_refcount, + refcount_bits, + })); + } refcounts[idx] += 1; Ok(()) } // Add a reference to the first cluster (header plus extensions). - fn set_header_refcount(refcounts: &mut [u64], cluster_size: u64) -> Result<()> { - add_ref(refcounts, cluster_size, 0) + fn set_header_refcount( + refcounts: &mut [u64], + cluster_size: u64, + max_refcount: u64, + refcount_bits: u64, + ) -> Result<()> { + add_ref(refcounts, cluster_size, 0, max_refcount, refcount_bits) } // Add references to the L1 table clusters. @@ -1138,12 +1159,20 @@ impl QcowFile { refcounts: &mut [u64], header: &QcowHeader, cluster_size: u64, + max_refcount: u64, + refcount_bits: u64, ) -> Result<()> { let entries_per_cluster = cluster_size / size_of::() as u64; let l1_clusters = div_round_up_u64(u64::from(header.l1_size), entries_per_cluster); let l1_table_offset = header.l1_table_offset; for i in 0..l1_clusters { - add_ref(refcounts, cluster_size, l1_table_offset + i * cluster_size)?; + add_ref( + refcounts, + cluster_size, + l1_table_offset + i * cluster_size, + max_refcount, + refcount_bits, + )?; } Ok(()) } @@ -1154,6 +1183,8 @@ impl QcowFile { header: &QcowHeader, cluster_size: u64, raw_file: &mut QcowRawFile, + max_refcount: u64, + refcount_bits: u64, ) -> Result<()> { let l1_table = raw_file .read_pointer_table( @@ -1166,7 +1197,13 @@ impl QcowFile { let l2_addr_disk = *l1_table.get(l1_index).ok_or(Error::InvalidIndex)?; if l2_addr_disk != 0 { // Add a reference to the L2 table cluster itself. - add_ref(refcounts, cluster_size, l2_addr_disk)?; + add_ref( + refcounts, + cluster_size, + l2_addr_disk, + max_refcount, + refcount_bits, + )?; // Read the L2 table and find all referenced data clusters. let l2_table = raw_file @@ -1178,7 +1215,13 @@ impl QcowFile { .map_err(Error::ReadingPointers)?; for data_cluster_addr in l2_table { if data_cluster_addr != 0 { - add_ref(refcounts, cluster_size, data_cluster_addr)?; + add_ref( + refcounts, + cluster_size, + data_cluster_addr, + max_refcount, + refcount_bits, + )?; } } } @@ -1192,6 +1235,8 @@ impl QcowFile { refcounts: &mut [u64], header: &QcowHeader, cluster_size: u64, + max_refcount: u64, + refcount_bits: u64, ) -> Result<()> { let refcount_table_offset = header.refcount_table_offset; for i in 0..u64::from(header.refcount_table_clusters) { @@ -1199,6 +1244,8 @@ impl QcowFile { refcounts, cluster_size, refcount_table_offset + i * cluster_size, + max_refcount, + refcount_bits, )?; } Ok(()) @@ -1211,6 +1258,8 @@ impl QcowFile { refcounts: &mut [u64], cluster_size: u64, refblock_clusters: u64, + max_refcount: u64, + refcount_bits: u64, ) -> Result> { let mut ref_table = vec![0; refblock_clusters as usize]; let mut first_free_cluster: u64 = 0; @@ -1226,7 +1275,13 @@ impl QcowFile { } *refblock_addr = first_free_cluster * cluster_size; - add_ref(refcounts, cluster_size, *refblock_addr)?; + add_ref( + refcounts, + cluster_size, + *refblock_addr, + max_refcount, + refcount_bits, + )?; first_free_cluster += 1; } @@ -1293,6 +1348,11 @@ impl QcowFile { .len(); let refcount_bits = 1u64 << header.refcount_order; + let max_refcount = if refcount_bits == 64 { + u64::MAX + } else { + (1u64 << refcount_bits) - 1 + }; let refcount_block_entries = cluster_size * 8 / refcount_bits; let pointers_per_cluster = cluster_size / size_of::() as u64; let data_clusters = div_round_up_u64(header.size, cluster_size); @@ -1325,13 +1385,38 @@ impl QcowFile { let mut refcounts = vec![0; max_valid_cluster_index as usize]; // Find all references clusters and rebuild refcounts. - set_header_refcount(&mut refcounts, cluster_size)?; - set_l1_refcounts(&mut refcounts, &header, cluster_size)?; - set_data_refcounts(&mut refcounts, &header, cluster_size, raw_file)?; - set_refcount_table_refcounts(&mut refcounts, &header, cluster_size)?; + set_header_refcount(&mut refcounts, cluster_size, max_refcount, refcount_bits)?; + set_l1_refcounts( + &mut refcounts, + &header, + cluster_size, + max_refcount, + refcount_bits, + )?; + set_data_refcounts( + &mut refcounts, + &header, + cluster_size, + raw_file, + max_refcount, + refcount_bits, + )?; + set_refcount_table_refcounts( + &mut refcounts, + &header, + cluster_size, + max_refcount, + refcount_bits, + )?; // Allocate clusters to store the new reference count blocks. - let ref_table = alloc_refblocks(&mut refcounts, cluster_size, refblock_clusters)?; + let ref_table = alloc_refblocks( + &mut refcounts, + cluster_size, + refblock_clusters, + max_refcount, + refcount_bits, + )?; // Write updated reference counts and point the reftable at them. write_refblocks( @@ -1856,6 +1941,9 @@ impl QcowFile { Err(refcount::Error::ReadingRefCounts(e)) => { return Err(e); } + Err(refcount::Error::RefcountOverflow { .. }) => { + return Err(std::io::Error::from_raw_os_error(EINVAL)); + } } } diff --git a/block/src/qcow/refcount.rs b/block/src/qcow/refcount.rs index 5009fef34..8fa3d5bfe 100644 --- a/block/src/qcow/refcount.rs +++ b/block/src/qcow/refcount.rs @@ -29,6 +29,13 @@ pub enum Error { /// `ReadingRefCounts` - Error reading the file into the refcount cache. #[error("Failed to read the file into the refcount cache")] ReadingRefCounts(#[source] io::Error), + /// `RefcountOverflow` - Refcount value exceeds maximum for the refcount width. + #[error("Refcount value {value} exceeds {refcount_bits}-bit max ({max})")] + RefcountOverflow { + value: u64, + max: u64, + refcount_bits: u64, + }, } pub type Result = std::result::Result; @@ -42,12 +49,15 @@ pub struct RefCount { refcount_block_entries: u64, // number of refcounts in a cluster. cluster_size: u64, max_valid_cluster_offset: u64, + max_refcount: u64, // maximum refcount value for this image's refcount_order + refcount_bits: u64, // number of bits per refcount entry } impl RefCount { /// Creates a `RefCount` from `file`, reading the refcount table from `refcount_table_offset`. /// `refcount_table_entries` specifies the number of refcount blocks used by this image. /// `refcount_block_entries` indicates the number of refcounts in each refcount block. + /// `refcount_bits` is the number of bits per refcount (1, 2, 4, 8, 16, 32, or 64). /// Each refcount table entry points to a refcount block. pub fn new( raw_file: &mut QcowRawFile, @@ -55,6 +65,7 @@ impl RefCount { refcount_table_entries: u64, refcount_block_entries: u64, cluster_size: u64, + refcount_bits: u64, ) -> io::Result { let ref_table = VecCache::from_vec(raw_file.read_pointer_table( refcount_table_offset, @@ -63,6 +74,11 @@ impl RefCount { )?); let max_valid_cluster_index = (ref_table.len() as u64) * refcount_block_entries - 1; let max_valid_cluster_offset = max_valid_cluster_index * cluster_size; + let max_refcount = if refcount_bits >= 64 { + u64::MAX + } else { + (1u64 << refcount_bits) - 1 + }; Ok(RefCount { ref_table, refcount_table_offset, @@ -70,6 +86,8 @@ impl RefCount { refcount_block_entries, cluster_size, max_valid_cluster_offset, + max_refcount, + refcount_bits, }) } @@ -95,6 +113,14 @@ impl RefCount { refcount: u64, mut new_cluster: Option<(u64, VecCache)>, ) -> Result> { + if refcount > self.max_refcount { + return Err(Error::RefcountOverflow { + value: refcount, + max: self.max_refcount, + refcount_bits: self.refcount_bits, + }); + } + let (table_index, block_index) = self.get_refcount_index(cluster_address); let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;