[Devel] [PATCH hci-8.1 1/3] dm-qcow2: fix a failed refcount table relocation leaking md pages

no-reply at sashiko.virtuozzo.com no-reply at sashiko.virtuozzo.com
Tue Nov 3 11:45:24 MSK 2026


Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] dm-qcow2: relocate_refcount_table() error path frees md pages still queued on wb lists
--

commit de2ba3b61c5585914bdb1778601206847fadbda6
Author: Denis V. Lunev <den at openvz.org>

dm-qcow2: fix a failed refcount table relocation leaking md pages

This commit changes the error path of relocate_refcount_table() so that it
frees exactly the md pages the allocation loop inserted. Previously it freed
only one page per cluster starting from end. The new range is
[new_md_start, new_md_end) in PAGE_SIZE steps.

> 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

[ ... ]

> @@ -2419,7 +2421,7 @@ static int relocate_refcount_table(struct qcow2 *qcow2, struct qio **qio)
>  	return 1;
>  
>  err_free_r2_pages:
> -	for (i = end; i < r2_end; i += clu_size) {
> +	for (i = new_md_start; i < new_md_end; i += PAGE_SIZE) {
>  		ret = handle_md_page(qcow2, i >> PAGE_SHIFT, NULL, &md);
>  		if (ret <= 0)
>  			break;
>  		spin_lock_irq(&qcow2->md_pages_lock);
>  		qcow2_md_page_erase(qcow2, md);
>  		spin_unlock_irq(&qcow2->md_pages_lock);
>  		qcow2_free_md_page(md);

[Severity: Medium]
Can this cleanup free md pages that are still linked on a writeback list?

The loop now frees every page in the inserted range. That range includes
the last new-R1 cluster pages and all pages of multi-page refblock
clusters. The loop calls qcow2_md_page_erase() and qcow2_free_md_page(),
but it never does list_del(&md->wb_link) and never clears MD_DIRTY.

qcow2_md_page_erase() only does rb_erase(). qcow2_free_md_page() only does
WARN_ON_ONCE(md->wbd || md->lockd), put_page() and kfree().

Pages in this range can be dirtied before the error label is reached.
mark_cluster_used() calls qcow2_md_make_dirty() on the new R2 pages:

mark_cluster_used()
    set_r2_entry(qcow2, r2_md, r2_index_in_page, 1);
    ...
    qcow2_md_make_dirty(qcow2, r2_md, true);

qcow2_md_make_dirty() then links the page onto the writeback list:

    if (!(md->status & MD_DIRTY)) {
        md->status |= MD_DIRTY;
        list_add_tail(&md->wb_link, head);

Consider a failure in the mark-unused loop after the mark-used loop has
run:

relocate_refcount_table()
    mark_cluster_used()            /* new R2 pages dirty and queued */
    qcow2_handle_r1r2_maps() ...   /* WARN_ON_ONCE(ret <= 0) */
    goto err_free_r2_pages

The same applies to the other late gotos after the header write: the
renumber failure, the calc_refcounters_map() < 0 case, and the
WARN_ON_ONCE(ret <= 0) checks in the connect and mark-used loops.

The freed md pages would then remain linked on slow_wb_batch_list or
wb_batch_list. When the writeback worker runs, it would walk the stale
entries:

submit_metadata_writeback()
    md = list_first_entry_or_null(&qcow2->wb_batch_list,
                                  struct md_page, wb_link);
    ...
    list_del_init(&md->wb_link);

Could that be a use-after-free or list corruption?

I could not construct a concrete trigger, since these paths are guarded by
WARN_ON_ONCE. The allocation-failure and header-write-failure cases named
in the commit message are fine, because no page in the range is dirty yet.

The cached hdr.refcount_table_offset and hdr.refcount_table_clusters are
also not rolled back at this label, and the on-disk header has already been
rewritten. The existing "/* TODO: switch to RO */" comment covers this.

Should the cleanup unlink wb_link and clear MD_DIRTY for each page before
freeing it?

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261103083708.131379-1-den@openvz.org?part=1



More information about the Devel mailing list