]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: make diff-iterate in fast-diff mode aware of encryption 58342/head
authorIlya Dryomov <idryomov@gmail.com>
Thu, 20 Jun 2024 19:13:56 +0000 (21:13 +0200)
committerIlya Dryomov <idryomov@gmail.com>
Fri, 12 Jul 2024 21:02:53 +0000 (23:02 +0200)
diff-iterate wasn't updated when librbd was being prepared to support
encryption in commit 8d6a47933269 ("librbd: add crypto image dispatch
layer").  This is even noted in [1]:

> The two places I skipped for now are DiffIterate and TrimRequest.

CryptoImageDispatch has since been removed, but diff-iterate in
fast-diff mode is still unaware of encryption and just assumes that all
offsets are raw.  This means that the callback gets invoked with
incorrect image offsets when encryption is loaded.  For example, for
a LUKS1-formatted image with some data at offsets 0 and 20971520,
diff-iterate with encryption loaded reports

  0~4194304
  4194304~4194304
  25165824~4194304

instead of

  0~4194304
  20971520~4194304

as "exists".  For any piece of code that is using diff-iterate to
optimize block-by-block processing (e.g. copy an encrypted source image
to a differently-encrypted destination image), this is fatal: it would
skip processing block 20971520 which has data and instead process block
25165824 which doesn't have any data and was to be skipped, producing
a corrupted destination image.

[1] https://github.com/ceph/ceph/pull/37935#issue-735278403

Fixes: https://tracker.ceph.com/issues/66570
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
(cherry picked from commit cdeb0efce3f9f857ad6d5b7ff3965f3292cb571a)

Conflicts:
src/librbd/api/DiffIterate.cc [ ImageArea support not in
  quincy ]
src/test/librbd/test_librbd.cc [ commit 4a5a0a5dd82b ("librbd:
  add cloned images encryption API") not in quincy

src/librbd/api/DiffIterate.cc
src/test/librbd/test_librbd.cc

index 4655f643ba686b2407ec78103535a6acac42e85b..3249712a49b1fb8b34584ebf0b2ff788b0519d40 100644 (file)
@@ -10,6 +10,7 @@
 #include "librbd/internal.h"
 #include "librbd/io/AioCompletion.h"
 #include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/Utils.h"
 #include "librbd/object_map/DiffRequest.h"
 #include "include/rados/librados.hpp"
 #include "include/interval_set.h"
@@ -275,15 +276,15 @@ std::pair<uint64_t, uint64_t> DiffIterate<I>::calc_object_diff_range() {
   if (first_period_off != last_period_off) {
     // map only the tail of the first period and the front of the last
     // period instead of the entire range for efficiency
-    Striper::file_to_extents(m_image_ctx.cct, &m_image_ctx.layout,
-                             m_offset, first_period_off + period - m_offset,
-                             0, 0, &object_extents);
-    Striper::file_to_extents(m_image_ctx.cct, &m_image_ctx.layout,
-                        last_period_off, m_offset + m_length - last_period_off,
-                        0, 0, &object_extents);
+    io::util::file_to_extents(&m_image_ctx, m_offset,
+                              first_period_off + period - m_offset, 0,
+                              &object_extents);
+    io::util::file_to_extents(&m_image_ctx, last_period_off,
+                              m_offset + m_length - last_period_off, 0,
+                              &object_extents);
   } else {
-    Striper::file_to_extents(m_image_ctx.cct, &m_image_ctx.layout, m_offset,
-                             m_length, 0, 0, &object_extents);
+    io::util::file_to_extents(&m_image_ctx, m_offset, m_length, 0,
+                              &object_extents);
   }
   return {object_extents.front().object_no, object_extents.back().object_no + 1};
 }
@@ -380,47 +381,43 @@ int DiffIterate<I>::execute() {
     uint64_t read_len = std::min(period_off + period - off, left);
 
     if (fast_diff_enabled) {
-      // map to extents
-      std::map<object_t,std::vector<ObjectExtent> > object_extents;
-      Striper::file_to_extents(cct, m_image_ctx.format_string,
-                               &m_image_ctx.layout, off, read_len, 0,
-                               object_extents, 0);
+      // map to objects (there would be one extent per object)
+      striper::LightweightObjectExtents object_extents;
+      io::util::file_to_extents(&m_image_ctx, off, read_len, 0,
+                                &object_extents);
 
       // get diff info for each object and merge adjacent stripe units
       // into an aggregate (this also sorts them)
       io::SparseExtents aggregate_sparse_extents;
-      for (auto& [object, extents] : object_extents) {
-        const uint64_t object_no = extents.front().objectno;
-        ceph_assert(object_no >= start_object_no && object_no < end_object_no);
-        uint8_t diff_state = object_diff_state[object_no - start_object_no];
-        ldout(cct, 20) << "object " << object << ": diff_state="
-                       << (int)diff_state << dendl;
+      for (const auto& oe : object_extents) {
+        ceph_assert(oe.object_no >= start_object_no &&
+                    oe.object_no < end_object_no);
+        uint8_t diff_state = object_diff_state[oe.object_no - start_object_no];
+        ldout(cct, 20) << "object "
+                       << util::data_object_name(&m_image_ctx, oe.object_no)
+                       << ": diff_state=" << (int)diff_state << dendl;
 
         if (diff_state == object_map::DIFF_STATE_HOLE &&
             from_snap_id == 0 && !parent_diff.empty()) {
           // no data in child object -- report parent diff instead
-          for (auto& oe : extents) {
-            for (auto& be : oe.buffer_extents) {
-              interval_set<uint64_t> o;
-              o.insert(off + be.first, be.second);
-              o.intersection_of(parent_diff);
-              ldout(cct, 20) << " reporting parent overlap " << o << dendl;
-              for (auto e = o.begin(); e != o.end(); ++e) {
-                aggregate_sparse_extents.insert(e.get_start(), e.get_len(),
-                                                {io::SPARSE_EXTENT_STATE_DATA,
-                                                 e.get_len()});
-              }
+          for (const auto& be : oe.buffer_extents) {
+            interval_set<uint64_t> o;
+            o.insert(off + be.first, be.second);
+            o.intersection_of(parent_diff);
+            ldout(cct, 20) << " reporting parent overlap " << o << dendl;
+            for (auto e = o.begin(); e != o.end(); ++e) {
+              aggregate_sparse_extents.insert(e.get_start(), e.get_len(),
+                                              {io::SPARSE_EXTENT_STATE_DATA,
+                                               e.get_len()});
             }
           }
         } else if (diff_state == object_map::DIFF_STATE_HOLE_UPDATED ||
                    diff_state == object_map::DIFF_STATE_DATA_UPDATED) {
           auto state = (diff_state == object_map::DIFF_STATE_HOLE_UPDATED ?
               io::SPARSE_EXTENT_STATE_ZEROED : io::SPARSE_EXTENT_STATE_DATA);
-          for (auto& oe : extents) {
-            for (auto& be : oe.buffer_extents) {
-              aggregate_sparse_extents.insert(off + be.first, be.second,
-                                              {state, be.second});
-            }
+          for (const auto& be : oe.buffer_extents) {
+            aggregate_sparse_extents.insert(off + be.first, be.second,
+                                            {state, be.second});
           }
         }
       }
index 05ee4f8ad35725d859bdf88b611b68bdaadd7d7e..46082a27ff5df715bb3e84b5a5d6496dd6ad6d7c 100644 (file)
@@ -6094,6 +6094,82 @@ public:
     test_deterministic_pp(image, object_off, len, 1);
   }
 
+#ifdef HAVE_LIBCRYPTSETUP
+
+  void test_deterministic_luks1(uint64_t object_off, uint64_t len) {
+    rados_ioctx_t ioctx;
+    ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx));
+
+    rbd_image_t image;
+    int order = 22;
+    std::string name = this->get_temp_image_name();
+    ASSERT_EQ(0, create_image(ioctx, name.c_str(), 24 << 20, &order));
+    ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+    rbd_encryption_luks1_format_options_t fopts = {
+        RBD_ENCRYPTION_ALGORITHM_AES256, "some passphrase", 15};
+    ASSERT_EQ(0, rbd_encryption_format(image, RBD_ENCRYPTION_FORMAT_LUKS1,
+                                       &fopts, sizeof(fopts)));
+    test_deterministic(image, object_off, len, 512);
+
+    ASSERT_EQ(0, rbd_close(image));
+    rados_ioctx_destroy(ioctx);
+  }
+
+  void test_deterministic_luks1_pp(uint64_t object_off, uint64_t len) {
+    librados::IoCtx ioctx;
+    ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx));
+
+    librbd::RBD rbd;
+    librbd::Image image;
+    int order = 22;
+    std::string name = this->get_temp_image_name();
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), 24 << 20, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL));
+    librbd::encryption_luks1_format_options_t fopts = {
+        RBD_ENCRYPTION_ALGORITHM_AES256, "some passphrase"};
+    ASSERT_EQ(0, image.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS1, &fopts,
+                                         sizeof(fopts)));
+    test_deterministic_pp(image, object_off, len, 512);
+  }
+
+  void test_deterministic_luks2(uint64_t object_off, uint64_t len) {
+    rados_ioctx_t ioctx;
+    ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx));
+
+    rbd_image_t image;
+    int order = 22;
+    std::string name = this->get_temp_image_name();
+    ASSERT_EQ(0, create_image(ioctx, name.c_str(), 36 << 20, &order));
+    ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+    rbd_encryption_luks2_format_options_t fopts = {
+        RBD_ENCRYPTION_ALGORITHM_AES256, "some passphrase", 15};
+    ASSERT_EQ(0, rbd_encryption_format(image, RBD_ENCRYPTION_FORMAT_LUKS2,
+                                       &fopts, sizeof(fopts)));
+    test_deterministic(image, object_off, len, 4096);
+
+    ASSERT_EQ(0, rbd_close(image));
+    rados_ioctx_destroy(ioctx);
+  }
+
+  void test_deterministic_luks2_pp(uint64_t object_off, uint64_t len) {
+    librados::IoCtx ioctx;
+    ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx));
+
+    librbd::RBD rbd;
+    librbd::Image image;
+    int order = 22;
+    std::string name = this->get_temp_image_name();
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), 36 << 20, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL));
+    librbd::encryption_luks2_format_options_t fopts = {
+        RBD_ENCRYPTION_ALGORITHM_AES256, "some passphrase"};
+    ASSERT_EQ(0, image.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, &fopts,
+                                         sizeof(fopts)));
+    test_deterministic_pp(image, object_off, len, 4096);
+  }
+
+#endif // HAVE_LIBCRYPTSETUP
+
 private:
   void test_deterministic(rbd_image_t image, uint64_t object_off,
                           uint64_t len, uint64_t block_size) {
@@ -6501,6 +6577,58 @@ TYPED_TEST(DiffIterateTest, DiffIterateDeterministicPP)
   EXPECT_NO_FATAL_FAILURE(this->test_deterministic_pp((4 << 20) - 2, 2));
 }
 
