]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: reduce overlap and respect area when pruning parent extents
authorIlya Dryomov <idryomov@gmail.com>
Sat, 15 Oct 2022 16:31:45 +0000 (18:31 +0200)
committerIlya Dryomov <idryomov@gmail.com>
Sun, 4 Dec 2022 17:19:19 +0000 (18:19 +0100)
DATA area in the parent may be smaller than the part of DATA area in
the clone that is still within the overlap.  This would occur e.g. in
LUKS2-formatted parent + LUKS1-formatted clone case, due to LUKS2
header usually being bigger than LUKS1 header:

parent: raw size = 64M
        LUKS2 header area = 16M
        data area = 48M

clone:  raw size = 64M (raw overlap 64M)
        LUKS1 header area = 4M
        data area = 60M

Currently, because parent extents are pruned only according to raw
overlap (64M), the clone ends up attempting to reach the parent for all
of its data area (60M < 64M) even though the parent only has 48M worth
of data.  All kinds of bugs ensue for 48M..60M offsets and this range
basically becomes inaccessible to the user.

A related issue is that prune_parent_extents() ignores area.

Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
12 files changed:
src/librbd/ImageCtx.cc
src/librbd/ImageCtx.h
src/librbd/cache/ObjectCacherWriteback.cc
src/librbd/deep_copy/ObjectCopyRequest.cc
src/librbd/io/CopyupRequest.cc
src/librbd/io/ObjectRequest.cc
src/librbd/io/Utils.cc
src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc
src/test/librbd/io/test_mock_CopyupRequest.cc
src/test/librbd/io/test_mock_ObjectRequest.cc
src/test/librbd/mock/MockImageCtx.h
src/test/librbd/test_librbd.cc

index 8f6413d0e405bf3a7edfc2d2f46e6c8200c5002d..01b7847c0861f6e29a79132ca75be14e16583d1f 100644 (file)
@@ -545,15 +545,15 @@ librados::IoCtx duplicate_io_ctx(librados::IoCtx& io_ctx) {
       return 0;
     }
 
-    auto [data_size, data_area] = io::util::raw_to_area_offset(*this, raw_size);
-    ceph_assert(data_size <= raw_size && data_area == io::ImageArea::DATA);
+    auto size = io::util::raw_to_area_offset(*this, raw_size);
+    ceph_assert(size.first <= raw_size && size.second == io::ImageArea::DATA);
 
     switch (area) {
     case io::ImageArea::DATA:
-      return data_size;
+      return size.first;
     case io::ImageArea::CRYPTO_HEADER:
       // CRYPTO_HEADER area ends where DATA area begins
-      return raw_size - data_size;
+      return raw_size - size.first;
     default:
       ceph_abort();
     }
@@ -701,48 +701,74 @@ librados::IoCtx duplicate_io_ctx(librados::IoCtx& io_ctx) {
     if (migration_write) {
       // don't reduce migration write overlap -- it may be larger as
       // it's the largest overlap across snapshots by construction
-      return io::util::remap_offset(*this, raw_overlap);
+      return io::util::raw_to_area_offset(*this, raw_overlap);
     }
     if (raw_overlap == 0 || parent == nullptr) {
       // image opened with OPEN_FLAG_SKIP_OPEN_PARENT -> no overlap
-      return io::util::remap_offset(*this, 0);
+      return io::util::raw_to_area_offset(*this, 0);
     }
     // DATA area in the parent may be smaller than the part of DATA
     // area in the clone that is still within the overlap (e.g. for
     // LUKS2-encrypted parent + LUKS1-encrypted clone, due to LUKS2
     // header usually being bigger than LUKS1 header)
-    auto overlap = io::util::remap_offset(*this, raw_overlap);
+    auto overlap = io::util::raw_to_area_offset(*this, raw_overlap);
     std::shared_lock parent_image_locker(parent->image_lock);
     overlap.first = std::min(overlap.first,
                              parent->get_area_size(overlap.second));
     return overlap;
   }
 
