block: qcow: Add refcount overflow protection
Reject refcount values exceeding the maximum for the image's refcount_order. This prevents silent truncation when storing refcounts in narrow widths (e.g., 1-bit max is 1, 4-bit max is 15, etc.). Returns RefcountOverflow error with the attempted value, maximum, and bit width. Propagates as EINVAL to the guest. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
parent
f8008191d2
commit
6e7f888f5d
2 changed files with 126 additions and 12 deletions
|
|
@ -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::<u64>() 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<Vec<u64>> {
|
||||
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::<u64>() 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
|
@ -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<RefCount> {
|
||||
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<u64>)>,
|
||||
) -> Result<Option<u64>> {
|
||||
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)?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue