From: "Denis V. Lunev" <den@openvz.org>
To: qemu-block@nongnu.org
Cc: qemu-devel@nongnu.org, "Denis V. Lunev" <den@openvz.org>,
Stefan Hajnoczi <stefanha@redhat.com>
Subject: [PULL 27/29] iotests: cover a broken Format Extension and a combined repair
Date: Fri, 11 Sep 2026 01:42:20 +0200 [thread overview]
Message-ID: <20260910234222.3039975-28-den@openvz.org> (raw)
In-Reply-To: <20260910234222.3039975-1-den@openvz.org>
From: Denis V. Lunev <den@openvz.org>
An image which was not closed correctly may have had its Format
Extension cluster reused by a guest write, which is what the reuse of
those clusters is for. It has to open anyway, as the payload is intact,
and it has to stop pointing at what is no longer an extension.
The same damage in an image which was closed correctly is what an older
qemu leaves behind, as it ignores the extension in read-write mode and
truncates the file to the end of the payload on close. Both shapes of
it are covered: the cluster overwritten with something which is not an
extension, and the cluster truncated away with the header still
pointing past the end of the file. Neither keeps the image shut, and
qemu-img info is enough to tell, as it opens the image on its own.
Rebuilding the used bitmap after a leak is repaired reports the errors
which come with a BAT pointing a cluster twice, and those are the ones
qemu-img check is there to fix, so a duplicate entry next to a leak is
covered too.
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qemu-iotests/tests/parallels-checks | 80 +++++++++++++++++++
tests/qemu-iotests/tests/parallels-checks.out | 57 +++++++++++++
2 files changed, 137 insertions(+)
diff --git a/tests/qemu-iotests/tests/parallels-checks b/tests/qemu-iotests/tests/parallels-checks
index 575b736e35..3e9928c9c3 100755
--- a/tests/qemu-iotests/tests/parallels-checks
+++ b/tests/qemu-iotests/tests/parallels-checks
@@ -437,6 +437,86 @@ echo "== guest data was never in doubt =="
# Clear image
_make_test_img $SIZE
+echo "== TEST BROKEN EXTENSION OF AN IMAGE WHICH WAS NOT CLOSED =="
+
+EXT_OFF_OFFSET=56
+
+echo "== add a persistent dirty bitmap =="
+$QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" $BITMAP 2>&1 | _filter_testdir
+
+ext_off=$(peek_file_le "$TEST_IMG" $EXT_OFF_OFFSET 8)
+
+echo "== a guest write may reuse the extension cluster, so clobber it =="
+poke_file "$TEST_IMG" $((ext_off * 512)) "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+
+echo "== pretend the image was not closed correctly =="
+poke_file "$TEST_IMG" "$INUSE_OFFSET" "\x59\x6e\x6f\x74"
+
+echo "== the image opens, the extension is dropped =="
+{ $QEMU_IO -c "write -P 0x11 0 64k" "$TEST_IMG"; } 2>&1 | _filter_qemu_io |
+ _filter_testdir | _filter_generated_node_ids
+
+echo "== and it stopped pointing at the broken extension =="
+echo "ext_off: $(peek_file_le "$TEST_IMG" $EXT_OFF_OFFSET 8)"
+
+echo "== an older qemu leaves the same damage behind a clean close =="
+_make_test_img $SIZE
+$QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" $BITMAP 2>&1 | _filter_testdir
+ext_off=$(peek_file_le "$TEST_IMG" $EXT_OFF_OFFSET 8)
+poke_file "$TEST_IMG" $((ext_off * 512)) "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+_img_info | _filter_generated_node_ids
+
+echo "== truncating the extension away is dropped as well =="
+_make_test_img $SIZE
+$QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" $BITMAP 2>&1 | _filter_testdir
+ext_off=$(peek_file_le "$TEST_IMG" $EXT_OFF_OFFSET 8)
+truncate -s $((ext_off * 512)) "$TEST_IMG"
+_img_info | _filter_generated_node_ids
+
+# Clear image
+_make_test_img $SIZE
+
+echo "== TEST REPAIR OF A DUPLICATE ENTRY NEXT TO A LEAK =="
+
+echo "== write two clusters =="
+{ $QEMU_IO -c "write -P 0x11 0 $CLUSTER_SIZE" \
+ -c "write -P 0x22 $CLUSTER_SIZE $CLUSTER_SIZE" \
+ "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir
+
+echo "== point the second BAT entry at the first cluster =="
+first=$(peek_file_le "$TEST_IMG" $BAT_OFFSET 4)
+poke_file_le "$TEST_IMG" $(($BAT_OFFSET + 4)) 4 $first
+
+echo "== leak a cluster at the end of the image =="
+file_size=`stat --printf="%s" "$TEST_IMG"`
+fallocate -xl $((file_size + CLUSTER_SIZE)) "$TEST_IMG"
+
+echo "== both are repaired in one go =="
+_check_test_img -r all
+
+echo "== and the image opens read-write afterwards =="
+{ $QEMU_IO -c "read -P 0x11 0 $CLUSTER_SIZE" "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir
+
+# Clear image
+_make_test_img $SIZE
+
+echo "== TEST A LEAK WHICH DOES NOT FIT AN INT =="
+
+echo "== write one cluster =="
+{ $QEMU_IO -c "write -P 0x11 0 $CLUSTER_SIZE" "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir
+
+echo "== leave 3 GiB of unused space behind it =="
+truncate -s $((3 * 1024 * 1024 * 1024)) "$TEST_IMG"
+
+echo "== closing the image truncates it without complaining =="
+{ $QEMU_IO -c "read -P 0x11 0 $CLUSTER_SIZE" "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir
+
+file_size=`stat --printf="%s" "$TEST_IMG"`
+echo "file size: $file_size"
+
+# Clear image
+_make_test_img $SIZE
+
echo "== TEST A DUPLICATE IN THE LAST ALLOCATED BAT ENTRY =="
echo "== write two clusters =="
diff --git a/tests/qemu-iotests/tests/parallels-checks.out b/tests/qemu-iotests/tests/parallels-checks.out
index f390ea90d4..871a88d924 100644
--- a/tests/qemu-iotests/tests/parallels-checks.out
+++ b/tests/qemu-iotests/tests/parallels-checks.out
@@ -283,6 +283,63 @@ qemu-img: warning: Dropping inconsistent bitmap b2c9e1a4-5d3f-4e8b-9a7c-6f0d1e2b
read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
+== TEST BROKEN EXTENSION OF AN IMAGE WHICH WAS NOT CLOSED ==
+== add a persistent dirty bitmap ==
+== a guest write may reuse the extension cluster, so clobber it ==
+== pretend the image was not closed correctly ==
+== the image opens, the extension is dropped ==
+qemu-io: warning: Dropping the Format Extension of node 'NODE_NAME', which does not look like one: Wrong parallels Format Extension magic: 0xaaaaaaaaaaaaaaaa, expected: 0xab234cef23dcea87
+Repairing image was not closed correctly
+Repairing space leaked at the end of the image 1048576
+wrote 65536/65536 bytes at offset 0
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+== and it stopped pointing at the broken extension ==
+ext_off: 0
+== an older qemu leaves the same damage behind a clean close ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
+qemu-img: warning: Dropping the Format Extension of node 'NODE_NAME', which does not look like one: Wrong IMGFMT Format Extension magic: 0xaaaaaaaaaaaaaaaa, expected: 0xab234cef23dcea87
+image: TEST_DIR/t.IMGFMT
+file format: IMGFMT
+virtual size: 4 MiB (4194304 bytes)
+== truncating the extension away is dropped as well ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
+qemu-img: warning: Dropping the Format Extension of node 'NODE_NAME', which does not look like one: Format Extension is outside the image file
+image: TEST_DIR/t.IMGFMT
+file format: IMGFMT
+virtual size: 4 MiB (4194304 bytes)
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
+== TEST REPAIR OF A DUPLICATE ENTRY NEXT TO A LEAK ==
+== write two clusters ==
+wrote 1048576/1048576 bytes at offset 0
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote 1048576/1048576 bytes at offset 1048576
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+== point the second BAT entry at the first cluster ==
+== leak a cluster at the end of the image ==
+== both are repaired in one go ==
+Repairing space leaked at the end of the image 2097152
+Repairing duplicate offset in BAT entry 1
+The following inconsistencies were found and repaired:
+
+ 2 leaked clusters
+ 1 corruptions
+
+Double checking the fixed image now...
+No errors were found on the image.
+== and the image opens read-write afterwards ==
+read 1048576/1048576 bytes at offset 0
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
+== TEST A LEAK WHICH DOES NOT FIT AN INT ==
+== write one cluster ==
+wrote 1048576/1048576 bytes at offset 0
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+== leave 3 GiB of unused space behind it ==
+== closing the image truncates it without complaining ==
+read 1048576/1048576 bytes at offset 0
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+file size: 2097152
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
== TEST A DUPLICATE IN THE LAST ALLOCATED BAT ENTRY ==
== write two clusters ==
wrote 1048576/1048576 bytes at offset 0
--
2.53.0
next prev parent reply other threads:[~2026-09-11 1:13 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-10 23:41 [PULL 00/29] parallels: persistent dirty bitmaps and Format Extension hardening Denis V. Lunev
2026-09-10 23:41 ` [PULL 01/29] parallels: fix out-of-bounds read in format extension parsing Denis V. Lunev
2026-09-10 23:41 ` [PULL 02/29] parallels: validate dirty bitmap granularity Denis V. Lunev
2026-09-10 23:41 ` [PULL 03/29] parallels: bound the bitmap L1 table against the bitmap size Denis V. Lunev
2026-09-10 23:41 ` [PULL 04/29] parallels: reject a Format Extension outside the image file Denis V. Lunev
2026-09-10 23:41 ` [PULL 05/29] parallels: allocate the Format Extension cluster gracefully Denis V. Lunev
2026-09-10 23:41 ` [PULL 06/29] parallels: fix GSList leak on the format extension success path Denis V. Lunev
2026-09-10 23:42 ` [PULL 07/29] iotests: cover the Parallels format extension parser Denis V. Lunev
2026-09-10 23:42 ` [PULL 08/29] parallels: Set s->used_bmap to NULL in parallels_free_used_bitmap() Denis V. Lunev
2026-09-10 23:42 ` [PULL 09/29] parallels: split inactivation out and add the activation counterpart Denis V. Lunev
2026-09-10 23:42 ` [PULL 10/29] iotests: cover inactivating a read-only node Denis V. Lunev
2026-09-10 23:42 ` [PULL 11/29] parallels: Make mark_used() a global function Denis V. Lunev
2026-09-10 23:42 ` [PULL 12/29] parallels: Limit search in parallels_mark_used to the last marked cluster Denis V. Lunev
2026-09-10 23:42 ` [PULL 13/29] parallels: Move host clusters allocation to a separate function Denis V. Lunev
2026-09-10 23:42 ` [PULL 14/29] parallels: do not let the check die on what it is meant to report Denis V. Lunev
2026-09-10 23:42 ` [PULL 15/29] parallels: Create used bitmap even if checks needed Denis V. Lunev
2026-09-10 23:42 ` [PULL 16/29] parallels: Drop unused clusters at the end of the image Denis V. Lunev
2026-09-10 23:42 ` [PULL 17/29] parallels: Remove unnecessary data_end field Denis V. Lunev
2026-09-10 23:42 ` [PULL 18/29] parallels: Add dirty bitmaps saving Denis V. Lunev
2026-09-10 23:42 ` [PULL 19/29] parallels: Let image extensions work in RW mode Denis V. Lunev
2026-09-10 23:42 ` [PULL 20/29] parallels: Handle L1 entries equal to one Denis V. Lunev
2026-09-10 23:42 ` [PULL 21/29] iotests: cover the Format Extension against the leak check Denis V. Lunev
2026-09-10 23:42 ` [PULL 22/29] iotests: run the persistent dirty bitmap test on parallels Denis V. Lunev
2026-09-10 23:42 ` [PULL 23/29] parallels: reject a bitmap L1 entry outside the data area Denis V. Lunev
2026-09-10 23:42 ` [PULL 24/29] parallels: do not trust the bitmaps of an image which was not closed Denis V. Lunev
2026-09-10 23:42 ` [PULL 25/29] parallels: implement removing a stored dirty bitmap Denis V. Lunev
2026-09-10 23:42 ` [PULL 26/29] iotests: rename parallels-read-bitmap to parallels-bitmap Denis V. Lunev
2026-09-10 23:42 ` Denis V. Lunev [this message]
2026-09-10 23:42 ` [PULL 28/29] tests: Turned on 256, 299, 304 and block-status-cache for parallels format Denis V. Lunev
2026-09-10 23:42 ` [PULL 29/29] tests: Add parallels format support to image-fleecing Denis V. Lunev
2026-09-11 10:51 ` [PULL 00/29] parallels: persistent dirty bitmaps and Format Extension hardening Richard Henderson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260910234222.3039975-28-den@openvz.org \
--to=den@openvz.org \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=stefanha@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.