vmm, virtio-devices: Deny zero sector writes for autodetected raw images

If the disk image was autodetected to raw (not specified with image_type
= 0) then in the virtio-block subsystem generate errors for writes to
block 0 (treat as if read-only). This gives an immediate error vs using
the image implementations in the block subsystem.

Signed-off-by: Rob Bradford <rbradford@meta.com>
(cherry picked from commit b3e8e2abc5)
This commit is contained in:
Rob Bradford
2026-02-15 22:51:56 +00:00
committed by Bo Chen
parent 5a0b6f2d06
commit 90cee24f98
3 changed files with 29 additions and 4 deletions

View File

@@ -67,6 +67,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
EventFd::new(EFD_NONBLOCK).unwrap(), EventFd::new(EFD_NONBLOCK).unwrap(),
None, None,
queue_affinity, queue_affinity,
false,
) )
.unwrap(); .unwrap();

View File

@@ -160,6 +160,7 @@ struct BlockEpollHandler {
access_platform: Option<Arc<dyn AccessPlatform>>, access_platform: Option<Arc<dyn AccessPlatform>>,
host_cpus: Option<Vec<usize>>, host_cpus: Option<Vec<usize>>,
acked_features: u64, acked_features: u64,
disable_sector0_writes: bool,
} }
fn has_feature(features: u64, feature_flag: u64) -> bool { fn has_feature(features: u64, feature_flag: u64) -> bool {
@@ -167,8 +168,13 @@ fn has_feature(features: u64, feature_flag: u64) -> bool {
} }
impl BlockEpollHandler { impl BlockEpollHandler {
fn check_request(features: u64, request_type: RequestType) -> result::Result<(), ExecuteError> { fn check_request(
if has_feature(features, VIRTIO_BLK_F_RO.into()) features: u64,
request: &Request,
disable_sector0_writes: bool,
) -> result::Result<(), ExecuteError> {
let request_type = request.request_type;
if (has_feature(features, VIRTIO_BLK_F_RO.into()))
&& !(request_type == RequestType::In || request_type == RequestType::GetDeviceId) && !(request_type == RequestType::In || request_type == RequestType::GetDeviceId)
{ {
// For virtio spec compliance // For virtio spec compliance
@@ -176,6 +182,11 @@ impl BlockEpollHandler {
// if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data."
return Err(ExecuteError::ReadOnly); return Err(ExecuteError::ReadOnly);
} }
if request_type == RequestType::Out && disable_sector0_writes && request.sector == 0 {
return Err(ExecuteError::ReadOnly);
}
Ok(()) Ok(())
} }
@@ -191,7 +202,10 @@ impl BlockEpollHandler {
// For virtio spec compliance // For virtio spec compliance
// "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request // "A device MUST set the status byte to VIRTIO_BLK_S_IOERR for a write request
// if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data." // if the VIRTIO_BLK_F_RO feature if offered, and MUST NOT write any data."
if let Err(e) = Self::check_request(self.acked_features, request.request_type) { // Also, if sector 0 writes are disabled, treat writes to sector 0 as read-only as well.
if let Err(e) =
Self::check_request(self.acked_features, &request, self.disable_sector0_writes)
{
warn!("Request check failed: {request:x?} {e:?}"); warn!("Request check failed: {request:x?} {e:?}");
desc_chain desc_chain
.memory() .memory()
@@ -644,6 +658,7 @@ pub struct Block {
exit_evt: EventFd, exit_evt: EventFd,
serial: Vec<u8>, serial: Vec<u8>,
queue_affinity: BTreeMap<u16, Vec<usize>>, queue_affinity: BTreeMap<u16, Vec<usize>>,
disable_sector0_writes: bool,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@@ -672,6 +687,7 @@ impl Block {
exit_evt: EventFd, exit_evt: EventFd,
state: Option<BlockState>, state: Option<BlockState>,
queue_affinity: BTreeMap<u16, Vec<usize>>, queue_affinity: BTreeMap<u16, Vec<usize>>,
disable_sector0_writes: bool,
) -> io::Result<Self> { ) -> io::Result<Self> {
let (disk_nsectors, avail_features, acked_features, config, paused) = let (disk_nsectors, avail_features, acked_features, config, paused) =
if let Some(state) = state { if let Some(state) = state {
@@ -772,6 +788,7 @@ impl Block {
exit_evt, exit_evt,
serial, serial,
queue_affinity, queue_affinity,
disable_sector0_writes,
}) })
} }
@@ -1016,6 +1033,7 @@ impl VirtioDevice for Block {
access_platform: self.common.access_platform.clone(), access_platform: self.common.access_platform.clone(),
host_cpus: self.queue_affinity.get(&queue_idx).cloned(), host_cpus: self.queue_affinity.get(&queue_idx).cloned(),
acked_features: self.common.acked_features, acked_features: self.common.acked_features,
disable_sector0_writes: self.disable_sector0_writes,
}; };
let paused = self.common.paused.clone(); let paused = self.common.paused.clone();

View File

@@ -2666,13 +2666,18 @@ impl DeviceManager {
let detected_image_type = let detected_image_type =
detect_image_type(&mut file).map_err(DeviceManagerError::DetectImageType)?; detect_image_type(&mut file).map_err(DeviceManagerError::DetectImageType)?;
let mut disable_sector0_writes = false;
if disk_cfg.image_type == ImageType::Unknown { if disk_cfg.image_type == ImageType::Unknown {
warn!( warn!(
"No image_type specified - detected as {detected_image_type}. \ "No image_type specified - detected as {detected_image_type}. \
Configuration updated to persist type across reboots and migrations." Configuration updated to persist type across reboots and migrations."
); );
if detected_image_type != ImageType::Raw { if detected_image_type == ImageType::Raw {
warn!("Autodetected raw image type. Disabling sector 0 writes.");
disable_sector0_writes = true;
} else {
warn!( warn!(
"Non-raw image type detected. In the future it will be necessary \ "Non-raw image type detected. In the future it will be necessary \
to specify image_type for non-raw files." to specify image_type for non-raw files."
@@ -2825,6 +2830,7 @@ impl DeviceManager {
state_from_id(self.snapshot.as_ref(), id.as_str()) state_from_id(self.snapshot.as_ref(), id.as_str())
.map_err(DeviceManagerError::RestoreGetState)?, .map_err(DeviceManagerError::RestoreGetState)?,
queue_affinity, queue_affinity,
disable_sector0_writes,
) )
.map_err(DeviceManagerError::CreateVirtioBlock)?; .map_err(DeviceManagerError::CreateVirtioBlock)?;