[Devel] [PATCH RHEL9 COMMIT] dm-qcow2: parse bitmap extension
Konstantin Khorenko
khorenko at virtuozzo.com
Mon Feb 24 22:55:12 MSK 2025
The commit is pushed to "branch-rh9-5.14.0-427.44.1.vz9.80.x-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh9-5.14.0-427.44.1.vz9.80.18
------>
commit 5990ec1f02a620b68af2541bb8164dff23016c53
Author: Andrey Zhadchenko <andrey.zhadchenko at virtuozzo.com>
Date: Thu Feb 20 15:51:11 2025 +0300
dm-qcow2: parse bitmap extension
Programs, which work with qcow2 files as writables, are
expected to reset autoclear_features bits which are unknown
to them. If qcow2 file has only inactive bitmaps, we may not
reset QCOW2_AUTOCLEAR_BITMAPS bit. Bitmaps are already allocated
and we are not expected to change them.
Still reset this bit if we encounter enabled bitmap.
qcow2 structures and constants imported from QEMU. Image file
structure reference can be found here:
https://gitlab.com/qemu-project/qemu/-/blob/master/docs/interop/qcow2.txt
https://virtuozzo.atlassian.net/browse/VSTOR-99404
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko at virtuozzo.com>
Reviewed-by: Alexander Atanasov <alexander.atanasov at virtuozzo.com>
Feature: dm-qcow2: block device over QCOW2 files driver
---
drivers/md/dm-qcow2-target.c | 134 ++++++++++++++++++++++++++++++++++++++++++-
drivers/md/dm-qcow2.h | 42 ++++++++++++++
2 files changed, 174 insertions(+), 2 deletions(-)
diff --git a/drivers/md/dm-qcow2-target.c b/drivers/md/dm-qcow2-target.c
index 1278a9af52b9..540c03cb3c44 100644
--- a/drivers/md/dm-qcow2-target.c
+++ b/drivers/md/dm-qcow2-target.c
@@ -554,7 +554,7 @@ static int qcow2_check_convert_hdr(struct dm_target *ti,
return 0;
hdr->incompatible_features = be64_to_cpu(raw_hdr->incompatible_features);
- hdr->autoclear_features = be64_to_cpu(raw_hdr->autoclear_features);
+ hdr->autoclear_features = 0;
hdr->refcount_order = be32_to_cpu(raw_hdr->refcount_order);
hdr->header_length = be32_to_cpu(raw_hdr->header_length);
@@ -630,7 +630,7 @@ int qcow2_set_image_file_features(struct qcow2 *qcow2, bool dirty)
return -EIO;
raw_hdr = kmap(md->page);
- qcow2->hdr.autoclear_features = raw_hdr->autoclear_features = 0;
+ raw_hdr->autoclear_features = cpu_to_be64(qcow2->hdr.autoclear_features);
if (kernel_sets_dirty_bit) {
if (dirty)
raw_hdr->incompatible_features |= dirty_mask;
@@ -708,12 +708,126 @@ static int qcow2_attach_file(struct dm_target *ti, struct qcow2_target *tgt,
}
ALLOW_ERROR_INJECTION(qcow2_attach_file, ERRNO);
+static int qcow2_parse_bitmap_ext(struct dm_target *ti, struct qcow2 *qcow2,
+ struct Qcow2BitmapHeaderExt *ext)
+{
+ struct Qcow2BitmapDirEntry entry;
+ u64 index, size, offset;
+ struct page **pages = NULL;
+ void *addr = NULL;
+ int i, ret;
+ bool clear_bitmaps = false;
+
+ ext->bitmap_directory_offset = be64_to_cpu(ext->bitmap_directory_offset);
+ ext->bitmap_directory_size = be64_to_cpu(ext->bitmap_directory_size);
+ ext->nb_bitmaps = be32_to_cpu(ext->nb_bitmaps);
+
+ if (!ext->nb_bitmaps ||
+ ext->nb_bitmaps > QCOW2_MAX_BITMAPS ||
+ ext->bitmap_directory_size > QCOW2_MAX_BITMAP_DIRECTORY_SIZE ||
+ ext->bitmap_directory_offset % PAGE_SIZE ||
+ ext->bitmap_directory_offset + ext->bitmap_directory_size > qcow2->file_size)
+ return -EINVAL;
+
+ index = ext->bitmap_directory_offset >> PAGE_SHIFT;
+ size = round_up(ext->bitmap_directory_size, PAGE_SIZE);
+
+ addr = kvmalloc(size, GFP_KERNEL);
+ if (!addr)
+ return -ENOMEM;
+
+ pages = kvmalloc_array(size / PAGE_SIZE, sizeof(*pages), GFP_KERNEL);
+ if (!pages) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < size >> PAGE_SHIFT; i++)
+ pages[i] = virt_to_page(addr + i * PAGE_SIZE);
+
+ ret = qcow2_rw_pages_sync(READ, qcow2, index, pages, size >> PAGE_SHIFT);
+ if (ret)
+ goto out;
+
+ offset = 0;
+ while (offset + sizeof(entry) <= ext->bitmap_directory_size) {
+ memcpy(&entry, addr + offset, sizeof(entry));
+
+ entry.flags = be32_to_cpu(entry.flags);
+ entry.name_size = be16_to_cpu(entry.name_size);
+ entry.extra_data_size = be32_to_cpu(entry.extra_data_size);
+
+ if (entry.flags & BME_FLAG_AUTO)
+ clear_bitmaps = true;
+
+ offset += round_up(sizeof(entry) + entry.name_size + entry.extra_data_size, 8);
+ }
+
+ if (!clear_bitmaps)
+ qcow2->hdr.autoclear_features |= QCOW2_AUTOCLEAR_BITMAPS;
+
+ ret = 0;
+out:
+ kvfree(pages);
+ kvfree(addr);
+ return ret;
+}
+
+static int qcow2_parse_extensions(struct dm_target *ti, struct qcow2 *qcow2,
+ loff_t start, loff_t end, void *addr)
+{
+ struct QCowExtension ext;
+ loff_t offset;
+ int ret;
+
+ offset = start;
+ while (offset + sizeof(ext) <= end) {
+ memcpy(&ext, (char *)addr + offset, sizeof(ext));
+ ext.magic = be32_to_cpu(ext.magic);
+ ext.len = be32_to_cpu(ext.len);
+ offset += sizeof(ext);
+
+ switch (ext.magic) {
+ case QCOW2_EXT_MAGIC_BITMAPS:
+ struct Qcow2BitmapHeaderExt bitmaps_ext;
+
+ if (ext.len != sizeof(bitmaps_ext)) {
+ QC_ERR(ti, "unexpected bitmap extension size\n");
+ return -EINVAL;
+ }
+
+ if (offset + sizeof(bitmaps_ext) > end) {
+ QC_ERR(ti, "unexpected end of extension area\n");
+ return -EINVAL;
+ }
+
+ memcpy(&bitmaps_ext, addr + offset, sizeof(bitmaps_ext));
+ ret = qcow2_parse_bitmap_ext(ti, qcow2, &bitmaps_ext);
+ if (ret) {
+ QC_ERR(ti, "unable to pase bitmap extension\n");
+ return ret;
+ }
+ break;
+ case QCOW2_EXT_MAGIC_END:
+ return 0;
+ default:
+ /* Skip all other extensions */
+ break;
+ }
+
+ offset += ((ext.len + 7) & ~7);
+ }
+
+ return 0;
+}
+
static int qcow2_parse_header(struct dm_target *ti, struct qcow2 *qcow2,
struct qcow2 *upper, bool is_bottom)
{
struct QCowHeader *raw_hdr, *hdr = &qcow2->hdr;
loff_t min_len, max_len, new_size;
struct file *file = qcow2->file;
+ loff_t ext_start, ext_end;
struct md_page *md;
int ret;
@@ -778,6 +892,22 @@ static int qcow2_parse_header(struct dm_target *ti, struct qcow2 *qcow2,
ret = -EFBIG;
if (qcow2->reftable_max_file_size < qcow2->file_size)
goto out;
+
+ ext_start = hdr->header_length;
+ if (hdr->backing_file_offset)
+ ext_end = hdr->backing_file_offset;
+ else
+ ext_end = PAGE_SIZE;
+
+ if (ext_end > PAGE_SIZE) {
+ QC_INFO(ti, "extensions are too long, skipping them\n");
+ } else {
+ ret = qcow2_parse_extensions(ti, qcow2, ext_start, ext_end, raw_hdr);
+ if (ret) {
+ QC_ERR(ti, "failed to parse extensions\n");
+ goto out;
+ }
+ }
ret = 0;
out:
return ret;
diff --git a/drivers/md/dm-qcow2.h b/drivers/md/dm-qcow2.h
index 7efd49ffae72..a89fe3db2196 100644
--- a/drivers/md/dm-qcow2.h
+++ b/drivers/md/dm-qcow2.h
@@ -43,6 +43,9 @@ struct QCowHeader {
#define INCOMPATIBLE_FEATURES_EXTL2_BIT (1 << 4)
uint64_t incompatible_features;
uint64_t compatible_features;
+
+#define QCOW2_AUTOCLEAR_BITMAPS (1 << 0)
+#define QCOW2_AUTOCLEAR_DATA_FILE_RAW (1 << 1)
uint64_t autoclear_features;
uint32_t refcount_order;
@@ -57,6 +60,45 @@ struct QCowHeader {
uint8_t padding[7];
} __packed;
+#define QCOW2_EXT_MAGIC_END 0
+#define QCOW2_EXT_MAGIC_BITMAPS 0x23852875
+
+struct QCowExtension {
+ uint32_t magic;
+ uint32_t len;
+} __packed;
+
+/* Bitmap header extension constraints */
+#define QCOW2_MAX_BITMAPS 65535
+#define QCOW2_MAX_BITMAP_DIRECTORY_SIZE (1024 * QCOW2_MAX_BITMAPS)
+
+struct Qcow2BitmapHeaderExt {
+ uint32_t nb_bitmaps;
+ uint32_t reserved32;
+ uint64_t bitmap_directory_size;
+ uint64_t bitmap_directory_offset;
+} __packed;
+
+/* Bitmap directory entry flags */
+#define BME_RESERVED_FLAGS 0xfffffffcU
+#define BME_FLAG_IN_USE (1U << 0)
+#define BME_FLAG_AUTO (1U << 1)
+
+struct Qcow2BitmapDirEntry {
+ /* header is 8 byte aligned */
+ uint64_t bitmap_table_offset;
+
+ uint32_t bitmap_table_size;
+ uint32_t flags;
+
+ uint8_t type;
+ uint8_t granularity_bits;
+ uint16_t name_size;
+ uint32_t extra_data_size;
+ /* extra data follows */
+ /* name follows */
+} __packed;
+
struct wb_desc {
struct md_page *md;
#define LX_INDEXES_PER_PAGE (PAGE_SIZE / sizeof(u64))
More information about the Devel
mailing list