On 9/30/26 18:49, Denis V. Lunev wrote:
> This email originated from an IP that might not be authorized by the domain 
> it was sent from.
> Do not click links or open attachments unless it is an email you expected to 
> receive.
> A guest running a container workload came up with 164 files of an image
> layer hollowed out, 45 MiB in all, reading back as zeroes where it had
> written data and been told the writes were on disk. The damage was
> scattered in guest offsets but contiguous in image file offsets: a
> qcow2 over something that zeroed a range of the file.
>
> The storage underneath records every change to the file's length. Over
> a tenth of a second the file grew thirteen times in unaligned steps,
> one per guest write past its end, then once by 82 MiB. A preallocation
> sets the length to the triggering write's end rounded up plus 128 MiB,
> so 82 cannot come from the length the file then had; it can only come
> from a zeroing that started where the burst began, 49 MiB lower, which
> is exactly the range that came back as zeroes. The fallocate ran over
> thirteen acknowledged writes.
>
> So the filter worked from a length the file no longer had. There are
> two ways to get there. Neither is proven, the record cannot decide
> between them, and both are fixed here.
>
> The first is that the filter meant to let those writes in. It raises
> data_end before issuing the preallocation, so that writes below it
> pass straight through into the range the fallocate is about to zero,
> trusting the block layer to order them after it. That trust is
> misplaced, and it needs no race to go wrong: two things meet. The
> file-posix widening that patch 1 removes parks the preallocation
> despite BDRV_REQ_NO_WAIT, and bdrv_find_conflicting_request() skips
> any overlapping request whose waiting_for is set without looking at
> what it points at, so a parked serialising request excludes nothing.
>
> The second is that the length was simply stale. The filter's lengths
> are plain fields, read, decided on and written back with nothing in
> between, and the guest that broke was a virtio-blk with its queues on
> two iothreads. A lost update on the reserved length gives a start 49
> MiB low with no ordering subtlety at all.
>
> For the first, the straight fix is in bdrv_find_conflicting_request(),
> and it regresses iotests/077, whose "Overlapping multiple requests"
> case then verifies wrong data. Patch 1 takes away the other half
> instead: the XFS bug the workaround guards was fixed by 249bd9087a52
> in v5.5, three days before the workaround went in, and predates every
> stable series still maintained. Without the widening NO_WAIT works as
> designed, so it is the one patch here I would not drop.
>
> For the second, the same state turns out to lose data three ways:
>
> - a write into the range the filter records as zero leaves the mark
>   behind it, so a later write zeroes over that range is skipped and
>   the guest reads the old data back;
>
> - two writers extending the file both pass the compare on data_end and
>   the smaller end lands last, so closing the image truncates the file
>   below acknowledged data;
>
> - two writers both preallocate over the same range, the older
>   fallocate lands last, and the first writer's data is zeroed. Fixing
>   this also stops the filter leaning on the ordering above.
>
> Two of the three have tests in iotests/298. The data_end race has
> none: there is no hook between the compare and the store to hang one
> on.
>
> Six patches are tooling, because none of this could be tested: every
> qemu-io request ran in the main loop, blkdebug could neither wake nor
> wait for a request on another AioContext, and nothing could hold a
> request before the block layer tracks it, which is the window the
> filter opens. None changes the output of an existing command.
>
> Signed-off-by: Denis V. Lunev <[email protected]>
> CC: Kevin Wolf <[email protected]>
> CC: Hanna Reitz <[email protected]>
> CC: Vladimir Sementsov-Ogievskiy <[email protected]>
> CC: Andrey Drobyshev <[email protected]>
>
> Andrey Drobyshev (1):
>   iotests/298: add testcase for async writes with preallocation filter
>
> Denis V. Lunev (12):
>   block/file-posix: drop the XFS workaround for post-EOF fallocate
>   block/preallocate: keep zero_start above written data
>   block/preallocate: collect the state assignments in one place
>   block/preallocate: serialise the filter state
>   block/preallocate: hold back writes into a preallocation in flight
>   tests/unit: prefix the iothread test helper
>   qemu-io: accept --object iothread
>   qemu-io: add -t to run an aio request in a named iothread
>   qemu-io: add -g to write past the end of the image
>   block/blkdebug: make a breakpoint work across AioContexts
>   block: add blkdebug events for a write before it is tracked
>   iotests/298: cover the preallocation races
>
>  block/blkdebug.c                  |   3 +-
>  block/file-posix.c                |  34 --------
>  block/io.c                        |   3 +
>  block/preallocate.c               | 139 ++++++++++++++++++++++++------
>  meson.build                       |   2 +-
>  qapi/block-core.json              |  10 ++-
>  qemu-io-cmds.c                    | 116 ++++++++++++++++++++++---
>  qemu-io.c                         |  41 ++++++++-
>  tests/qemu-iotests/298            | 113 ++++++++++++++++++++++++
>  tests/qemu-iotests/298.out        |   4 +-
>  tests/unit/iothread.c             |  28 +++---
>  tests/unit/iothread.h             |   8 +-
>  tests/unit/test-aio-multithread.c |   8 +-
>  tests/unit/test-bdrv-drain.c      |  32 +++----
>  tests/unit/test-block-iothread.c  |  28 +++---
>  15 files changed, 435 insertions(+), 134 deletions(-)
>
ping

Reply via email to