rbd_encryption_format_t format,
rbd_encryption_options_t opts,
size_t opts_size);
+/* encryption will be loaded on all ancestor images,
+ * until reaching an ancestor image which does not match any known format */
CEPH_RBD_API int rbd_encryption_load(rbd_image_t image,
rbd_encryption_format_t format,
rbd_encryption_options_t opts,
/* encryption */
int encryption_format(encryption_format_t format, encryption_options_t opts,
size_t opts_size);
+ /* encryption will be loaded on all ancestor images,
+ * until reaching an ancestor image which does not match any known format */
int encryption_load(encryption_format_t format, encryption_options_t opts,
size_t opts_size);
virtual std::unique_ptr<EncryptionFormat<ImageCtxT>> clone() const = 0;
virtual void format(ImageCtxT* ictx, Context* on_finish) = 0;
- virtual void load(ImageCtxT* ictx, Context* on_finish) = 0;
+ virtual void load(ImageCtxT* ictx, std::string* detected_format_name,
+ Context* on_finish) = 0;
virtual void flatten(ImageCtxT* ictx, Context* on_finish) = 0;
virtual CryptoInterface* get_crypto() = 0;
#include "librbd/Utils.h"
#include "librbd/ImageCtx.h"
#include "librbd/crypto/EncryptionFormat.h"
+#include "librbd/crypto/Types.h"
#include "librbd/crypto/Utils.h"
#include "librbd/io/AioCompletion.h"
#include "librbd/io/ImageDispatcherInterface.h"
void LoadRequest<I>::load() {
ldout(m_image_ctx->cct, 20) << "format_idx=" << m_format_idx << dendl;
+ m_detected_format_name = "";
auto ctx = create_context_callback<
LoadRequest<I>, &LoadRequest<I>::handle_load>(this);
- m_formats[m_format_idx]->load(m_current_image_ctx, ctx);
+ m_formats[m_format_idx]->load(m_current_image_ctx, &m_detected_format_name,
+ ctx);
}
template <typename I>
ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
if (r < 0) {
+ if (m_is_current_format_cloned &&
+ m_detected_format_name == UNKNOWN_FORMAT) {
+ // encryption format was not detected, assume plaintext
+ ldout(m_image_ctx->cct, 5) << "assuming plaintext for image "
+ << m_current_image_ctx->name << dendl;
+ m_formats.pop_back();
+ invalidate_cache();
+ return;
+ }
+
lderr(m_image_ctx->cct) << "failed to load encryption. image name: "
<< m_current_image_ctx->name << dendl;
finish(r);
return;
}
+ ldout(m_image_ctx->cct, 5) << "loaded format " << m_detected_format_name
+ << (m_is_current_format_cloned ? " (cloned)" : "")
+ << " for image " << m_current_image_ctx->name
+ << dendl;
+
m_format_idx++;
m_current_image_ctx = m_current_image_ctx->parent;
if (m_current_image_ctx != nullptr) {
public:
using EncryptionFormat = decltype(I::encryption_format);
+ static constexpr char UNKNOWN_FORMAT[] = "<unknown>";
+
static LoadRequest* create(
I* image_ctx, std::vector<EncryptionFormat>&& formats,
Context* on_finish) {
bool m_is_current_format_cloned;
std::vector<EncryptionFormat> m_formats;
I* m_current_image_ctx;
+ std::string m_detected_format_name;
};
} // namespace crypto
return crypt_get_cipher_mode(m_cd);
}
+const char* Header::get_format_name() {
+ ceph_assert(m_cd != nullptr);
+ return crypt_get_type(m_cd);
+}
+
} // namespace luks
} // namespace crypto
} // namespace librbd
uint64_t get_data_offset();
const char* get_cipher();
const char* get_cipher_mode();
+ const char* get_format_name();
private:
void libcryptsetup_log(int level, const char* msg);
}
template <typename I>
-void LUKSEncryptionFormat<I>::load(I* image_ctx, Context* on_finish) {
+void LUKSEncryptionFormat<I>::load(
+ I* image_ctx, std::string* detected_format_name, Context* on_finish) {
auto req = luks::LoadRequest<I>::create(
- image_ctx, m_passphrase, &m_crypto, on_finish);
+ image_ctx, m_passphrase, &m_crypto, detected_format_name, on_finish);
req->send();
}
}
void format(ImageCtxT* ictx, Context* on_finish) override;
- void load(ImageCtxT* ictx, Context* on_finish) override;
+ void load(ImageCtxT* ictx, std::string* detected_format_name,
+ Context* on_finish) override;
void flatten(ImageCtxT* ictx, Context* on_finish) override;
CryptoInterface* get_crypto() override {
#include "common/errno.h"
#include "librbd/Utils.h"
#include "librbd/crypto/Utils.h"
+#include "librbd/crypto/LoadRequest.h"
#include "librbd/crypto/luks/Magic.h"
#include "librbd/io/AioCompletion.h"
#include "librbd/io/ImageDispatchSpec.h"
LoadRequest<I>::LoadRequest(
I* image_ctx, std::string_view passphrase,
std::unique_ptr<CryptoInterface>* result_crypto,
+ std::string* detected_format_name,
Context* on_finish) : m_image_ctx(image_ctx),
m_passphrase(passphrase),
m_on_finish(on_finish),
m_result_crypto(result_crypto),
+ m_detected_format_name(detected_format_name),
m_initial_read_size(DEFAULT_INITIAL_READ_SIZE),
m_header(image_ctx->cct), m_offset(0) {
}
return false;
}
- m_offset += m_bl.length();
-
- if (m_last_read_bl.length() > 0) {
- m_last_read_bl.claim_append(m_bl);
- m_bl = std::move(m_last_read_bl);
- }
+ // first, check LUKS magic at the beginning of the image
+ // If no magic is detected, caller may assume image is actually plaintext
+ if (m_offset == 0) {
+ if (Magic::is_luks(m_bl) > 0 || Magic::is_rbd_clone(m_bl) > 0) {
+ *m_detected_format_name = "LUKS";
+ } else {
+ *m_detected_format_name = crypto::LoadRequest<I>::UNKNOWN_FORMAT;
+ finish(-EINVAL);
+ return false;
+ }
- if (m_image_ctx->parent != nullptr && m_bl.length() == m_offset &&
- Magic::is_rbd_clone(m_bl) > 0) {
- r = Magic::replace_magic(m_image_ctx->cct, m_bl);
- if (r < 0) {
- if (r == -EINVAL && m_offset < MAXIMUM_HEADER_SIZE) {
- m_last_read_bl = std::move(m_bl);
- auto ctx = create_context_callback<
- LoadRequest<I>, &LoadRequest<I>::handle_read_header>(this);
- read(MAXIMUM_HEADER_SIZE, ctx);
+ if (m_image_ctx->parent != nullptr && Magic::is_rbd_clone(m_bl) > 0) {
+ r = Magic::replace_magic(m_image_ctx->cct, m_bl);
+ if (r < 0) {
+ m_image_ctx->image_lock.lock_shared();
+ auto image_size = m_image_ctx->get_image_size(m_image_ctx->snap_id);
+ m_image_ctx->image_lock.unlock_shared();
+
+ auto max_header_size = std::min(MAXIMUM_HEADER_SIZE, image_size);
+
+ if (r == -EINVAL && m_bl.length() < max_header_size) {
+ m_bl.clear();
+ auto ctx = create_context_callback<
+ LoadRequest<I>, &LoadRequest<I>::handle_read_header>(this);
+ read(max_header_size, ctx);
+ return false;
+ }
+
+ lderr(m_image_ctx->cct) << "error replacing rbd clone magic: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
return false;
}
-
- lderr(m_image_ctx->cct) << "error replacing rbd clone magic: "
- << cpp_strerror(r) << dendl;
- finish(r);
- return false;
}
}
return false;
}
+ m_offset += m_bl.length();
+
// write header to libcryptsetup interface
r = m_header.write(m_bl);
if (r < 0) {
// parse header via libcryptsetup
r = m_header.load(CRYPT_LUKS);
if (r != 0) {
- if (m_offset < MAXIMUM_HEADER_SIZE) {
+ m_image_ctx->image_lock.lock_shared();
+ auto image_size = m_image_ctx->get_image_size(m_image_ctx->snap_id);
+ m_image_ctx->image_lock.unlock_shared();
+
+ auto max_header_size = std::min(MAXIMUM_HEADER_SIZE, image_size);
+ if (m_offset < max_header_size) {
// perhaps we did not feed the entire header to libcryptsetup, retry
auto ctx = create_context_callback<
LoadRequest<I>, &LoadRequest<I>::handle_read_header>(this);
- read(MAXIMUM_HEADER_SIZE, ctx);
+ read(max_header_size, ctx);
return;
}
return;
}
+ // gets actual LUKS version (only used for logging)
+ ceph_assert(*m_detected_format_name == "LUKS");
+ *m_detected_format_name = m_header.get_format_name();
+
auto cipher = m_header.get_cipher();
if (strcmp(cipher, "aes") != 0) {
lderr(m_image_ctx->cct) << "unsupported cipher: " << cipher << dendl;
static LoadRequest* create(
I* image_ctx, std::string_view passphrase,
std::unique_ptr<CryptoInterface>* result_crypto,
+ std::string* detected_format_name,
Context* on_finish) {
- return new LoadRequest(image_ctx, passphrase, result_crypto, on_finish);
+ return new LoadRequest(image_ctx, passphrase, result_crypto,
+ detected_format_name, on_finish);
}
LoadRequest(I* image_ctx, std::string_view passphrase,
std::unique_ptr<CryptoInterface>* result_crypto,
- Context* on_finish);
+ std::string* detected_format_name, Context* on_finish);
void send();
void finish(int r);
void set_initial_read_size(uint64_t read_size);
std::string_view m_passphrase;
Context* m_on_finish;
ceph::bufferlist m_bl;
- ceph::bufferlist m_last_read_bl;
std::unique_ptr<CryptoInterface>* m_result_crypto;
+ std::string* m_detected_format_name;
uint64_t m_initial_read_size;
Header m_header;
uint64_t m_offset;
Context* image_read_request;
ceph::bufferlist header_bl;
uint64_t data_offset;
+ std::string detected_format_name;
void SetUp() override {
TestMockFixture::SetUp();
ASSERT_EQ(0, open_image(m_image_name, &ictx));
mock_image_ctx = new MockImageCtx(*ictx);
mock_load_request = MockLoadRequest::create(
- mock_image_ctx, std::move(passphrase), &crypto, on_finish);
+ mock_image_ctx, std::move(passphrase), &crypto,
+ &detected_format_name, on_finish);
+ detected_format_name = "";
}
void TearDown() override {
image_read_request = ctx;
}));
}
+
+ void expect_get_image_size() {
+ EXPECT_CALL(*mock_image_ctx, get_image_size(_)).WillOnce(
+ Return(100 * 1024 * 1024));
+ }
};
TEST_F(TestMockCryptoLuksLoadRequest, AES128) {
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, AES256) {
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, LUKS1) {
delete mock_load_request;
mock_load_request = MockLoadRequest::create(
- mock_image_ctx, {passphrase_cstr}, &crypto, on_finish);
+ mock_image_ctx, {passphrase_cstr}, &crypto, &detected_format_name,
+ on_finish);
generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, false);
expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
mock_load_request->send();
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS1", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, WrongFormat) {
expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
mock_load_request->send();
- expect_image_read(DEFAULT_INITIAL_READ_SIZE,
- MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE);
- image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); // complete 1st read
+ image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
- image_read_request->complete(
- MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(-EINVAL, finished_cond.wait());
ASSERT_EQ(crypto.get(), nullptr);
+ ASSERT_EQ("<unknown>", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedAlgorithm) {
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(-ENOTSUP, finished_cond.wait());
ASSERT_EQ(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedCipherMode) {
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(-ENOTSUP, finished_cond.wait());
ASSERT_EQ(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, HeaderBiggerThanInitialRead) {
expect_image_read(0, 4096);
mock_load_request->send();
+ expect_get_image_size();
expect_image_read(4096, MAXIMUM_HEADER_SIZE - 4096);
image_read_request->complete(4096); // complete initial read
image_read_request->complete(MAXIMUM_HEADER_SIZE - 4096);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, LUKS1FormattedClone) {
mock_image_ctx->parent = mock_image_ctx;
delete mock_load_request;
mock_load_request = MockLoadRequest::create(
- mock_image_ctx, {passphrase_cstr}, &crypto, on_finish);
+ mock_image_ctx, {passphrase_cstr}, &crypto, &detected_format_name,
+ on_finish);
generate_header(CRYPT_LUKS1, "aes", 64, "xts-plain64", 512, true);
expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
mock_load_request->send();
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS1", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, LUKS2FormattedClone) {
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, KeyslotsBiggerThanInitialRead) {
image_read_request->complete(data_offset - 16384);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NE(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
}
TEST_F(TestMockCryptoLuksLoadRequest, WrongPassphrase) {
delete mock_load_request;
mock_load_request = MockLoadRequest::create(
- mock_image_ctx, "wrong", &crypto, on_finish);
+ mock_image_ctx, "wrong", &crypto, &detected_format_name, on_finish);
generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false);
expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
image_read_request->complete(data_offset - DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(-EPERM, finished_cond.wait());
ASSERT_EQ(crypto.get(), nullptr);
+ ASSERT_EQ("LUKS2", detected_format_name);
}
} // namespace luks
}
void expect_encryption_load(MockEncryptionFormat* encryption_format,
- MockTestImageCtx* ictx) {
+ MockTestImageCtx* ictx,
+ std::string detected_format = "SOMEFORMAT") {
EXPECT_CALL(*encryption_format, load(
- ictx, _)).WillOnce(
- WithArgs<1>(Invoke([this](Context* ctx) {
+ ictx, _, _)).WillOnce(
+ WithArgs<1, 2>(Invoke([this, detected_format](
+ std::string* detected_format_name, Context* ctx) {
+ if (!detected_format.empty()) {
+ *detected_format_name = detected_format;
+ }
load_context = ctx;
})));
}
ASSERT_EQ(nullptr, mock_parent_image_ctx->encryption_format.get());
}
+
+TEST_F(TestMockCryptoLoadRequest, LoadClonedPlaintextParent) {
+ expect_test_journal_feature(mock_image_ctx);
+ expect_test_journal_feature(mock_parent_image_ctx);
+ expect_image_flush();
+ expect_encryption_load(mock_encryption_format, mock_image_ctx);
+ mock_load_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_encryption_format_clone(mock_encryption_format);
+ expect_encryption_load(
+ cloned_encryption_format, mock_parent_image_ctx,
+ LoadRequest<MockImageCtx>::UNKNOWN_FORMAT);
+ load_context->complete(0);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_invalidate_cache();
+ load_context->complete(-EINVAL);
+ ASSERT_EQ(0, finished_cond.wait());
+ ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get());
+ ASSERT_EQ(nullptr, mock_parent_image_ctx->encryption_format.get());
+}
+
+TEST_F(TestMockCryptoLoadRequest, LoadClonedParentDetectionError) {
+ expect_test_journal_feature(mock_image_ctx);
+ expect_test_journal_feature(mock_parent_image_ctx);
+ expect_image_flush();
+ expect_encryption_load(mock_encryption_format, mock_image_ctx);
+ mock_load_request->send();
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ expect_encryption_format_clone(mock_encryption_format);
+ expect_encryption_load(
+ cloned_encryption_format, mock_parent_image_ctx, "");
+ load_context->complete(0);
+ ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+ load_context->complete(-EINVAL);
+ ASSERT_EQ(-EINVAL, finished_cond.wait());
+ ASSERT_EQ(nullptr, mock_image_ctx->encryption_format.get());
+ ASSERT_EQ(nullptr, mock_parent_image_ctx->encryption_format.get());
+}
+
TEST_F(TestMockCryptoLoadRequest, LoadParentFail) {
delete mock_load_request;
mock_encryption_format = new MockEncryptionFormat();
struct MockEncryptionFormat {
MOCK_CONST_METHOD0(clone, std::unique_ptr<MockEncryptionFormat>());
MOCK_METHOD2(format, void(MockImageCtx*, Context*));
- MOCK_METHOD2(load, void(MockImageCtx*, Context*));
+ MOCK_METHOD3(load, void(MockImageCtx*, std::string*, Context*));
MOCK_METHOD2(flatten, void(MockImageCtx*, Context*));
MOCK_METHOD0(get_crypto, MockCryptoInterface*());
};