[Devel] [PATCH hci-8.1 v11 10/10] dm-qcow2: truncate unused tail of image on suspend

no-reply at sashiko.virtuozzo.com no-reply at sashiko.virtuozzo.com
Mon Nov 9 17:18:02 MSK 2026


Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] dm-qcow2: unused-tail truncation on suspend trusts refcounts with no kernel-side guard
- [Medium] dm-qcow2: GFP_KERNEL md page reads in postsuspend can deadlock on reclaim
--

commit 74d743673c019d9879114318e501a9a5de73c937
Author: Alexander Ivanov <alexander.ivanov at virtuozzo.com>

dm-qcow2: truncate unused tail of image on suspend

This patch adds qcow2_calc_used_end(), which scans the refcount blocks backward from file_preallocated_area_start. It returns the end of the last cluster with a non-zero refcount. qcow2_truncate_unused_tail() now truncates the image file to that point on postsuspend. It falls back to the old behaviour on scan errors, after a metadata writeback error, and for dirty images when kernel_sets_dirty_bit is set.

> diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
> --- a/drivers/md/dm-qcow2-map.c
> +++ b/drivers/md/dm-qcow2-map.c

[ ... ]

> +loff_t qcow2_calc_used_end(struct qcow2 *qcow2)
> +{

[ ... ]

> +		if (!r1_md || r1_md->id != r1.page_id) {
> +			ret = qcow2_md_page_read_sync(qcow2, r1.page_id, &r1_md);
> +			if (ret)
> +				return ret;
> +		}

[ ... ]

> +		ret = qcow2_md_page_read_sync(qcow2, r2.page_id, &r2.md);
> +		if (ret)
> +			return ret;
> +
> +		for (i = r2.index_in_page; i >= 0; i--, pos -= clu_size) {
> +			if (get_r2_entry(qcow2, r2.md, i))
> +				return min_t(loff_t, pos + clu_size,
> +					     qcow2->file_preallocated_area_start);
> +		}

[Severity: Medium]
Can this scan allocate memory in a way that deadlocks under reclaim? qcow2_calc_used_end() is called from the postsuspend hook:

qcow2_postsuspend()
  qcow2_truncate_unused_tail()
    qcow2_calc_used_end()
      qcow2_md_page_read_sync()
        qcow2_alloc_and_insert_md_page()
          kmalloc(sizeof(**md), GFP_KERNEL)
          alloc_page(GFP_KERNEL)

Every R1 or R2 page that is not already in the md cache is read this way, and then read from the image file. Neither the dm core nor the driver sets a memalloc_noio or memalloc_nofs scope around postsuspend.

Direct reclaim from these GFP_KERNEL allocations could issue writeback to a filesystem or swap on this same suspended device, or on a device stacked on it. That I/O cannot complete until resume, and resume waits for postsuspend.

Before this patch, postsuspend only used the already cached page 0 through qcow2_set_image_file_features(). The other allocations in the I/O and metadata paths use GFP_NOIO or run under PF_MEMALLOC_NOIO.

Should the scan run under a memalloc_noio_save()/memalloc_noio_restore() scope, or use GFP_NOIO allocations?

> diff --git a/drivers/md/dm-qcow2-target.c b/drivers/md/dm-qcow2-target.c
> --- a/drivers/md/dm-qcow2-target.c
> +++ b/drivers/md/dm-qcow2-target.c

[ ... ]

> +	if (tgt->md_writeback_error) {
> +		QC_INFO(ti, "Skip shrinking to used end after md writeback error");
> +	} else if (kernel_sets_dirty_bit && qcow2->opened_dirty) {
> +		QC_INFO(ti, "Skip shrinking to used end of dirty image");
> +	} else {
> +		used_end = qcow2_calc_used_end(qcow2);

[Severity: Medium]
Can this truncation cut off live guest data? The used end is derived from refcounts alone, with no cross-check against L2. If a cluster has refcount zero but is still referenced by L2, the cut point moves below live data and qcow2_truncate_safe() discards it permanently.

The commit message says refcounts may be below real usage after a crash, since both QEMU with lazy_refcounts and this driver write L2 before refcounts. The only guards here are md_writeback_error and kernel_sets_dirty_bit && opened_dirty.

A v2 image has no dirty bit. qcow2_check_convert_hdr() returns early for version 2, so opened_dirty stays false.

In the default mode kernel_sets_dirty_bit is false, so the opened_dirty guard is skipped.

The ctr-time check in qcow2_check_convert_hdr() is commented out:

//	if (!is_ro && kernel_sets_dirty_bit !=
//	    !(hdr->incompatible_features & INCOMPATIBLE_FEATURES_DIRTY_BIT))
//		return kernel_sets_dirty_bit ? -EUCLEAN : -ENOLCK;

qcow2_wants_check() covers only md_writeback_error and truncate_error, which are errors from the current session. Nothing in the driver detects an on-disk inconsistency left by an earlier crash.

The commit message says the scan is safe "only because userspace checks the image before an RW open". Can the driver verify that assumption?

Would it be possible to refuse the truncation when the cut range is still referenced by L2, or to make the shrink opt-in?

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261109140143.1408038-2-alexander.ivanov@virtuozzo.com?part=10



More information about the Devel mailing list