+#ifdef HAVE_LIBCRYPTSETUP
+
+TYPED_TEST(DiffIterateTest, DiffIterateDeterministicLUKS1)
+{
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1(0, 256));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1((1 << 20) - 256, 256));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1((1 << 20) - 128, 256));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1(1 << 20, 256));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1((4 << 20) - 256, 256));
+}
+
+TYPED_TEST(DiffIterateTest, DiffIterateDeterministicLUKS1PP)
+{
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1_pp(0, 2));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1_pp((3 << 20) - 2, 2));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1_pp((3 << 20) - 1, 2));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1_pp(3 << 20, 2));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks1_pp((4 << 20) - 2, 2));
+}
+
+TYPED_TEST(DiffIterateTest, DiffIterateDeterministicLUKS2)
+{
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2(0, 256));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2((1 << 20) - 256, 256));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2((1 << 20) - 128, 256));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2(1 << 20, 256));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2((4 << 20) - 256, 256));
+}
+
+TYPED_TEST(DiffIterateTest, DiffIterateDeterministicLUKS2PP)
+{
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2_pp(0, 2));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2_pp((3 << 20) - 2, 2));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2_pp((3 << 20) - 1, 2));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2_pp(3 << 20, 2));
+  EXPECT_NO_FATAL_FAILURE(this->test_deterministic_luks2_pp((4 << 20) - 2, 2));
+}
+
+#endif // HAVE_LIBCRYPTSETUP
+
 TYPED_TEST(DiffIterateTest, DiffIterateDiscard)
 {
   REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));