block: vhdx: fix incomplete bounds check in sync I/O worker - #8529
Merged
rbradford merged 4 commits intoJul 7, 2026
Merged
Conversation
rbradford
requested changes
Jul 6, 2026
rbradford
left a comment
Member
There was a problem hiding this comment.
This should be structured as 4 commits:
- Add these bounds check methods to
AsyncIoOperation - Test refactoring setup for vhdx
- Switch vhd to use them
- Switch vhdx to use them
Comment on lines
+1
to
+38
| // Copyright 2026 The Cloud Hypervisor Authors. All rights reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| use std::io; | ||
|
|
||
| use crate::async_io::{AsyncIoError, AsyncIoOperation, AsyncIoResult}; | ||
|
|
||
| pub(super) fn validate_operation_bounds(op: &AsyncIoOperation, size: u64) -> AsyncIoResult<()> { | ||
| let offset = u64::try_from(op.offset()).map_err(|_| bounds_error(op, size))?; | ||
| let len = u64::try_from(op.total_len()).map_err(|_| bounds_error(op, size))?; | ||
| let end = offset | ||
| .checked_add(len) | ||
| .ok_or_else(|| bounds_error(op, size))?; | ||
|
|
||
| if end > size { | ||
| return Err(bounds_error(op, size)); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| fn bounds_error(op: &AsyncIoOperation, size: u64) -> AsyncIoError { | ||
| let error = io::Error::new( | ||
| io::ErrorKind::InvalidData, | ||
| format!( | ||
| "Invalid request offset {} and length {}, can't exceed file size {}", | ||
| op.offset(), | ||
| op.total_len(), | ||
| size | ||
| ), | ||
| ); | ||
| if op.is_read() { | ||
| AsyncIoError::ReadVectored(error) | ||
| } else { | ||
| AsyncIoError::WriteVectored(error) | ||
| } | ||
| } |
Member
There was a problem hiding this comment.
This is a duplicate of vhd - please do something like:
impl AsyncIoOperation {
/// Rejects an operation whose byte range falls outside a disk of `size` bytes.
pub(crate) fn validate_bounds(&self, size: u64) -> AsyncIoResult<()> {
let out_of_bounds = || {
let error = io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid request offset {} and length {}, can't exceed file size {size}",
self.offset(),
self.total_len(),
),
);
if self.is_read() {
AsyncIoError::ReadVectored(error)
} else {
AsyncIoError::WriteVectored(error)
}
};
let offset = u64::try_from(self.offset()).map_err(|_| out_of_bounds())?;
let len = u64::try_from(self.total_len()).map_err(|_| out_of_bounds())?;
if offset.checked_add(len).ok_or_else(out_of_bounds)? > size {
return Err(out_of_bounds());
}
Ok(())
}
}and port vhd
Alvov1
force-pushed
the
fix/vhdx-sync-bounds-check
branch
from
July 7, 2026 08:01
9ef1e25 to
7dc19d4
Compare
Implement global helper to validate vhd and vhdx sync workers' I/O requests whose offset + length exceeds the virtual disk's logical size. Signed-off-by: Alexander Lvov <[email protected]>
rbradford
approved these changes
Jul 7, 2026
rbradford
left a comment
Member
There was a problem hiding this comment.
Thanks! Really good - could we just make that small rename?
|
|
||
| /// Generate a small dynamic VHDX with `qemu-img`. Returns `None` (and the | ||
| /// test is skipped) when `qemu-img` is unavailable, e.g. in minimal CI. | ||
| pub(crate) fn dynamic_vhdx(size_mib: u64) -> Option<TempFile> { |
Member
There was a problem hiding this comment.
I think this name is confusing (I know this PR just moved it around). It sounds like it could be an method to identify if this a "dynamic vhdx" file. I think create_dynamic_vhdx would be better.
Contributor
Author
|
Sure! Please wait, I missed one small detail, there will be one more fpush |
Extract the dynamic VHDX qemu-img helper into a shared vhdx:: test_util module to reuse inside the upcoming VhdxSync bounds-check. Signed-off-by: Alexander Lvov <[email protected]>
Reuse global validate_bounds() operation helper instead of having a local implementation in vhd/worker/common.rs Signed-off-by: Alexander Lvov <[email protected]>
VhdxSync::submit_data_operation() passed every read/write straight to the underlying Vhdx without checking the request against the virtual disk's logical size. A request that started inside the image but extended past its end (or an offset past the end entirely) was passed through unchecked, silently reading/writing out of the intended bounds. Call AsyncIoOperation::validate_bounds() from submit_data_operation() before dispatching the operation, the same way the VHD sync worker does. The check rejects any request whose offset + length exceeds the logical size. Signed-off-by: Alexander Lvov <[email protected]>
Alvov1
force-pushed
the
fix/vhdx-sync-bounds-check
branch
from
July 7, 2026 09:07
7dc19d4 to
fa38a13
Compare
Contributor
Author
|
Now should be good. Implemented requested changes and updated the PR description |
rbradford
approved these changes
Jul 7, 2026
rbradford
enabled auto-merge
July 7, 2026 09:20
Merged
via the queue into
cloud-hypervisor:main
with commit Jul 7, 2026
fa7cad4
37 of 41 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
VhdxSync::submit_data_operation() passed every read/write straight to the underlying Vhdx without checking the request against the virtual disk's logical size. A request that started inside the image but extended past its end (or an offset past the end entirely) was passed through unchecked, silently reading/writing out of the intended bounds
Add AsyncIoOperation::validate_bounds() as a shared helper on the operation type itself, and call it from VhdxSync::submit_data_operation() before dispatching the operation, the same way the VHD sync worker already does (#8394). The check rejects any request whose offset + length exceeds the logical size
The VHD workers are also switched over to the shared helper, replacing their local validate_operation_bounds() copy in vhd/worker/common.rs