[Devel] [PATCH RHEL10 COMMIT] dm-qcow2: fix NULL deref in merge_backward complete without an active merge

Konstantin Khorenko khorenko at virtuozzo.com
Wed Aug 5 23:20:02 MSK 2026


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.2.vz10
------>
commit 0b0514cc0ada5c48fe5d239f54c3ba803d4fa4a1
Author: Konstantin Khorenko <khorenko at virtuozzo.com>
Date:   Mon Jul 6 12:59:59 2026 +0200

    dm-qcow2: fix NULL deref in merge_backward complete without an active merge
    
    qcow2_merge_backward_complete() loaded *tgt->backward_merge.pqcow2 in its
    variable initializer, before checking the merge state:
    
            struct qcow2 *qcow2 = *tgt->backward_merge.pqcow2, *i;
            ...
            if (tgt->backward_merge.state != BACKWARD_MERGE_WAIT_COMPLETION)
                    return -EBUSY;
    
    tgt->backward_merge.pqcow2 stays NULL until a "merge_backward start" message
    sets it, so a `dmsetup message <dev> 0 "merge_backward complete"` on a device
    that never started a backward merge dereferences NULL in the kernel and
    crashes the host (NULL-ptr deref at qcow2_merge_backward_complete+0x19,
    offset backward_merge(632)+pqcow2(72) = 0x2c0).
    Any unprivileged-of-the-VE holder of the dm control device can trigger it.
    
    Move the pqcow2 load below the state check. When the state really is
    BACKWARD_MERGE_WAIT_COMPLETION, pqcow2 was set by "start" under the same
    ctl_mutex and is never cleared while in that state, so the normal
    start->...->complete path is unchanged; the no-active-merge case now returns
    -EBUSY (the driver's existing "wrong merge state" convention) instead of
    oopsing. The sibling verbs (cancel/update_eventfd/progress) already guard
    their pqcow2 use behind the state and are not affected.
    
    Fixes: 11ba92733c99a ("dm-qcow2: allow specifying depth to merge intermediate images")
    Feature: dm-qcow2: block device over QCOW2 files driver
    https://virtuozzo.atlassian.net/browse/VSTOR-137234
    Signed-off-by: Konstantin Khorenko <khorenko at virtuozzo.com>
    Reviewed-by: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
---
 drivers/md/dm-qcow2-cmd.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/md/dm-qcow2-cmd.c b/drivers/md/dm-qcow2-cmd.c
index c15c46a0fe8b7..e1a94bc4a140c 100644
--- a/drivers/md/dm-qcow2-cmd.c
+++ b/drivers/md/dm-qcow2-cmd.c
@@ -366,14 +366,19 @@ void qcow2_merge_backward_work(struct work_struct *work)
 
 static int qcow2_merge_backward_complete(struct qcow2_target *tgt)
 {
-	struct qcow2 *qcow2 = *tgt->backward_merge.pqcow2, *i;
+	struct qcow2 *qcow2, *i;
 	int ret;
 
 	lockdep_assert_held(&tgt->ctl_mutex);
 
+	/*
+	 * .pqcow2 is NULL until the first "merge_backward start" and
+	 * is valid while the state is BACKWARD_MERGE_WAIT_COMPLETION.
+	 */
 	if (tgt->backward_merge.state != BACKWARD_MERGE_WAIT_COMPLETION)
 		return -EBUSY;
 
+	qcow2 = *tgt->backward_merge.pqcow2;
 	*tgt->backward_merge.pqcow2 = qcow2->lower;
 
 	i = tgt->top;


More information about the Devel mailing list