mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
tests: qcow: Add corrupt bit integration tests
Add integration tests for QCOW2 corrupt bit handling. Verify that images with the corrupt bit set are rejected for writable access but allowed for read-only access with a warning. Helper functions are added to read and modify the corrupt flag in the QCOW2 v3 header. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
9baa904a5c
commit
edaeaed5f7
@@ -3549,17 +3549,48 @@ mod common_parallel {
|
|||||||
serde_json::from_slice(&output.stdout).ok()
|
serde_json::from_slice(&output.stdout).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_dirty_flag(path: &std::path::Path) -> Result<Option<bool>, String> {
|
fn get_qcow2_v3_info(path: &Path) -> Result<Option<serde_json::Value>, String> {
|
||||||
let info = get_image_info(path)
|
let info = get_image_info(path)
|
||||||
.ok_or_else(|| format!("qemu-img info failed for {}", path.display()))?;
|
.ok_or_else(|| format!("qemu-img info failed for {}", path.display()))?;
|
||||||
if info["format"].as_str() != Some("qcow2") {
|
if info["format"].as_str() != Some("qcow2") {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
// QCOW2 v3 has compat "1.1", v2 has "0.10" and doesn't support dirty flag
|
// QCOW2 v3 has compat "1.1", v2 has "0.10"
|
||||||
if info["format-specific"]["data"]["compat"].as_str() != Some("1.1") {
|
if info["format-specific"]["data"]["compat"].as_str() != Some("1.1") {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Ok(info["dirty-flag"].as_bool())
|
Ok(Some(info))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_dirty_flag(path: &Path) -> Result<Option<bool>, String> {
|
||||||
|
Ok(get_qcow2_v3_info(path)?.and_then(|info| info["dirty-flag"].as_bool()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_corrupt_flag(path: &Path) -> Result<Option<bool>, String> {
|
||||||
|
Ok(get_qcow2_v3_info(path)?
|
||||||
|
.and_then(|info| info["format-specific"]["data"]["corrupt"].as_bool()))
|
||||||
|
}
|
||||||
|
|
||||||
|
const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72;
|
||||||
|
|
||||||
|
fn set_corrupt_flag(path: &Path, corrupt: bool) -> io::Result<()> {
|
||||||
|
let mut file = OpenOptions::new().read(true).write(true).open(path)?;
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?;
|
||||||
|
let mut buf = [0u8; 8];
|
||||||
|
file.read_exact(&mut buf)?;
|
||||||
|
let mut features = u64::from_be_bytes(buf);
|
||||||
|
|
||||||
|
if corrupt {
|
||||||
|
features |= 0x02;
|
||||||
|
} else {
|
||||||
|
features &= !0x02;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(QCOW2_INCOMPATIBLE_FEATURES_OFFSET))?;
|
||||||
|
file.write_all(&features.to_be_bytes())?;
|
||||||
|
file.sync_all()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_disk_path(path_or_image_name: impl AsRef<std::path::Path>) -> std::path::PathBuf {
|
fn resolve_disk_path(path_or_image_name: impl AsRef<std::path::Path>) -> std::path::PathBuf {
|
||||||
@@ -3884,6 +3915,135 @@ mod common_parallel {
|
|||||||
disk_check_consistency(&test_image_path, None);
|
disk_check_consistency(&test_image_path, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_virtio_block_qcow2_corrupt_bit_rejected_for_write() {
|
||||||
|
let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string());
|
||||||
|
let guest = Guest::new(Box::new(disk_config));
|
||||||
|
let kernel_path = direct_kernel_boot_path();
|
||||||
|
|
||||||
|
let test_image_path = guest.tmp_dir.as_path().join("test-corrupt.qcow2");
|
||||||
|
let original_image = guest.disk_config.disk(DiskType::OperatingSystem).unwrap();
|
||||||
|
|
||||||
|
copy(original_image, &test_image_path).expect("Failed to copy qcow2 image");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
check_corrupt_flag(&test_image_path).expect("Failed to check corrupt flag"),
|
||||||
|
Some(false),
|
||||||
|
"Image should start with corrupt bit cleared"
|
||||||
|
);
|
||||||
|
|
||||||
|
set_corrupt_flag(&test_image_path, true).expect("Failed to set corrupt flag");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
check_corrupt_flag(&test_image_path).expect("Failed to check corrupt flag"),
|
||||||
|
Some(true),
|
||||||
|
"Corrupt bit should be set"
|
||||||
|
);
|
||||||
|
|
||||||
|
let child = GuestCommand::new(&guest)
|
||||||
|
.args(["--cpus", "boot=1"])
|
||||||
|
.args(["--memory", "size=512M"])
|
||||||
|
.args(["--kernel", kernel_path.to_str().unwrap()])
|
||||||
|
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||||
|
.args([
|
||||||
|
"--disk",
|
||||||
|
&format!("path={}", test_image_path.to_str().unwrap()),
|
||||||
|
&format!(
|
||||||
|
"path={}",
|
||||||
|
guest.disk_config.disk(DiskType::CloudInit).unwrap()
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.default_net()
|
||||||
|
.capture_output()
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let output = child.wait_with_output().unwrap();
|
||||||
|
assert!(
|
||||||
|
!output.status.success(),
|
||||||
|
"VM should fail to start with corrupt disk image"
|
||||||
|
);
|
||||||
|
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("corrupt") || stderr.contains("Corrupt"),
|
||||||
|
"Error message should mention corruption: {stderr}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_virtio_block_qcow2_corrupt_bit_allowed_readonly() {
|
||||||
|
let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string());
|
||||||
|
let guest = Guest::new(Box::new(disk_config));
|
||||||
|
let kernel_path = direct_kernel_boot_path();
|
||||||
|
|
||||||
|
let test_image_path = guest.tmp_dir.as_path().join("test-corrupt-ro.qcow2");
|
||||||
|
let original_image = guest.disk_config.disk(DiskType::OperatingSystem).unwrap();
|
||||||
|
|
||||||
|
copy(original_image, &test_image_path).expect("Failed to copy qcow2 image");
|
||||||
|
|
||||||
|
set_corrupt_flag(&test_image_path, true).expect("Failed to set corrupt flag");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
check_corrupt_flag(&test_image_path).expect("Failed to check corrupt flag"),
|
||||||
|
Some(true),
|
||||||
|
"Corrupt bit should be set"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut child = GuestCommand::new(&guest)
|
||||||
|
.args(["--cpus", "boot=1"])
|
||||||
|
.args(["--memory", "size=512M"])
|
||||||
|
.args(["--kernel", kernel_path.to_str().unwrap()])
|
||||||
|
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
|
||||||
|
.args([
|
||||||
|
"--disk",
|
||||||
|
&format!("path={},readonly=on", test_image_path.to_str().unwrap()),
|
||||||
|
&format!(
|
||||||
|
"path={}",
|
||||||
|
guest.disk_config.disk(DiskType::CloudInit).unwrap()
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.default_net()
|
||||||
|
.capture_output()
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_secs(5));
|
||||||
|
|
||||||
|
match child.try_wait() {
|
||||||
|
Ok(Some(status)) => {
|
||||||
|
let output = child.wait_with_output().unwrap();
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
panic!(
|
||||||
|
"VM should not have exited when opening corrupt image as readonly. Exit status: {}, stderr: {}",
|
||||||
|
status, stderr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
// VM is still running as expected
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Error checking process status: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = unsafe { libc::kill(child.id() as i32, libc::SIGKILL) };
|
||||||
|
let output = child.wait_with_output().unwrap();
|
||||||
|
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("QCOW2 image is marked corrupt, opening read-only"),
|
||||||
|
"Expected warning about corrupt image being opened read-only. stderr: {}",
|
||||||
|
stderr
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
check_corrupt_flag(&test_image_path).expect("Failed to check corrupt flag"),
|
||||||
|
Some(true),
|
||||||
|
"Corrupt bit should remain set for read-only access"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtio_block_vhd() {
|
fn test_virtio_block_vhd() {
|
||||||
let mut workload_path = dirs::home_dir().unwrap();
|
let mut workload_path = dirs::home_dir().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user