From: Kefu Chai Date: Sun, 21 Jun 2026 23:44:20 +0000 (+0800) Subject: librbd: fix use-after-free releasing object map locks in deep copy X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=ecd883a1b3924b981042df3903cb1dc6b55d5920;p=ceph.git librbd: fix use-after-free releasing object map locks in deep copy DeepCopyRequest::send_copy_object_map() holds the destination image's owner_lock and image_lock (shared), issues an asynchronous object_map->rollback(), and then releases the two locks through m_dst_image_ctx. The rollback callback drives the rest of the request to completion (handle_copy_object_map() ... finish() -> put()) on other threads. That path needs image_lock, so it only proceeds once this method releases it, and can then run to completion and free 'this' before the following owner_lock.unlock_shared() executes, dereferencing the freed DeepCopyRequest. ASan reported this as a heap-use-after-free in TestImageReplayer.StartReplayAndWrite: the object was freed on the finisher thread (handle_copy_metadata() -> finish()) while the io_context thread was still in send_copy_object_map(). The destination image ctx outlives the request, so operate through a local reference to it and release the locks through that reference rather than through 'this', as operation/SnapshotRollbackRequest::send_rollback_object_map() already does. Keep the explicit lock_shared()/unlock_shared() calls rather than scoped std::shared_lock guards: unlike operation/SnapshotRollbackRequest::send_rollback_object_map(), send_copy_object_map() has four exit paths (send_copy_metadata(), send_refresh_object_map(), finish(), and the object map rollback), each of which must release the locks before running a different continuation, so RAII guards would push that continuation out of the locked scope and read less clearly than unlocking at each exit. Fixes: https://tracker.ceph.com/issues/77551 Signed-off-by: Kefu Chai --- diff --git a/src/librbd/DeepCopyRequest.cc b/src/librbd/DeepCopyRequest.cc index 6878f145f26..934dad4a4ba 100644 --- a/src/librbd/DeepCopyRequest.cc +++ b/src/librbd/DeepCopyRequest.cc @@ -197,36 +197,38 @@ void DeepCopyRequest::handle_copy_image(int r) { template void DeepCopyRequest::send_copy_object_map() { - m_dst_image_ctx->owner_lock.lock_shared(); - m_dst_image_ctx->image_lock.lock_shared(); - - if (!m_dst_image_ctx->test_features(RBD_FEATURE_OBJECT_MAP, - m_dst_image_ctx->image_lock)) { - m_dst_image_ctx->image_lock.unlock_shared(); - m_dst_image_ctx->owner_lock.unlock_shared(); + // local ref so the unlocks don't touch 'this', which rollback() may free + I &dst_image_ctx = *m_dst_image_ctx; + dst_image_ctx.owner_lock.lock_shared(); + dst_image_ctx.image_lock.lock_shared(); + + if (!dst_image_ctx.test_features(RBD_FEATURE_OBJECT_MAP, + dst_image_ctx.image_lock)) { + dst_image_ctx.image_lock.unlock_shared(); + dst_image_ctx.owner_lock.unlock_shared(); send_copy_metadata(); return; } if (m_src_snap_id_end == CEPH_NOSNAP) { - m_dst_image_ctx->image_lock.unlock_shared(); - m_dst_image_ctx->owner_lock.unlock_shared(); + dst_image_ctx.image_lock.unlock_shared(); + dst_image_ctx.owner_lock.unlock_shared(); send_refresh_object_map(); return; } - ceph_assert(m_dst_image_ctx->object_map != nullptr); + ceph_assert(dst_image_ctx.object_map != nullptr); ldout(m_cct, 20) << dendl; Context *finish_op_ctx = nullptr; int r; - if (m_dst_image_ctx->exclusive_lock != nullptr) { - finish_op_ctx = m_dst_image_ctx->exclusive_lock->start_op(&r); + if (dst_image_ctx.exclusive_lock != nullptr) { + finish_op_ctx = dst_image_ctx.exclusive_lock->start_op(&r); } if (finish_op_ctx == nullptr) { lderr(m_cct) << "lost exclusive lock" << dendl; - m_dst_image_ctx->image_lock.unlock_shared(); - m_dst_image_ctx->owner_lock.unlock_shared(); + dst_image_ctx.image_lock.unlock_shared(); + dst_image_ctx.owner_lock.unlock_shared(); finish(r); return; } @@ -238,9 +240,9 @@ void DeepCopyRequest::send_copy_object_map() { }); ceph_assert(m_snap_seqs->count(m_src_snap_id_end) > 0); librados::snap_t copy_snap_id = (*m_snap_seqs)[m_src_snap_id_end]; - m_dst_image_ctx->object_map->rollback(copy_snap_id, ctx); - m_dst_image_ctx->image_lock.unlock_shared(); - m_dst_image_ctx->owner_lock.unlock_shared(); + dst_image_ctx.object_map->rollback(copy_snap_id, ctx); + dst_image_ctx.image_lock.unlock_shared(); + dst_image_ctx.owner_lock.unlock_shared(); } template