-  void ImageCtx::register_watch(Context *on_finish) {
-    ceph_assert(image_watcher != NULL);
-    image_watcher->register_watch(on_finish);
-  }
+  uint64_t ImageCtx::prune_parent_extents(io::Extents& image_extents,
+                                          io::ImageArea area,
+                                          uint64_t raw_overlap,
+                                          bool migration_write) const {
+    ceph_assert(ceph_mutex_is_locked(image_lock));
+    ldout(cct, 10) << __func__ << ": image_extents=" << image_extents
+                   << " area=" << area << " raw_overlap=" << raw_overlap
+                   << " migration_write=" << migration_write << dendl;
+    if (raw_overlap == 0) {
+      image_extents.clear();
+      return 0;
+    }
 
-  uint64_t ImageCtx::prune_parent_extents(vector<pair<uint64_t,uint64_t> >& objectx,
-                                         uint64_t overlap)
-  {
-    // drop extents completely beyond the overlap
-    while (!objectx.empty() && objectx.back().first >= overlap)
-      objectx.pop_back();
+    auto overlap = reduce_parent_overlap(raw_overlap, migration_write);
+    if (area == overlap.second) {
+      // drop extents completely beyond the overlap
+      while (!image_extents.empty() &&
+             image_extents.back().first >= overlap.first) {
+        image_extents.pop_back();
+      }
+      if (!image_extents.empty()) {
+        // trim final overlapping extent
+        auto& last_extent = image_extents.back();
+        if (last_extent.first + last_extent.second > overlap.first) {
+          last_extent.second = overlap.first - last_extent.first;
+        }
+      }
+    } else if (area == io::ImageArea::DATA &&
+               overlap.second == io::ImageArea::CRYPTO_HEADER) {
+      // all extents completely beyond the overlap
+      image_extents.clear();
+    } else {
+      // all extents completely within the overlap
+      ceph_assert(area == io::ImageArea::CRYPTO_HEADER &&
+                  overlap.second == io::ImageArea::DATA);
+    }
 
-    // trim final overlapping extent
-    if (!objectx.empty() && objectx.back().first + objectx.back().second > overlap)
-      objectx.back().second = overlap - objectx.back().first;
+    uint64_t overlap_bytes = 0;
+    for (auto [_, len] : image_extents) {
+      overlap_bytes += len;
+    }
+    ldout(cct, 10) << __func__ << ": overlap=" << overlap.first
+                   << "/" << overlap.second
+                   << " got overlap_bytes=" << overlap_bytes
+                   << " at " << image_extents << dendl;
+    return overlap_bytes;
+  }
 
-    uint64_t len = 0;
-    for (vector<pair<uint64_t,uint64_t> >::iterator p = objectx.begin();
-        p != objectx.end();
-        ++p)
-      len += p->second;
-    ldout(cct, 10) << "prune_parent_extents image overlap " << overlap
-                  << ", object overlap " << len
-                  << " from image extents " << objectx << dendl;
-    return len;
+  void ImageCtx::register_watch(Context *on_finish) {
+    ceph_assert(image_watcher != NULL);
+    image_watcher->register_watch(on_finish);
   }
 
   void ImageCtx::cancel_async_requests() {
index 8c69615a7178fe3a0142ebd8b89c1cf26cf81390..9a432c764d542f489cf6dd0faabafa8e3906e86c 100644 (file)
@@ -326,9 +326,11 @@ namespace librbd {
                            uint64_t* raw_overlap) const;
     std::pair<uint64_t, io::ImageArea> reduce_parent_overlap(
         uint64_t raw_overlap, bool migration_write) const;
+    uint64_t prune_parent_extents(
+        std::vector<std::pair<uint64_t, uint64_t>>& image_extents,
+        io::ImageArea area, uint64_t raw_overlap, bool migration_write) const;
+
     void register_watch(Context *on_finish);
-    uint64_t prune_parent_extents(std::vector<std::pair<uint64_t,uint64_t> >& objectx,
-                                 uint64_t overlap);
 
     void cancel_async_requests();
     void cancel_async_requests(Context *on_finish);
index 008098f07b0e229a48463686304ed3fcb46026ee..97f2d46ba853e39e7e0419126e74bd66233df1b3 100644 (file)
@@ -160,19 +160,17 @@ bool ObjectCacherWriteback::may_copy_on_write(const object_t& oid,
                                               uint64_t read_len,
                                               snapid_t snapid)
 {
-  m_ictx->image_lock.lock_shared();
-  librados::snap_t snap_id = m_ictx->snap_id;
-  uint64_t overlap = 0;
-  m_ictx->get_parent_overlap(snap_id, &overlap);
-  m_ictx->image_lock.unlock_shared();
-
-  uint64_t object_no = oid_to_object_no(oid.name, m_ictx->object_prefix);
-
-  // reverse map this object extent onto the parent
-  auto [parent_extents, _] = io::util::object_to_area_extents(
-      m_ictx, object_no, {{0, m_ictx->layout.object_size}});
-  uint64_t object_overlap = m_ictx->prune_parent_extents(parent_extents,
-                                                         overlap);
+  std::shared_lock image_locker(m_ictx->image_lock);
+  uint64_t raw_overlap = 0;
+  uint64_t object_overlap = 0;
+  m_ictx->get_parent_overlap(m_ictx->snap_id, &raw_overlap);
+  if (raw_overlap > 0) {
+    uint64_t object_no = oid_to_object_no(oid.name, m_ictx->object_prefix);
+    auto [parent_extents, area] = io::util::object_to_area_extents(
+        m_ictx, object_no, {{0, m_ictx->layout.object_size}});
+    object_overlap = m_ictx->prune_parent_extents(parent_extents, area,
+                                                  raw_overlap, false);
+  }
   bool may = object_overlap > 0;
   ldout(m_ictx->cct, 10) << "may_copy_on_write " << oid << " " << read_off
                          << "~" << read_len << " = " << may << dendl;
index 8fa325aaea286eceed5405452f77e16bcf9ef131..e8b42b68f7deebdff610220bf76fed899df0e07e 100644 (file)
@@ -538,26 +538,21 @@ void ObjectCopyRequest<I>::compute_read_ops() {
     WriteReadSnapIds write_read_snap_ids{src_snap_seq, src_snap_seq};
 
     // prepare to prune the extents to the maximum parent overlap
-    m_src_image_ctx->image_lock.lock_shared();
-    uint64_t src_parent_overlap = 0;
-    int r = m_src_image_ctx->get_parent_overlap(src_snap_seq,
-                                                &src_parent_overlap);
-    m_src_image_ctx->image_lock.unlock_shared();
-
+    std::shared_lock image_locker(m_src_image_ctx->image_lock);
+    uint64_t raw_overlap = 0;
+    int r = m_src_image_ctx->get_parent_overlap(src_snap_seq, &raw_overlap);
     if (r < 0) {
       ldout(m_cct, 5) << "failed getting parent overlap for snap_id: "
                       << src_snap_seq << ": " << cpp_strerror(r) << dendl;
-    } else {
-      ldout(m_cct, 20) << "parent overlap=" << src_parent_overlap << dendl;
-      for (auto& [image_offset, image_length] : dne_image_interval) {
-        auto end_image_offset = std::min(
-          image_offset + image_length, src_parent_overlap);
-        if (image_offset >= end_image_offset) {
-          // starting offset is beyond the end of the parent overlap
-          continue;
-        }
-
-        image_length = end_image_offset - image_offset;
+    } else if (raw_overlap > 0) {
+      ldout(m_cct, 20) << "raw_overlap=" << raw_overlap << dendl;
+      io::Extents parent_extents;
+      for (auto [image_offset, image_length] : dne_image_interval) {
+        parent_extents.emplace_back(image_offset, image_length);
+      }
+      m_src_image_ctx->prune_parent_extents(parent_extents, m_image_area,
+                                            raw_overlap, false);
+      for (auto [image_offset, image_length] : parent_extents) {
         ldout(m_cct, 20) << "parent read op: "
                          << "snap_ids=" << write_read_snap_ids << " "
                          << image_offset << "~" << image_length << dendl;
@@ -672,24 +667,20 @@ void ObjectCopyRequest<I>::compute_zero_ops() {
 
     if (hide_parent) {
       std::shared_lock image_locker{m_dst_image_ctx->image_lock};
-      uint64_t parent_overlap = 0;
-      int r = m_dst_image_ctx->get_parent_overlap(dst_snap_seq,
-                                                  &parent_overlap);
+      uint64_t raw_overlap = 0;
+      uint64_t object_overlap = 0;
+      int r = m_dst_image_ctx->get_parent_overlap(dst_snap_seq, &raw_overlap);
       if (r < 0) {
         ldout(m_cct, 5) << "failed getting parent overlap for snap_id: "
                         << dst_snap_seq << ": " << cpp_strerror(r) << dendl;
+      } else if (raw_overlap > 0) {
+        auto parent_extents = m_image_extents;
+        object_overlap = m_dst_image_ctx->prune_parent_extents(
+            parent_extents, m_image_area, raw_overlap, false);
       }
-      if (parent_overlap == 0) {
+      if (object_overlap == 0) {
         ldout(m_cct, 20) << "no parent overlap" << dendl;
         hide_parent = false;
-      } else {
-        auto image_extents = m_image_extents;
-        uint64_t overlap = m_dst_image_ctx->prune_parent_extents(
-          image_extents, parent_overlap);
-        if (overlap == 0) {
-          ldout(m_cct, 20) << "no parent overlap" << dendl;
-          hide_parent = false;
-        }
       }
     }
 
index 220566c64cc5629f17bf7ca5a277dcb68416e3c2..228f959801772dfdbb83463d415a4f63092117b6 100644 (file)
@@ -649,20 +649,19 @@ void CopyupRequest<I>::compute_deep_copy_snap_ids() {
         return false;
       }
 
-      uint64_t parent_overlap = 0;
-      int r = m_image_ctx->get_parent_overlap(snap_id, &parent_overlap);
+      uint64_t raw_overlap = 0;
+      uint64_t object_overlap = 0;
+      int r = m_image_ctx->get_parent_overlap(snap_id, &raw_overlap);
       if (r < 0) {
         ldout(cct, 5) << "failed getting parent overlap for snap_id: "
                       << snap_id << ": " << cpp_strerror(r) << dendl;
+      } else if (raw_overlap > 0) {
+        auto [parent_extents, area] = util::object_to_area_extents(
+            m_image_ctx, m_object_no, {{0, m_image_ctx->layout.object_size}});
+        object_overlap = m_image_ctx->prune_parent_extents(parent_extents, area,
+                                                           raw_overlap, false);
       }
-      if (parent_overlap == 0) {
-        return false;
-      }
-      auto [parent_extents, _] = util::object_to_area_extents(
-          m_image_ctx, m_object_no, {{0, m_image_ctx->layout.object_size}});
-      auto overlap = m_image_ctx->prune_parent_extents(parent_extents,
-                                                       parent_overlap);
-      return overlap > 0;
+      return object_overlap > 0;
     });
 }
 
index 9c27be813524c29e6ac9d09a5facc5ceb999a456..6d246cdf33fef2d7335e60f6f37197d7b20218c7 100644 (file)
@@ -153,9 +153,9 @@ bool ObjectRequest<I>::compute_parent_extents(Extents *parent_extents,
   parent_extents->clear();
   *area = ImageArea::DATA;
 
-  uint64_t parent_overlap;
+  uint64_t raw_overlap;
   int r = m_ictx->get_parent_overlap(
-    m_io_context->read_snap().value_or(CEPH_NOSNAP), &parent_overlap);
+      m_io_context->read_snap().value_or(CEPH_NOSNAP), &raw_overlap);
   if (r < 0) {
     // NOTE: it's possible for a snapshot to be deleted while we are
     // still reading from it
@@ -163,24 +163,20 @@ bool ObjectRequest<I>::compute_parent_extents(Extents *parent_extents,
                        << cpp_strerror(r) << dendl;
     return false;
   }
-
-  if (!read_request && !m_ictx->migration_info.empty()) {
-    parent_overlap = m_ictx->migration_info.overlap;
+  bool migration_write = !read_request && !m_ictx->migration_info.empty();
+  if (migration_write) {
+    raw_overlap = m_ictx->migration_info.overlap;
   }
-
-  if (parent_overlap == 0) {
+  if (raw_overlap == 0) {
     return false;
   }
 
   std::tie(*parent_extents, *area) = io::util::object_to_area_extents(
       m_ictx, m_object_no, {{0, m_ictx->layout.object_size}});
-  uint64_t object_overlap = m_ictx->prune_parent_extents(*parent_extents,
-                                                         parent_overlap);
+  uint64_t object_overlap = m_ictx->prune_parent_extents(
+      *parent_extents, *area, raw_overlap, migration_write);
   if (object_overlap > 0) {
-    ldout(m_ictx->cct, 20) << "overlap=" << parent_overlap
-                           << " extents=" << *parent_extents
-                           << " area=" << *area << dendl;
-    m_has_parent = !parent_extents->empty();
+    m_has_parent = true;
     return true;
   }
   return false;
@@ -959,19 +955,17 @@ void ObjectListSnapsRequest<I>::list_from_parent() {
     return;
   }
 
-  // calculate reverse mapping onto the parent image
   Extents parent_extents;
-  std::tie(parent_extents, m_image_area) = io::util::object_to_area_extents(
-      image_ctx, this->m_object_no, m_object_extents);
-
-  uint64_t parent_overlap = 0;
+  uint64_t raw_overlap = 0;
   uint64_t object_overlap = 0;
-  int r = image_ctx->get_parent_overlap(snap_id_end, &parent_overlap);
-  if (r == 0) {
-    object_overlap = image_ctx->prune_parent_extents(parent_extents,
-                                                     parent_overlap);
+  image_ctx->get_parent_overlap(snap_id_end, &raw_overlap);
+  if (raw_overlap > 0) {
+    // calculate reverse mapping onto the parent image
+    std::tie(parent_extents, m_image_area) = io::util::object_to_area_extents(
+        image_ctx, this->m_object_no, m_object_extents);
+    object_overlap = image_ctx->prune_parent_extents(
+        parent_extents, m_image_area, raw_overlap, false);
   }
-
   if (object_overlap == 0) {
     image_locker.unlock();
 
index a8cef0e41bb93111dc08393cb32ebb00332b2035..63d5872061367d377afe7ad3554c124a23c35252 100644 (file)
@@ -92,22 +92,22 @@ void read_parent(I *image_ctx, uint64_t object_no, ReadExtents* read_extents,
 
   std::shared_lock image_locker{image_ctx->image_lock};
 
-  // calculate reverse mapping onto the image
-  Extents extents;
-  for (const auto& extent : *read_extents) {
-    extents.emplace_back(extent.offset, extent.length);
-  }
-  auto [parent_extents, area] = object_to_area_extents(image_ctx, object_no,
-                                                       extents);
-
-  uint64_t parent_overlap = 0;
+  Extents parent_extents;
+  ImageArea area;
+  uint64_t raw_overlap = 0;
   uint64_t object_overlap = 0;
-  int r = image_ctx->get_parent_overlap(snap_id, &parent_overlap);
-  if (r == 0) {
-    object_overlap = image_ctx->prune_parent_extents(parent_extents,
-                                                     parent_overlap);
+  image_ctx->get_parent_overlap(snap_id, &raw_overlap);
+  if (raw_overlap > 0) {
+    // calculate reverse mapping onto the parent image
+    Extents extents;
+    for (const auto& extent : *read_extents) {
+      extents.emplace_back(extent.offset, extent.length);
+    }
+    std::tie(parent_extents, area) = object_to_area_extents(image_ctx,
+                                                            object_no, extents);
+    object_overlap = image_ctx->prune_parent_extents(parent_extents, area,
+                                                     raw_overlap, false);
   }
-
   if (object_overlap == 0) {
     image_locker.unlock();
 
index 38a1cbbdebcc6d1f8f3f0ffbf4825d8986031fd3..bfd29261628bdc25a317c0ed09d6602d93b938c7 100644 (file)
@@ -239,7 +239,7 @@ struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture {
   }
 
   void expect_prune_parent_extents(uint64_t object_overlap) {
-    EXPECT_CALL(*mock_image_ctx, prune_parent_extents(_, _))
+    EXPECT_CALL(*mock_image_ctx, prune_parent_extents(_, _, _, _))
             .WillOnce(Return(object_overlap));
   }
 
index b8a193619deacdcdfc1dd31e7612ed3c9582061a..a4fe54af2117c18988eed0fd680d7e43c623e7d1 100644 (file)
@@ -197,7 +197,7 @@ struct TestMockIoCopyupRequest : public TestMockFixture {
 
   void expect_prune_parent_extents(MockTestImageCtx& mock_image_ctx,
                                    uint64_t overlap, uint64_t object_overlap) {
-    EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap))
+    EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, _, overlap, _))
       .WillOnce(WithoutArgs(Invoke([object_overlap]() {
                               return object_overlap;
                             })));
index 503c83827d5a2e4d53e489e7d2c8c7fc3f05161f..0690b7722a0cb5aa487f4065f1738fd968bb7aa4 100644 (file)
@@ -196,7 +196,7 @@ struct TestMockIoObjectRequest : public TestMockFixture {
   void expect_prune_parent_extents(MockTestImageCtx &mock_image_ctx,
                                    const Extents& extents,
                                    uint64_t overlap, uint64_t object_overlap) {
-    EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap))
+    EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, _, overlap, _))
       .WillOnce(WithArg<0>(Invoke([extents, object_overlap](Extents& e) {
                              e = extents;
                              return object_overlap;
index a4c3fc0326ed884ee5e3eb60dc766d4fdf69cf7b..8da257f44ab0e5cf7e781a8a524d7e08b687ec88 100644 (file)
@@ -88,8 +88,9 @@ struct MockImageCtx {
                                              uint64_t *raw_overlap));
   MOCK_CONST_METHOD2(reduce_parent_overlap,
                      std::pair<uint64_t, io::ImageArea>(uint64_t, bool));
-  MOCK_CONST_METHOD2(prune_parent_extents, uint64_t(std::vector<std::pair<uint64_t,uint64_t> >& ,
-                                                    uint64_t));
+  MOCK_CONST_METHOD4(prune_parent_extents,
+                     uint64_t(std::vector<std::pair<uint64_t, uint64_t>>&,
+                              io::ImageArea, uint64_t, bool));
 
   MOCK_CONST_METHOD2(is_snap_protected, int(librados::snap_t in_snap_id,
                                             bool *is_protected));
index ec31b861b7aed4a20fe97c097a7d641e694d1903..359ea020321e51dfc71b6954a4b3b7f6fef599c4 100644 (file)
@@ -2531,6 +2531,158 @@ TEST_F(TestLibRBD, TestCloneEncryption)
   rados_ioctx_destroy(ioctx);
 }
 
+TEST_F(TestLibRBD, LUKS1UnderLUKS2WithoutResize)
+{
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+
+  librados::IoCtx ioctx;
+  ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx));
+
+  librbd::RBD rbd;
+  std::string parent_name = get_temp_image_name();
+  std::string clone_name = get_temp_image_name();
+  uint64_t data_size = 25 << 20;
+  uint64_t luks1_meta_size = 4 << 20;
+  uint64_t luks2_meta_size = 16 << 20;
+  std::string parent_passphrase = "parent passphrase";
+  std::string clone_passphrase = "clone passphrase";
+
+  {
+    int order = 22;
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(),
+                                 luks1_meta_size + data_size, &order));
+    librbd::Image parent;
+    ASSERT_EQ(0, rbd.open(ioctx, parent, parent_name.c_str(), nullptr));
+
+    librbd::encryption_luks1_format_options_t fopts = {
+        RBD_ENCRYPTION_ALGORITHM_AES256, parent_passphrase};
+    ASSERT_EQ(0, parent.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS1, &fopts,
+                                          sizeof(fopts)));
+
+    ceph::bufferlist bl;
+    bl.append(std::string(data_size, 'a'));
+    ASSERT_EQ(data_size, parent.write(0, data_size, bl));
+
+    ASSERT_EQ(0, parent.snap_create("snap"));
+    ASSERT_EQ(0, parent.snap_protect("snap"));
+    uint64_t features;
+    ASSERT_EQ(0, parent.features(&features));
+    ASSERT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "snap", ioctx,
+                           clone_name.c_str(), features, &order));
+  }
+
+  {
+    librbd::Image clone;
+    ASSERT_EQ(0, rbd.open(ioctx, clone, clone_name.c_str(), nullptr));
+
+    librbd::encryption_luks2_format_options_t fopts =
+        {RBD_ENCRYPTION_ALGORITHM_AES256, clone_passphrase};
+    ASSERT_EQ(0, clone.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, &fopts,
+                                         sizeof(fopts)));
+
+    librbd::encryption_luks_format_options_t opts1 = {parent_passphrase};
+    librbd::encryption_luks_format_options_t opts2 = {clone_passphrase};
+    librbd::encryption_spec_t specs[] = {
+        {RBD_ENCRYPTION_FORMAT_LUKS, &opts2, sizeof(opts2)},
+        {RBD_ENCRYPTION_FORMAT_LUKS, &opts1, sizeof(opts1)}};
+    ASSERT_EQ(0, clone.encryption_load2(specs, std::size(specs)));
+
+    uint64_t size;
+    ASSERT_EQ(0, clone.size(&size));
+    EXPECT_EQ(data_size + luks1_meta_size - luks2_meta_size, size);
+    uint64_t overlap;
+    ASSERT_EQ(0, clone.overlap(&overlap));
+    EXPECT_EQ(data_size + luks1_meta_size - luks2_meta_size, overlap);
+
+    ceph::bufferlist expected_bl;
+    expected_bl.append(std::string(
+        data_size + luks1_meta_size - luks2_meta_size, 'a'));
+
+    ceph::bufferlist read_bl;
+    ASSERT_EQ(expected_bl.length(),
+              clone.read(0, expected_bl.length(), read_bl));
+    EXPECT_TRUE(expected_bl.contents_equal(read_bl));
+  }
+}
+
+TEST_F(TestLibRBD, LUKS2UnderLUKS1WithoutResize)
+{
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+
+  librados::IoCtx ioctx;
+  ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx));
+
+  librbd::RBD rbd;
+  std::string parent_name = get_temp_image_name();
+  std::string clone_name = get_temp_image_name();
+  uint64_t data_size = 25 << 20;
+  uint64_t luks1_meta_size = 4 << 20;
+  uint64_t luks2_meta_size = 16 << 20;
+  std::string parent_passphrase = "parent passphrase";
+  std::string clone_passphrase = "clone passphrase";
+
+  {
+    int order = 22;
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(),
+                                 luks2_meta_size + data_size, &order));
+    librbd::Image parent;
+    ASSERT_EQ(0, rbd.open(ioctx, parent, parent_name.c_str(), nullptr));
+
+    librbd::encryption_luks2_format_options_t fopts = {
+        RBD_ENCRYPTION_ALGORITHM_AES256, parent_passphrase};
+    ASSERT_EQ(0, parent.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, &fopts,
+                                          sizeof(fopts)));
+
+    ceph::bufferlist bl;
+    bl.append(std::string(data_size, 'a'));
+    ASSERT_EQ(data_size, parent.write(0, data_size, bl));
+
+    ASSERT_EQ(0, parent.snap_create("snap"));
+    ASSERT_EQ(0, parent.snap_protect("snap"));
+    uint64_t features;
+    ASSERT_EQ(0, parent.features(&features));
+    ASSERT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "snap", ioctx,
+                           clone_name.c_str(), features, &order));
+  }
+
+  {
+    librbd::Image clone;
+    ASSERT_EQ(0, rbd.open(ioctx, clone, clone_name.c_str(), nullptr));
+
+    librbd::encryption_luks1_format_options_t fopts =
+        {RBD_ENCRYPTION_ALGORITHM_AES256, clone_passphrase};
+    ASSERT_EQ(0, clone.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS1, &fopts,
+                                         sizeof(fopts)));
+
+    librbd::encryption_luks_format_options_t opts1 = {parent_passphrase};
+    librbd::encryption_luks_format_options_t opts2 = {clone_passphrase};
+    librbd::encryption_spec_t specs[] = {
+        {RBD_ENCRYPTION_FORMAT_LUKS, &opts2, sizeof(opts2)},
+        {RBD_ENCRYPTION_FORMAT_LUKS, &opts1, sizeof(opts1)}};
+    ASSERT_EQ(0, clone.encryption_load2(specs, std::size(specs)));
+
+    uint64_t size;
+    ASSERT_EQ(0, clone.size(&size));
+    EXPECT_EQ(data_size + luks2_meta_size - luks1_meta_size, size);
+    uint64_t overlap;
+    ASSERT_EQ(0, clone.overlap(&overlap));
+    EXPECT_EQ(data_size, overlap);
+
+    ceph::bufferlist expected_bl;
+    expected_bl.append(std::string(data_size, 'a'));
+    expected_bl.append_zero(luks2_meta_size - luks1_meta_size);
+
+    ceph::bufferlist read_bl;
+    ASSERT_EQ(expected_bl.length(),
+              clone.read(0, expected_bl.length(), read_bl));
+    EXPECT_TRUE(expected_bl.contents_equal(read_bl));
+  }
+}
+
 TEST_F(TestLibRBD, EncryptionFormatNoData)
 {
   REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));