From 4a2a9390be8ecb3b873a746a1226648edf8036c1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 29 May 2026 18:24:56 +0200 Subject: [PATCH] block: qcow: Reject backing file name outside first cluster The qcow2 spec requires the backing file name string to live entirely within the first cluster, between the end of the header extension area and the cluster boundary. The parser previously only validated the 1023 byte cap on the name length and accepted any backing_file_offset, so a corrupt or malicious image could place the name string anywhere in the file. Add the cluster bound check in QcowHeader::new and report it via a new BackingFileOutsideFirstCluster error. Fixes: #8261 Signed-off-by: Anatol Belski --- block/src/formats/qcow/internal/header.rs | 12 ++++++++++++ block/src/formats/qcow/internal/mod.rs | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/block/src/formats/qcow/internal/header.rs b/block/src/formats/qcow/internal/header.rs index 22a5492b1..030815438 100644 --- a/block/src/formats/qcow/internal/header.rs +++ b/block/src/formats/qcow/internal/header.rs @@ -332,6 +332,18 @@ impl QcowHeader { return Err(Error::BackingFileTooLong(header.backing_file_size as usize)); } if header.backing_file_offset != 0 { + let cluster_size = 1u64 + .checked_shl(header.cluster_bits) + .ok_or(Error::InvalidClusterSize)?; + if header.backing_file_offset >= cluster_size + || header.backing_file_offset + u64::from(header.backing_file_size) > cluster_size + { + return Err(Error::BackingFileOutsideFirstCluster( + header.backing_file_offset, + header.backing_file_size, + cluster_size, + )); + } f.seek(SeekFrom::Start(header.backing_file_offset)) .map_err(Error::ReadingHeader)?; let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize]; diff --git a/block/src/formats/qcow/internal/mod.rs b/block/src/formats/qcow/internal/mod.rs index bb695e815..a2da14318 100644 --- a/block/src/formats/qcow/internal/mod.rs +++ b/block/src/formats/qcow/internal/mod.rs @@ -68,6 +68,10 @@ pub enum Error { BackingFileIo(String /* path */, #[source] io::Error), #[error("Backing file open error: {0}")] BackingFileOpen(String /* path */, #[source] Box), + #[error( + "Backing file name at offset {0:#x} length {1:#x} lies outside first cluster of {2:#x}" + )] + BackingFileOutsideFirstCluster(u64, u32, u64), #[error("Backing file support is disabled")] BackingFilesDisabled, #[error("Backing file name is too long: {0} bytes over")]