The sync I/O worker only checked that the operation offset did
not start past the end of the virtual disk (offset >= size) -
did not verify that the operation end (offset + len) stays
within bounds.
A read or write that started inside the image but extended
beyond the logical size was silently passed to the raw backend.
The async io_uring worker already had the correct check
(offset + len > size with overflow protection). I extracted it
into a shared helper in worker/common.rs and reused inside the
sync path to eliminate duplication and close the gap.
Fixes#8311
Signed-off-by: Alexander Lvov <alexander.lvov.git@gmail.com>
Removal of absolute paths is currently in progress. To avoid regressing
those changes add a clippy deny at the workspace level and at the crate
level override with #[expect(clippy::absolute_paths)]
See: #7670
Signed-off-by: Rob Bradford <rbradford@meta.com>
Acquiring an image lock can be interrupted with EINTR. In this case, we
returned with an error. Instead, we now retry acquiring the lock.
Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
By redirecting VHDx I/O through the AlignedFile the required RMW
semantics can be achieved for writes less than the logical block size
whilt reusing the same logic used for other backend implementations.
Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
Reuse the functionality in the AlignedFile wrapper for the QCOW RawFile
wrapper. This makes alignment handling more transparent.
Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
Provide a single home for O_DIRECT alignment and RMW behavior behind an
std::os::unix::fs::FileExt implementation built on AlignedBuffer.
Unaligned requests are bounced through an AlignedBuffer (applying RMW
for writes) and aligned requests pass straight through to the inner
File.
Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
Replace the use of the pread64/pwrite64 helpers with versions from
std::os::unix::fs::FileExt.
As this was the last use of these pread functions remove them and their
tests.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
Replace the qcow specific AlignedBuf along with the pread64/pwrite64
helpers with the new common AlignedBuffer implementation.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
Replace use of raw pread64/pwrite64 functions with
std::os::unix::fs::FileExt for I/O without a cursor.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
Replace the manual alloc_zeroed/dealloc and pread64/pwrite64 with use of
the new AlignedBuffer structure.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
The block code repeatedly open-codes O_DIRECT alignment calculations and
bounce-buffer allocation at each I/O site. Add an AlignedBuffer struct
that handles the alignment and allocation in one place, using FileExt
(read_exact_at and write_all_at) for the I/O (so no need for custom libc
wrappers).
The caller creates an AlignedBuffer with an offset, length and
alignment, then uses read_from and write_to for aligned I/O and as_slice
and as_mut_slice to access the logical data portion within the aligned
region.
This is a lot like the AlignedBuf that was already existing in the QCOW2
code but is a more generalised version.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
Implement std::os::unix::fs::FileExt for RawFile by delegating to the
inner File. This enables callers holding a reference to a RawFile to use
read_exact_at and write_all_at directly for non-cursor I/O (like pread,
etc) without going through custom helper functions.
Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
Import the std modules used in the crate instead of spelling the full
paths at every use site.
Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
Cover the prior commit by constructing a Request directly and a stub
AsyncIo whose backend methods are unreachable, then submit a payload
with sector + num_sectors past u64::MAX and assert BadRequest.
Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
In Request::execute_async the WriteZeroes arm multiplied wz_sector
by SECTOR_SIZE before the checked_add of sector and num_sectors.
A wz_sector near u64::MAX overflows the multiplication.
Reorder the arm to run the checked_add and disk_nsectors check
first, matching the Discard arm above.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Import the std modules used in the disk-format handlers instead of
spelling the full paths at every use site.
Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
Delete the now unused QcowFile implementation it was only used for
creating disk images for the tests and for the performance-metrics. It
was not used for the virtio-block device.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Pull rebuild_refcounts out of QcowFile so QcowFile can be removed in a
follow up commit. This function is still required by parse_qcow().
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Port the internal/mod.rs tests from QcowFile to QcowDisk so
the surviving tests exercise the code paths that are hit when used via
QcowDisk (and thus virtio-block).
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Switch from QcowFile to QcowDisk taking advantage of QcowTempDisk where
appropriate.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Port tests over to QcowTempDisk and also over to QcowDisk rather than
QcowFile where necessary.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
A common pattern in the test code is to create a temporary file, format
it as QCOW2 and then open it as a QcowDisk. Create a helper struct that
can be used in those tests. This is marked as #[cfg(test)] as initially
it will only be used by the test suite.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Add a method to format a file as a QCOW2 file which will mainly be used
by the test infrastructure. This copies the logic from QcowFile. It
doesn't refactor it as the removal of QcowFile is planned.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
Cover the malformed header case where backing_file_offset is non
zero but backing_file_size is zero, which must be rejected with the
new dedicated error.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Cover the malformed header case where backing_file_offset is zero
but backing_file_size is non zero, which must be rejected with the
new dedicated error.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Cover the case where backing_file_offset points inside the fixed
header fields, which the new header overlap check must reject.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Cover the boundary positive case where backing_file_offset plus
backing_file_size equals the cluster size, which the spec allows
and the new bound check must accept.
Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Cover the case where backing_file_offset lies inside the first
cluster but backing_file_offset + backing_file_size crosses the
cluster boundary, so the end of the name spills outside.
Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Cover an offset that lies well past the end of the first cluster
to make sure the bound check fires for arbitrary out of range
offsets rather than only the boundary case.
Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
A backing file string placed exactly at cluster_size starts past
the first cluster boundary, so QcowHeader::new must reject it.
Introduce a read_header_with_patched_backing helper that builds
a valid header, patches backing_file_offset and backing_file_size,
writes it out and re-parses it. Use it to cover this case.
Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
A qcow2 header with non-zero backing_file_offset that points at a
zero length name is malformed. The parser would otherwise read an
empty path string and store it as a backing file. Reject it with a
dedicated error so the user gets a clear diagnostic.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
A qcow2 header with backing_file_offset == 0 indicates the image
has no backing file, so any non-zero backing_file_size is malformed.
Qemu silently ignores the size in this case, which hides image
corruption. Reject it explicitly with a dedicated error so the user
gets a clear diagnostic.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
The qcow2 spec requires the backing file name string to live in the
remaining space between the end of the header extension area and
the end of the first cluster. Header parsing accepted any backing
file offset above zero, including offsets pointing into the fixed
header fields themselves, so a corrupt or malicious image could
redirect the parser into reinterpreting header bytes as the backing
path.
Reject any backing_file_offset that is less than header.header_size
via a new BackingFileOverlapsHeader error. The check fires before
the existing first cluster bound, since an overlap is a different
class of corruption and deserves a distinct diagnostic.
Ref: #8261
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
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 <anbelski@linux.microsoft.com>
Standard L2 data offsets and L1-referenced L2 table offsets must be
aligned and covered by the current refcount table. The write path
checked both constraints at one call site, while read, cache population,
and deallocation paths only checked alignment or relied on later
refcount lookup errors.
Centralize the validation in QcowState and use it before reading L2
tables, mapping standard L2 entries for reads and writes, and
deallocating existing clusters. Invalid offsets set the corrupt bit and
fail with EIO before data I/O or refcount updates.
Add QcowSync regression tests that corrupt a standard L2 entry past the
refcount-addressable range and verify that reads and writes fail with
EIO and mark the header corrupt.
Assisted-by: Codex:GPT-5
Signed-off-by: Ian Klemm <hi@ianklemm.de>
write_pointer_table() used a BufWriter over a cloned fd because the
per-entry callback also needs mutable access to QcowRawFile.
That couples the final write location to the ambient kernel cursor while
the callback is allowed to perform metadata I/O. Materialize the encoded
entries first, then seek and write the table after callback execution
has finished.
This keeps the pointer-table write independent from current and future
callback behavior without depending on proving that a cursor-moving
callback is reachable in today's synchronous CH path.
Apply the same materialize-then-write shape to
write_pointer_table_direct() for consistent semantics, and cover both
paths with unit tests.
Assisted-by: Codex:GPT-5
Signed-off-by: Ian Klemm <hi@ianklemm.de>
Switch Request::parse over to the CheckedDescriptorIter helper from
vm-virtio so the block crate validates each descriptor's translated
(addr, len) range against guest memory through the same centralized
path used by virtio-devices. Any descriptor whose buffer is not fully
backed by guest RAM is now rejected before any I/O is set up against
it.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Re-export Vhdx from formats::vhdx and update the fuzz target to
use block::formats::vhdx::Vhdx. Remove the vhdx compat alias
from lib.rs.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Update external consumers to use formats::qcow::internal instead
of the top level qcow alias. Keep a crate private use for the
QcowError variant in lib.rs.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Update external consumers and internal test modules to use
formats::raw instead of the raw_disk alias, then remove the
re-export from lib.rs.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move QCOW2 format implementation into a structured directory layout:
qcow/ -> formats/qcow/internal/ (filenames unchanged)
qcow_disk.rs -> formats/qcow/mod.rs (QcowDisk)
qcow_sync.rs -> formats/qcow/worker/sync.rs (QcowSync)
qcow_async.rs -> formats/qcow/worker/async_uring.rs (QcowAsync)
qcow_common.rs -> formats/qcow/common.rs
All internal cross references continue to resolve through
re-exports in lib.rs: formats::qcow::internal as qcow,
formats::qcow as qcow_disk, and
formats::qcow::common as qcow_common.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move VHDX format implementation into a structured directory layout:
vhdx/mod.rs -> formats/vhdx/internal/mod.rs (Vhdx)
vhdx/vhdx_bat.rs -> formats/vhdx/internal/bat.rs
vhdx/vhdx_header.rs -> formats/vhdx/internal/header.rs
vhdx/vhdx_io.rs -> formats/vhdx/internal/io.rs
vhdx/vhdx_metadata.rs -> formats/vhdx/internal/metadata.rs
vhdx_sync.rs -> formats/vhdx/mod.rs (VhdxDisk)
Extract VhdxSync from vhdx_sync.rs into formats/vhdx/worker/sync.rs.
Drop the vhdx_ prefix from internal file names since the parent
directory already provides the namespace. Update all internal cross
references to use the new module paths. Re-export formats::vhdx as
vhdx_sync in lib.rs for backward compatibility.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move VHD format implementation into a structured directory layout:
fixed_vhd.rs -> formats/vhd/internal/fixed.rs (FixedVhd)
fixed_vhd_disk.rs -> formats/vhd/mod.rs (VhdDisk)
vhd.rs -> formats/vhd/internal/footer.rs (VhdFooter)
fixed_vhd_sync.rs -> formats/vhd/worker/sync.rs (FixedVhdSync)
fixed_vhd_async.rs -> formats/vhd/worker/async_uring.rs (FixedVhdAsync)
Add #[allow(dead_code)] to VhdFooter struct and impl because the
module is now pub(crate) and the compiler can see that several
fields and getters are only exercised by unit tests. Re-export
formats::vhd as fixed_vhd_disk in lib.rs for backward
compatibility with external consumers.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move raw format implementation into a structured directory layout:
raw_disk.rs -> formats/raw/mod.rs (RawDisk)
raw_sync.rs -> formats/raw/worker/sync.rs (RawSync)
raw_async.rs -> formats/raw/worker/async_uring.rs (RawAsync)
raw_async_aio.rs -> formats/raw/worker/async_aio.rs (RawAio)
raw_async_io_tests.rs -> formats/raw/worker/tests.rs
Update imports in fixed_vhd_sync.rs and fixed_vhd_async.rs to use
the new paths. Re-export formats::raw as raw_disk in lib.rs to
preserve the external API.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move async_io.rs, fcntl.rs, and request.rs into block/src/io/. These
files are generic I/O infrastructure shared by all formats rather
than format specific code.
The io/ directory name clashes with std::io in lib.rs, so the module
is declared as io_impl via #[path] and the submodules are re-exported
at the crate root to keep existing import paths working.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
FixedVhdDisk is the format level DiskFile wrapper for VHD images.
Rename it to VhdDisk to match the <Format>Disk convention used by
RawDisk, QcowDisk, and VhdxDisk. The Fixed prefix remains on the
workers (FixedVhdSync, FixedVhdAsync) since those are specific to
the fixed subformat.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Align with the <Format>Disk wrapper naming convention. VHDx
has a single on disk format so no variant suffix is needed.
The async backend will be added to VhdxDisk.
No functional change.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>