Merge pull request #37875 from orozery/librbd-luks-format

librbd: add LUKS support

Reviewed-by: Jason Dillaman <dillaman@redhat.com>
This commit is contained in:
Jason Dillaman 2020-11-23 12:06:32 -05:00 committed by GitHub
commit c569a302be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1284 additions and 6 deletions

View File

@ -193,6 +193,15 @@ if(WITH_BLUESTORE)
endif()
endif()
# libcryptsetup is only available on linux
if(WITH_RBD AND LINUX)
find_package(libcryptsetup REQUIRED)
set(HAVE_LIBCRYPTSETUP ${LIBCRYPTSETUP_FOUND})
if(${LIBCRYPTSETUP_VERSION} VERSION_LESS 2.0.5)
set(LIBCRYPTSETUP_LEGACY_DATA_ALIGNMENT TRUE)
endif()
endif()
include(CMakeDependentOption)
CMAKE_DEPENDENT_OPTION(WITH_ZBD "Enable libzbd bluestore backend" OFF

View File

@ -152,6 +152,7 @@ BuildRequires: gperftools-devel >= 2.4
BuildRequires: leveldb-devel > 1.2
BuildRequires: libaio-devel
BuildRequires: libblkid-devel >= 2.17
BuildRequires: cryptsetup-devel
BuildRequires: libcurl-devel
BuildRequires: libcap-ng-devel
BuildRequires: fmt-devel >= 5.2.1

View File

@ -0,0 +1,32 @@
# - Find libcryptsetup
# Sets the following:
#
# LIBCRYPTSETUP_INCLUDE_DIR
# LIBCRYPTSETUP_LIBRARIES
# LIBCRYPTSETUP_VERSION
# LIBCRYPTSETUP_FOUND
pkg_search_module(PC_libcryptsetup libcryptsetup)
find_path(LIBCRYPTSETUP_INCLUDE_DIR
NAMES libcryptsetup.h
PATHS ${PC_libcryptsetup_INCLUDE_DIRS})
find_library(LIBCRYPTSETUP_LIBRARIES
NAMES libcryptsetup.so
PATHS ${PC_libcryptsetup_LIBRARY_DIRS})
set(LIBCRYPTSETUP_VERSION ${PC_libcryptsetup_VERSION})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(libcryptsetup
REQUIRED_VARS
LIBCRYPTSETUP_INCLUDE_DIR
LIBCRYPTSETUP_LIBRARIES
VERSION_VAR LIBCRYPTSETUP_VERSION)
mark_as_advanced(
LIBCRYPTSETUP_LIBRARIES
LIBCRYPTSETUP_INCLUDE_DIR
LIBCRYPTSETUP_VERSION)

1
debian/control vendored
View File

@ -29,6 +29,7 @@ Build-Depends: cmake (>= 3.10.2),
libblkid-dev (>= 2.17),
# Crimson libc-ares-dev,
# Crimson libcrypto++-dev,
libcryptsetup-dev,
libcap-ng-dev,
libcunit1-dev,
libcurl4-openssl-dev,

View File

@ -360,6 +360,9 @@
/* Define if PWL-SSD is enabled */
#cmakedefine WITH_RBD_SSD_CACHE
/* Define if libcryptsetup version < 2.0.5 */
#cmakedefine LIBCRYPTSETUP_LEGACY_DATA_ALIGNMENT
/* Shared library extension, such as .so, .dll or .dylib */
#cmakedefine CMAKE_SHARED_LIBRARY_SUFFIX "@CMAKE_SHARED_LIBRARY_SUFFIX@"

View File

@ -200,6 +200,13 @@ if(WITH_EVENTTRACE)
list(APPEND librbd_internal_srcs ../common/EventTrace.cc)
endif()
if(LINUX AND HAVE_LIBCRYPTSETUP)
list(APPEND librbd_internal_srcs
crypto/luks/Header.cc
crypto/luks/FormatRequest.cc
crypto/luks/LoadRequest.cc)
endif()
if(WITH_RBD_RWL OR WITH_RBD_SSD_CACHE)
set(librbd_internal_srcs
${librbd_internal_srcs}
@ -241,6 +248,10 @@ target_link_libraries(rbd_internal PRIVATE
osdc rbd_types
OpenSSL::SSL)
target_include_directories(rbd_internal PRIVATE ${OPENSSL_INCLUDE_DIR})
if(LINUX AND HAVE_LIBCRYPTSETUP)
target_include_directories(rbd_internal PRIVATE ${LIBCRYPTSETUP_INCLUDE_DIR})
target_link_libraries(rbd_internal PRIVATE ${LIBCRYPTSETUP_LIBRARIES})
endif()
if(WITH_RBD_RWL OR WITH_RBD_SSD_CACHE)
target_link_libraries(rbd_internal

View File

@ -12,9 +12,9 @@ namespace crypto {
template <typename T>
BlockCrypto<T>::BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
uint64_t block_size)
uint64_t block_size, uint64_t data_offset)
: m_cct(cct), m_data_cryptor(data_cryptor), m_block_size(block_size),
m_iv_size(data_cryptor->get_iv_size()) {
m_data_offset(data_offset), m_iv_size(data_cryptor->get_iv_size()) {
ceph_assert(isp2(block_size));
ceph_assert((block_size % data_cryptor->get_block_size()) == 0);
}
@ -100,3 +100,5 @@ int BlockCrypto<T>::decrypt(ceph::bufferlist* data, uint64_t image_offset) {
} // namespace crypto
} // namespace librbd
template class librbd::crypto::BlockCrypto<EVP_CIPHER_CTX>;

View File

@ -6,7 +6,7 @@
#include "include/Context.h"
#include "librbd/crypto/CryptoInterface.h"
#include "librbd/crypto/DataCryptor.h"
#include "librbd/crypto/openssl/DataCryptor.h"
namespace librbd {
namespace crypto {
@ -15,8 +15,12 @@ template <typename T>
class BlockCrypto : public CryptoInterface {
public:
static BlockCrypto* create(CephContext* cct, DataCryptor<T>* data_cryptor,
uint32_t block_size, uint64_t data_offset) {
return new BlockCrypto(cct, data_cryptor, block_size, data_offset);
}
BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
uint64_t block_size);
uint64_t block_size, uint64_t data_offset);
int encrypt(ceph::bufferlist* data, uint64_t image_offset) override;
int decrypt(ceph::bufferlist* data, uint64_t image_offset) override;
@ -25,10 +29,15 @@ public:
return m_block_size;
}
uint64_t get_data_offset() const override {
return m_data_offset;
}
private:
CephContext* m_cct;
DataCryptor<T>* m_data_cryptor;
uint64_t m_block_size;
uint64_t m_data_offset;
uint32_t m_iv_size;
int crypt(ceph::bufferlist* data, uint64_t image_offset, CipherMode mode);
@ -37,4 +46,6 @@ private:
} // namespace crypto
} // namespace librbd
extern template class librbd::crypto::BlockCrypto<EVP_CIPHER_CTX>;
#endif //CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H

View File

@ -18,6 +18,7 @@ public:
virtual int encrypt(ceph::bufferlist* data, uint64_t image_offset) = 0;
virtual int decrypt(ceph::bufferlist* data, uint64_t image_offset) = 0;
virtual uint64_t get_block_size() const = 0;
virtual uint64_t get_data_offset() const = 0;
inline std::pair<uint64_t, uint64_t> get_pre_and_post_align(
uint64_t off, uint64_t len) {

View File

@ -12,6 +12,16 @@ enum CipherMode {
CIPHER_MODE_DEC,
};
enum DiskEncryptionFormat {
DISK_ENCRYPTION_FORMAT_LUKS1,
DISK_ENCRYPTION_FORMAT_LUKS2,
};
enum CipherAlgorithm {
CIPHER_ALGORITHM_AES128,
CIPHER_ALGORITHM_AES256,
};
} // namespace crypto
} // namespace librbd

View File

@ -0,0 +1,147 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#include "FormatRequest.h"
#include "common/dout.h"
#include "common/errno.h"
#include "librbd/Utils.h"
#include "librbd/crypto/luks/Header.h"
#include "librbd/io/AioCompletion.h"
#include "librbd/io/ImageDispatchSpec.h"
#include "librbd/io/ObjectDispatcherInterface.h"
#define dout_subsys ceph_subsys_rbd
#undef dout_prefix
#define dout_prefix *_dout << "librbd::crypto::luks::FormatRequest: " << this \
<< " " << __func__ << ": "
namespace librbd {
namespace crypto {
namespace luks {
using librbd::util::create_context_callback;
template <typename I>
FormatRequest<I>::FormatRequest(
I* image_ctx, DiskEncryptionFormat type, CipherAlgorithm cipher,
std::string&& passphrase, Context* on_finish,
bool insecure_fast_mode) : m_image_ctx(image_ctx), m_type(type),
m_cipher(cipher), m_on_finish(on_finish),
m_insecure_fast_mode(insecure_fast_mode),
m_header(image_ctx->cct),
m_passphrase(std::move(passphrase)) {
}
template <typename I>
void FormatRequest<I>::send() {
if (m_image_ctx->io_object_dispatcher->exists(
io::OBJECT_DISPATCH_LAYER_CRYPTO)) {
finish(-EEXIST);
return;
}
const char* type;
size_t sector_size;
switch(m_type) {
case DISK_ENCRYPTION_FORMAT_LUKS1:
type = CRYPT_LUKS1;
sector_size = 512;
break;
case DISK_ENCRYPTION_FORMAT_LUKS2:
type = CRYPT_LUKS2;
sector_size = 4096;
break;
default:
lderr(m_image_ctx->cct) << "unsupported disk encryption type: " << m_type
<< dendl;
finish(-EINVAL);
return;
}
const char* alg;
size_t key_size;
switch (m_cipher) {
case CIPHER_ALGORITHM_AES128:
alg = "aes";
key_size = 32;
break;
case CIPHER_ALGORITHM_AES256:
alg = "aes";
key_size = 64;
break;
default:
lderr(m_image_ctx->cct) << "unsupported cipher algorithm: " << m_cipher
<< dendl;
finish(-EINVAL);
return;
}
// setup interface with libcryptsetup
auto r = m_header.init();
if (r < 0) {
finish(r);
return;
}
// format (create LUKS header)
r = m_header.format(type, alg, key_size, "xts-plain64", sector_size,
m_image_ctx->get_object_size(), m_insecure_fast_mode);
if (r != 0) {
finish(r);
return;
}
// add keyslot (volume key encrypted with passphrase)
r = m_header.add_keyslot(m_passphrase.c_str(), m_passphrase.size());
if (r != 0) {
finish(r);
return;
}
// read header from libcryptsetup interface
ceph::bufferlist bl;
r = m_header.read(&bl);
if (r < 0) {
finish(r);
return;
}
// write header to offset 0 of the image
auto ctx = create_context_callback<
FormatRequest<I>, &FormatRequest<I>::handle_write_header>(this);
auto aio_comp = io::AioCompletion::create_and_start(
ctx, util::get_image_ctx(m_image_ctx), io::AIO_TYPE_WRITE);
ZTracer::Trace trace;
auto req = io::ImageDispatchSpec::create_write(
*m_image_ctx, io::IMAGE_DISPATCH_LAYER_API_START, aio_comp,
{{0, bl.length()}}, std::move(bl),
m_image_ctx->get_data_io_context(), 0, trace);
req->send();
}
template <typename I>
void FormatRequest<I>::handle_write_header(int r) {
if (r < 0) {
lderr(m_image_ctx->cct) << "error writing header to image: "
<< cpp_strerror(r) << dendl;
finish(r);
return;
}
finish(0);
}
template <typename I>
void FormatRequest<I>::finish(int r) {
explicit_bzero(&m_passphrase[0], m_passphrase.size());
m_on_finish->complete(r);
delete this;
}
} // namespace luks
} // namespace crypto
} // namespace librbd
template class librbd::crypto::luks::FormatRequest<librbd::ImageCtx>;

View File

@ -0,0 +1,54 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#ifndef CEPH_LIBRBD_CRYPTO_LUKS_FORMAT_REQUEST_H
#define CEPH_LIBRBD_CRYPTO_LUKS_FORMAT_REQUEST_H
#include "librbd/ImageCtx.h"
#include "librbd/crypto/Types.h"
#include "librbd/crypto/luks/Header.h"
namespace librbd {
class ImageCtx;
namespace crypto {
namespace luks {
template <typename I>
class FormatRequest {
public:
static FormatRequest* create(
I* image_ctx, DiskEncryptionFormat type, CipherAlgorithm cipher,
std::string&& passphrase, Context* on_finish,
bool insecure_fast_mode) {
return new FormatRequest(image_ctx, type, cipher, std::move(passphrase),
on_finish, insecure_fast_mode);
}
FormatRequest(I* image_ctx, DiskEncryptionFormat type,
CipherAlgorithm cipher, std::string&& passphrase,
Context* on_finish, bool insecure_fast_mode);
void send();
void finish(int r);
private:
I* m_image_ctx;
DiskEncryptionFormat m_type;
CipherAlgorithm m_cipher;
Context* m_on_finish;
bool m_insecure_fast_mode;
Header m_header;
std::string m_passphrase;
void handle_write_header(int r);
};
} // namespace luks
} // namespace crypto
} // namespace librbd
extern template class librbd::crypto::luks::FormatRequest<librbd::ImageCtx>;
#endif // CEPH_LIBRBD_CRYPTO_LUKS_FORMAT_REQUEST_H

View File

@ -0,0 +1,248 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#include "Header.h"
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include "common/dout.h"
#include "common/errno.h"
#define dout_subsys ceph_subsys_rbd
#undef dout_prefix
#define dout_prefix *_dout << "librbd::crypto::luks::Header: " << this << " " \
<< __func__ << ": "
namespace librbd {
namespace crypto {
namespace luks {
Header::Header(CephContext* cct) : m_cct(cct), m_fd(-1), m_cd(nullptr) {
}
Header::~Header() {
if (m_fd != -1) {
close(m_fd);
m_fd = -1;
}
if (m_cd != nullptr) {
crypt_free(m_cd);
m_cd = nullptr;
}
}
void Header::libcryptsetup_log_wrapper(int level, const char* msg, void* header) {
((Header*)header)->libcryptsetup_log(level, msg);
}
void Header::libcryptsetup_log(int level, const char* msg) {
switch (level) {
case CRYPT_LOG_NORMAL:
ldout(m_cct, 5) << "[libcryptsetup] " << msg << dendl;
break;
case CRYPT_LOG_ERROR:
lderr(m_cct) << "[libcryptsetup] " << msg << dendl;
break;
case CRYPT_LOG_VERBOSE:
ldout(m_cct, 10) << "[libcryptsetup] " << msg << dendl;
break;
case CRYPT_LOG_DEBUG:
ldout(m_cct, 20) << "[libcryptsetup] " << msg << dendl;
break;
}
}
int Header::init() {
// create anonymous file
m_fd = syscall(SYS_memfd_create, "LibcryptsetupInterface", 0);
if (m_fd == -1) {
lderr(m_cct) << "error creating anonymous file: " << cpp_strerror(-errno)
<< dendl;
return -errno;
}
std::string path =
"/proc/" + std::to_string(getpid()) + "/fd/" + std::to_string(m_fd);
if (m_cct->_conf->subsys.should_gather<dout_subsys, 20>()) {
crypt_set_debug_level(CRYPT_DEBUG_ALL);
}
// init libcryptsetup handle
auto r = crypt_init(&m_cd, path.c_str());
if (r != 0) {
lderr(m_cct) << "crypt_init failed: " << cpp_strerror(r) << dendl;
return r;
}
// redirect logging
crypt_set_log_callback(m_cd, &libcryptsetup_log_wrapper, this);
return 0;
}
int Header::write(const ceph::bufferlist& bl) {
ceph_assert(m_fd != -1);
auto r = bl.write_fd(m_fd);
if (r != 0) {
lderr(m_cct) << "error writing header: " << cpp_strerror(r) << dendl;
}
return r;
}
ssize_t Header::read(ceph::bufferlist* bl) {
ceph_assert(m_fd != -1);
// get current header size
struct stat st;
ssize_t r = fstat(m_fd, &st);
if (r < 0) {
r = -errno;
lderr(m_cct) << "failed to stat anonymous file: " << cpp_strerror(r)
<< dendl;
return r;
}
r = bl->read_fd(m_fd, st.st_size);
if (r < 0) {
lderr(m_cct) << "error reading header: " << cpp_strerror(r) << dendl;
}
return r;
}
int Header::format(const char* type, const char* alg, size_t key_size,
const char* cipher_mode, uint32_t sector_size,
uint32_t data_alignment, bool insecure_fast_mode) {
ceph_assert(m_cd != nullptr);
// required for passing libcryptsetup device size check
if (ftruncate(m_fd, 4096) != 0) {
lderr(m_cct) << "failed to truncate anonymous file: "
<< cpp_strerror(-errno) << dendl;
return -errno;
}
struct crypt_params_luks1 luks1params;
struct crypt_params_luks2 luks2params;
#ifdef LIBCRYPTSETUP_LEGACY_DATA_ALIGNMENT
size_t converted_data_alignment = data_alignment / sector_size;
#else
size_t converted_data_alignment = data_alignment / 512;
#endif
void* params = nullptr;
if (strcmp(type, CRYPT_LUKS1) == 0) {
memset(&luks1params, 0, sizeof(luks1params));
luks1params.data_alignment = converted_data_alignment;
params = &luks1params;
} else if (strcmp(type, CRYPT_LUKS2) == 0) {
memset(&luks2params, 0, sizeof(luks2params));
luks2params.data_alignment = converted_data_alignment;
luks2params.sector_size = sector_size;
params = &luks2params;
}
// this mode should be used for testing only
if (insecure_fast_mode) {
struct crypt_pbkdf_type pbkdf;
memset(&pbkdf, 0, sizeof(pbkdf));
pbkdf.type = CRYPT_KDF_PBKDF2;
pbkdf.flags = CRYPT_PBKDF_NO_BENCHMARK;
pbkdf.hash = "sha256";
pbkdf.iterations = 1000;
pbkdf.time_ms = 1;
auto r = crypt_set_pbkdf_type(m_cd, &pbkdf);
if (r != 0) {
lderr(m_cct) << "crypt_set_pbkdf_type failed: " << cpp_strerror(r)
<< dendl;
return r;
}
}
auto r = crypt_format(
m_cd, type, alg, cipher_mode, NULL, NULL, key_size, params);
if (r != 0) {
lderr(m_cct) << "crypt_format failed: " << cpp_strerror(r) << dendl;
return r;
}
return 0;
}
int Header::add_keyslot(const char* passphrase, size_t passphrase_size) {
ceph_assert(m_cd != nullptr);
auto r = crypt_keyslot_add_by_volume_key(
m_cd, CRYPT_ANY_SLOT, NULL, 0, passphrase, passphrase_size);
if (r != 0) {
lderr(m_cct) << "crypt_keyslot_add_by_volume_key failed: "
<< cpp_strerror(r) << dendl;
return r;
}
return 0;
}
int Header::load() {
ceph_assert(m_cd != nullptr);
// libcryptsetup checks if device size matches the header and keyslots size
// in LUKS2, 2 X 4MB header + 128MB keyslots
if (ftruncate(m_fd, 136 * 1024 * 1024) != 0) {
lderr(m_cct) << "failed to truncate anonymous file: "
<< cpp_strerror(-errno) << dendl;
return -errno;
}
auto r = crypt_load(m_cd, CRYPT_LUKS, NULL);
if (r != 0) {
lderr(m_cct) << "crypt_load failed: " << cpp_strerror(r) << dendl;
return r;
}
return 0;
}
int Header::read_volume_key(const char* passphrase, size_t passphrase_size,
char* volume_key, size_t* volume_key_size) {
ceph_assert(m_cd != nullptr);
auto r = crypt_volume_key_get(
m_cd, CRYPT_ANY_SLOT, volume_key, volume_key_size, passphrase,
passphrase_size);
if (r != 0) {
lderr(m_cct) << "crypt_volume_key_get failed: " << cpp_strerror(r)
<< dendl;
return r;
}
return 0;
}
int Header::get_sector_size() {
ceph_assert(m_cd != nullptr);
return crypt_get_sector_size(m_cd);
}
uint64_t Header::get_data_offset() {
ceph_assert(m_cd != nullptr);
return crypt_get_data_offset(m_cd) << 9;
}
const char* Header::get_cipher() {
ceph_assert(m_cd != nullptr);
return crypt_get_cipher(m_cd);
}
const char* Header::get_cipher_mode() {
ceph_assert(m_cd != nullptr);
return crypt_get_cipher_mode(m_cd);
}
} // namespace luks
} // namespace crypto
} // namespace librbd

View File

@ -0,0 +1,51 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#ifndef CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H
#define CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H
#include <libcryptsetup.h>
#include "common/ceph_context.h"
#include "include/buffer.h"
namespace librbd {
namespace crypto {
namespace luks {
class Header {
public:
Header(CephContext* cct);
~Header();
int init();
int write(const ceph::bufferlist& bl);
ssize_t read(ceph::bufferlist* bl);
int format(const char* type, const char* alg, size_t key_size,
const char* cipher_mode, uint32_t sector_size,
uint32_t data_alignment, bool insecure_fast_mode);
int add_keyslot(const char* passphrase, size_t passphrase_size);
int load();
int read_volume_key(const char* passphrase, size_t passphrase_size,
char* volume_key, size_t* volume_key_size);
int get_sector_size();
uint64_t get_data_offset();
const char* get_cipher();
const char* get_cipher_mode();
private:
void libcryptsetup_log(int level, const char* msg);
static void libcryptsetup_log_wrapper(int level, const char* msg,
void* header);
CephContext* m_cct;
int m_fd;
struct crypt_device *m_cd;
};
} // namespace luks
} // namespace crypto
} // namespace librbd
#endif // CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H

View File

@ -0,0 +1,214 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#include "LoadRequest.h"
#include "common/dout.h"
#include "common/errno.h"
#include "librbd/Utils.h"
#include "librbd/crypto/BlockCrypto.h"
#include "librbd/crypto/openssl/DataCryptor.h"
#include "librbd/io/AioCompletion.h"
#include "librbd/io/ImageDispatchSpec.h"
#include "librbd/io/ObjectDispatcherInterface.h"
#include "librbd/io/ReadResult.h"
#define dout_subsys ceph_subsys_rbd
#undef dout_prefix
#define dout_prefix *_dout << "librbd::crypto::luks::LoadRequest: " << this \
<< " " << __func__ << ": "
namespace librbd {
namespace crypto {
namespace luks {
using librbd::util::create_context_callback;
template <typename I>
LoadRequest<I>::LoadRequest(
I* image_ctx, std::string&& passphrase,
ceph::ref_t<CryptoInterface>* result_crypto,
Context* on_finish) : m_image_ctx(image_ctx), m_on_finish(on_finish),
m_result_crypto(result_crypto),
m_initial_read_size(DEFAULT_INITIAL_READ_SIZE),
m_header(image_ctx->cct), m_offset(0),
m_passphrase(std::move(passphrase)) {
}
template <typename I>
void LoadRequest<I>::set_initial_read_size(uint64_t read_size) {
m_initial_read_size = read_size;
}
template <typename I>
void LoadRequest<I>::send() {
if (m_image_ctx->io_object_dispatcher->exists(
io::OBJECT_DISPATCH_LAYER_CRYPTO)) {
finish(-EEXIST);
return;
}
// setup interface with libcryptsetup
auto r = m_header.init();
if (r < 0) {
finish(r);
return;
}
auto ctx = create_context_callback<
LoadRequest<I>, &LoadRequest<I>::handle_read_header>(this);
read(m_initial_read_size, ctx);
}
template <typename I>
void LoadRequest<I>::read(uint64_t end_offset, Context* on_finish) {
auto length = end_offset - m_offset;
auto aio_comp = io::AioCompletion::create_and_start(
on_finish, util::get_image_ctx(m_image_ctx), io::AIO_TYPE_READ);
ZTracer::Trace trace;
auto req = io::ImageDispatchSpec::create_read(
*m_image_ctx, io::IMAGE_DISPATCH_LAYER_API_START, aio_comp,
{{m_offset, length}}, io::ReadResult{&m_bl},
m_image_ctx->get_data_io_context(), 0, 0, trace);
req->send();
}
template <typename I>
bool LoadRequest<I>::handle_read(int r) {
if (r < 0) {
lderr(m_image_ctx->cct) << "error reading from image: " << cpp_strerror(r)
<< dendl;
finish(r);
return false;
}
// write header to libcryptsetup interface
r = m_header.write(m_bl);
if (r < 0) {
finish(r);
return false;
}
m_offset += m_bl.length();
m_bl.clear();
return true;
}
template <typename I>
void LoadRequest<I>::handle_read_header(int r) {
if (!handle_read(r)) {
return;
}
// parse header via libcryptsetup
r = m_header.load();
if (r != 0) {
if (m_offset < MAXIMUM_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);
return;
}
finish(r);
return;
}
auto cipher = m_header.get_cipher();
if (strcmp(cipher, "aes") != 0) {
lderr(m_image_ctx->cct) << "unsupported cipher: " << cipher << dendl;
finish(-ENOTSUP);
return;
}
auto cipher_mode = m_header.get_cipher_mode();
if (strcmp(cipher_mode, "xts-plain64") != 0) {
lderr(m_image_ctx->cct) << "unsupported cipher mode: " << cipher_mode
<< dendl;
finish(-ENOTSUP);
return;
}
read_volume_key();
return;
}
template <typename I>
void LoadRequest<I>::handle_read_keyslots(int r) {
if (!handle_read(r)) {
return;
}
read_volume_key();
}
template <typename I>
void LoadRequest<I>::read_volume_key() {
char volume_key[64];
size_t volume_key_size = sizeof(volume_key);
auto r = m_header.read_volume_key(
m_passphrase.c_str(), m_passphrase.size(),
reinterpret_cast<char*>(volume_key), &volume_key_size);
if (r != 0) {
auto keyslots_end_offset = m_header.get_data_offset();
if (m_offset < keyslots_end_offset) {
// perhaps we did not feed the the necessary keyslot, retry
auto ctx = create_context_callback<
LoadRequest<I>, &LoadRequest<I>::handle_read_keyslots>(this);
read(keyslots_end_offset, ctx);
return;
}
finish(r);
return;
}
const char* cipher_suite;
switch (volume_key_size) {
case 32:
cipher_suite = "aes-128-xts";
break;
case 64:
cipher_suite = "aes-256-xts";
break;
default:
lderr(m_image_ctx->cct) << "unsupported volume key size: "
<< volume_key_size << dendl;
finish(-ENOTSUP);
return;
}
auto data_cryptor = new openssl::DataCryptor(m_image_ctx->cct);
r = data_cryptor->init(
cipher_suite, reinterpret_cast<unsigned char*>(volume_key),
volume_key_size);
if (r != 0) {
lderr(m_image_ctx->cct) << "error initializing data cryptor: " << r
<< dendl;
delete data_cryptor;
finish(r);
return;
}
auto sector_size = m_header.get_sector_size();
auto data_offset = m_header.get_data_offset();
*m_result_crypto = BlockCrypto<EVP_CIPHER_CTX>::create(
m_image_ctx->cct, data_cryptor, sector_size, data_offset);
finish(0);
}
template <typename I>
void LoadRequest<I>::finish(int r) {
explicit_bzero(&m_passphrase[0], m_passphrase.size());
m_on_finish->complete(r);
delete this;
}
} // namespace luks
} // namespace crypto
} // namespace librbd
template class librbd::crypto::luks::LoadRequest<librbd::ImageCtx>;

View File

@ -0,0 +1,63 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#ifndef CEPH_LIBRBD_CRYPTO_LUKS_LOAD_REQUEST_H
#define CEPH_LIBRBD_CRYPTO_LUKS_LOAD_REQUEST_H
#include "librbd/ImageCtx.h"
#include "librbd/crypto/CryptoInterface.h"
#include "librbd/crypto/luks/Header.h"
namespace librbd {
class ImageCtx;
namespace crypto {
namespace luks {
// max header size in LUKS1/2 (excl. keyslots) is 4MB
const uint64_t MAXIMUM_HEADER_SIZE = 4 * 1024 * 1024;
// default header size in LUKS2 2 X 16KB + 1 X 256KB keyslot
const uint64_t DEFAULT_INITIAL_READ_SIZE = 288 * 1024;
template <typename I>
class LoadRequest {
public:
static LoadRequest* create(
I* image_ctx, std::string&& passphrase,
ceph::ref_t<CryptoInterface>* result_crypto, Context* on_finish) {
return new LoadRequest(image_ctx, std::move(passphrase), result_crypto,
on_finish);
}
LoadRequest(I* image_ctx, std::string&& passphrase,
ceph::ref_t<CryptoInterface>* result_crypto,
Context* on_finish);
void send();
void finish(int r);
void set_initial_read_size(uint64_t read_size);
private:
I* m_image_ctx;
Context* m_on_finish;
ceph::bufferlist m_bl;
ceph::ref_t<CryptoInterface>* m_result_crypto;
uint64_t m_initial_read_size;
Header m_header;
uint64_t m_offset;
std::string m_passphrase;
void read(uint64_t end_offset, Context* on_finish);
bool handle_read(int r);
void handle_read_header(int r);
void handle_read_keyslots(int r);
void read_volume_key();
};
} // namespace luks
} // namespace crypto
} // namespace librbd
extern template class librbd::crypto::luks::LoadRequest<librbd::ImageCtx>;
#endif // CEPH_LIBRBD_CRYPTO_LUKS_LOAD_REQUEST_H

View File

@ -68,6 +68,11 @@ public:
ceph_assert(result.second);
}
bool exists(DispatchLayer dispatch_layer) override {
std::unique_lock locker{m_lock};
return m_dispatches.find(dispatch_layer) != m_dispatches.end();
}
void shut_down_dispatch(DispatchLayer dispatch_layer,
Context* on_finish) override {
auto cct = m_image_ctx->cct;

View File

@ -24,6 +24,7 @@ public:
virtual void shut_down(Context* on_finish) = 0;
virtual void register_dispatch(Dispatch* dispatch) = 0;
virtual bool exists(DispatchLayer dispatch_layer) = 0;
virtual void shut_down_dispatch(DispatchLayer dispatch_layer,
Context* on_finish) = 0;

View File

@ -132,6 +132,12 @@ if(WITH_RBD_RWL)
cache/pwl/test_WriteLogMap.cc)
endif(WITH_RBD_RWL)
if(LINUX AND HAVE_LIBCRYPTSETUP)
list(APPEND unittest_librbd_srcs
crypto/luks/test_mock_FormatRequest.cc
crypto/luks/test_mock_LoadRequest.cc)
endif()
add_executable(unittest_librbd
${unittest_librbd_srcs}
$<TARGET_OBJECTS:common_texttable_obj>)

View File

@ -0,0 +1,181 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#include "test/librbd/test_mock_fixture.h"
#include "test/librbd/test_support.h"
#include "test/librbd/mock/MockImageCtx.h"
namespace librbd {
namespace util {
inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
return image_ctx->image_ctx;
}
} // namespace util
} // namespace librbd
#include "librbd/crypto/luks/FormatRequest.cc"
namespace librbd {
namespace crypto {
namespace luks {
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
struct TestMockCryptoLuksFormatRequest : public TestMockFixture {
typedef FormatRequest<librbd::MockImageCtx> MockFormatRequest;
const size_t OBJECT_SIZE = 4 * 1024 * 1024;
const char* passphrase_cstr = "password";
std::string passphrase = passphrase_cstr;
MockImageCtx* mock_image_ctx;
C_SaferCond finished_cond;
Context *on_finish = &finished_cond;
io::AioCompletion* aio_comp;
ceph::bufferlist header_bl;
void SetUp() override {
TestMockFixture::SetUp();
librbd::ImageCtx *ictx;
ASSERT_EQ(0, open_image(m_image_name, &ictx));
mock_image_ctx = new MockImageCtx(*ictx);
}
void TearDown() override {
delete mock_image_ctx;
TestMockFixture::TearDown();
}
void expect_get_object_size() {
EXPECT_CALL(*mock_image_ctx, get_object_size()).WillOnce(Return(
OBJECT_SIZE));
}
void expect_crypto_layer_exists_check(bool exists = false) {
EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, exists(
io::OBJECT_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists));
}
void expect_image_write() {
EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_))
.WillOnce(Invoke([this](io::ImageDispatchSpec* spec) {
auto* write = boost::get<io::ImageDispatchSpec::Write>(
&spec->request);
ASSERT_TRUE(write != nullptr);
ASSERT_EQ(1, spec->image_extents.size());
ASSERT_EQ(0, spec->image_extents[0].first);
ASSERT_GT(spec->image_extents[0].second, 0);
spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
aio_comp = spec->aio_comp;
header_bl = write->bl;
}));
}
void complete_aio(int r) {
if (r < 0) {
aio_comp->fail(r);
} else {
aio_comp->set_request_count(1);
aio_comp->add_request();
aio_comp->complete_request(r);
}
}
void verify_header(size_t expected_key_length,
uint64_t expected_sector_size) {
Header header(mock_image_ctx->cct);
ASSERT_EQ(0, header.init());
ASSERT_EQ(0, header.write(header_bl));
ASSERT_EQ(0, header.load());
ASSERT_EQ(expected_sector_size, header.get_sector_size());
ASSERT_EQ(0, header.get_data_offset() % OBJECT_SIZE);
char volume_key[64];
size_t volume_key_size = sizeof(volume_key);
ASSERT_EQ(0, header.read_volume_key(
passphrase_cstr, strlen(passphrase_cstr),
reinterpret_cast<char*>(volume_key), &volume_key_size));
}
};
TEST_F(TestMockCryptoLuksFormatRequest, LUKS1) {
auto mock_format_request = MockFormatRequest::create(
mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS1,
CipherAlgorithm::CIPHER_ALGORITHM_AES128, std::move(passphrase),
on_finish, true);
expect_crypto_layer_exists_check();
expect_get_object_size();
expect_image_write();
mock_format_request->send();
ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
complete_aio(0);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NO_FATAL_FAILURE(verify_header(32, 512));
}
TEST_F(TestMockCryptoLuksFormatRequest, AES128) {
auto mock_format_request = MockFormatRequest::create(
mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS2,
CipherAlgorithm::CIPHER_ALGORITHM_AES128, std::move(passphrase),
on_finish, true);
expect_crypto_layer_exists_check();
expect_get_object_size();
expect_image_write();
mock_format_request->send();
ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
complete_aio(0);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NO_FATAL_FAILURE(verify_header(32, 4096));
}
TEST_F(TestMockCryptoLuksFormatRequest, AES256) {
auto mock_format_request = MockFormatRequest::create(
mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS2,
CipherAlgorithm::CIPHER_ALGORITHM_AES256, std::move(passphrase),
on_finish, true);
expect_crypto_layer_exists_check();
expect_get_object_size();
expect_image_write();
mock_format_request->send();
ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
complete_aio(0);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NO_FATAL_FAILURE(verify_header(62, 4096));
}
TEST_F(TestMockCryptoLuksFormatRequest, CryptoAlreadyLoaded) {
auto mock_format_request = MockFormatRequest::create(
mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS2,
CipherAlgorithm::CIPHER_ALGORITHM_AES256, std::move(passphrase),
on_finish, true);
expect_crypto_layer_exists_check(true);
mock_format_request->send();
ASSERT_EQ(-EEXIST, finished_cond.wait());
}
TEST_F(TestMockCryptoLuksFormatRequest, WriteFail) {
auto mock_format_request = MockFormatRequest::create(
mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS2,
CipherAlgorithm::CIPHER_ALGORITHM_AES256, std::move(passphrase),
on_finish, true);
expect_crypto_layer_exists_check();
expect_get_object_size();
expect_image_write();
mock_format_request->send();
ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
complete_aio(-123);
ASSERT_EQ(-123, finished_cond.wait());
}
} // namespace luks
} // namespace crypto
} // namespace librbd

View File

@ -0,0 +1,220 @@
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
#include "test/librbd/test_mock_fixture.h"
#include "test/librbd/test_support.h"
#include "test/librbd/mock/MockImageCtx.h"
namespace librbd {
namespace util {
inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
return image_ctx->image_ctx;
}
} // namespace util
} // namespace librbd
#include "librbd/crypto/luks/LoadRequest.cc"
namespace librbd {
namespace crypto {
namespace luks {
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
struct TestMockCryptoLuksLoadRequest : public TestMockFixture {
typedef LoadRequest<librbd::MockImageCtx> MockLoadRequest;
const size_t OBJECT_SIZE = 4 * 1024 * 1024;
const char* passphrase_cstr = "password";
std::string passphrase = passphrase_cstr;
MockImageCtx* mock_image_ctx;
ceph::ref_t<CryptoInterface> crypto;
MockLoadRequest* mock_load_request;
C_SaferCond finished_cond;
Context *on_finish = &finished_cond;
Context* image_read_request;
ceph::bufferlist header_bl;
uint64_t data_offset;
void SetUp() override {
TestMockFixture::SetUp();
librbd::ImageCtx *ictx;
ASSERT_EQ(0, open_image(m_image_name, &ictx));
mock_image_ctx = new MockImageCtx(*ictx);
crypto = nullptr;
mock_load_request = MockLoadRequest::create(
mock_image_ctx, std::move(passphrase), &crypto, on_finish);
}
void TearDown() override {
delete mock_image_ctx;
if (crypto != nullptr) {
crypto = nullptr;
}
TestMockFixture::TearDown();
}
// returns data offset in bytes
void generate_header(const char* type, const char* alg, size_t key_size,
const char* cipher_mode, uint32_t sector_size) {
Header header(mock_image_ctx->cct);
ASSERT_EQ(0, header.init());
ASSERT_EQ(0, header.format(type, alg, key_size, cipher_mode, sector_size,
OBJECT_SIZE, true));
ASSERT_EQ(0, header.add_keyslot(passphrase_cstr, strlen(passphrase_cstr)));
ASSERT_LE(0, header.read(&header_bl));
data_offset = header.get_data_offset();
}
void expect_crypto_layer_exists_check(bool exists = false) {
EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, exists(
io::OBJECT_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists));
}
void expect_image_read(uint64_t offset, uint64_t length) {
EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_))
.WillOnce(Invoke([this, offset,
length](io::ImageDispatchSpec* spec) {
auto* read = boost::get<io::ImageDispatchSpec::Read>(
&spec->request);
ASSERT_TRUE(read != nullptr);
ASSERT_EQ(1, spec->image_extents.size());
ASSERT_EQ(offset, spec->image_extents[0].first);
ASSERT_EQ(length, spec->image_extents[0].second);
spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
auto aio_comp = spec->aio_comp;
aio_comp->set_request_count(1);
aio_comp->read_result = std::move(read->read_result);
aio_comp->read_result.set_image_extents(spec->image_extents);
auto ctx = new io::ReadResult::C_ImageReadRequest(
aio_comp, 0, spec->image_extents);
if (header_bl.length() < offset + length) {
header_bl.append_zero(offset + length - header_bl.length());
}
ctx->bl.substr_of(header_bl, offset, length);
image_read_request = ctx;
}));
}
};
TEST_F(TestMockCryptoLuksLoadRequest, AES128) {
generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096);
expect_crypto_layer_exists_check();
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, nullptr);
}
TEST_F(TestMockCryptoLuksLoadRequest, AES256) {
generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
expect_crypto_layer_exists_check();
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, nullptr);
}
TEST_F(TestMockCryptoLuksLoadRequest, LUKS1) {
generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512);
expect_crypto_layer_exists_check();
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, nullptr);
}
TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedAlgorithm) {
generate_header(CRYPT_LUKS2, "twofish", 32, "xts-plain64", 4096);
expect_crypto_layer_exists_check();
expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
mock_load_request->send();
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(-ENOTSUP, finished_cond.wait());
ASSERT_EQ(crypto, nullptr);
}
TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedCipherMode) {
generate_header(CRYPT_LUKS2, "aes", 32, "cbc-essiv:sha256", 4096);
expect_crypto_layer_exists_check();
expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
mock_load_request->send();
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(-ENOTSUP, finished_cond.wait());
ASSERT_EQ(crypto, nullptr);
}
TEST_F(TestMockCryptoLuksLoadRequest, HeaderBiggerThanInitialRead) {
generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
mock_load_request->set_initial_read_size(4096);
expect_crypto_layer_exists_check();
expect_image_read(0, 4096);
mock_load_request->send();
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, nullptr);
}
TEST_F(TestMockCryptoLuksLoadRequest, KeyslotsBiggerThanInitialRead) {
generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
mock_load_request->set_initial_read_size(16384);
expect_crypto_layer_exists_check();
expect_image_read(0, 16384);
mock_load_request->send();
expect_image_read(16384, data_offset - 16384);
image_read_request->complete(16384); // complete initial read
image_read_request->complete(data_offset - 16384);
ASSERT_EQ(0, finished_cond.wait());
ASSERT_NE(crypto, nullptr);
}
TEST_F(TestMockCryptoLuksLoadRequest, WrongPassphrase) {
delete mock_load_request;
mock_load_request = MockLoadRequest::create(
mock_image_ctx, "wrong", &crypto, on_finish);
generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
expect_crypto_layer_exists_check();
expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
mock_load_request->send();
// crypt_volume_key_get will fail, we will retry reading more
expect_image_read(DEFAULT_INITIAL_READ_SIZE,
data_offset - DEFAULT_INITIAL_READ_SIZE);
image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
image_read_request->complete(data_offset - DEFAULT_INITIAL_READ_SIZE);
ASSERT_EQ(-EPERM, finished_cond.wait());
ASSERT_EQ(crypto, nullptr);
}
TEST_F(TestMockCryptoLuksLoadRequest, CryptoAlreadyLoaded) {
generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096);
expect_crypto_layer_exists_check(true);
mock_load_request->send();
ASSERT_EQ(-EEXIST, finished_cond.wait());
ASSERT_EQ(crypto, nullptr);
}
} // namespace luks
} // namespace crypto
} // namespace librbd

View File

@ -23,10 +23,11 @@ MATCHER_P(CompareArrayToString, s, "") {
struct TestMockBlockCrypto : public TestFixture {
MockDataCryptor cryptor;
BlockCrypto<MockCryptoContext>* bc;
ceph::ref_t<BlockCrypto<MockCryptoContext>> bc;
int cryptor_block_size = 2;
int cryptor_iv_size = 16;
int block_size = 4;
int data_offset = 0;
ExpectationSet* expectation_set;
void SetUp() override {
@ -35,7 +36,7 @@ struct TestMockBlockCrypto : public TestFixture {
cryptor.block_size = cryptor_block_size;
bc = new BlockCrypto<MockCryptoContext>(
reinterpret_cast<CephContext*>(m_ioctx.cct()), &cryptor,
block_size);
block_size, data_offset);
expectation_set = new ExpectationSet();
}

View File

@ -19,6 +19,10 @@ struct MockCryptoInterface : CryptoInterface {
uint64_t get_block_size() const override {
return 4096;
}
uint64_t get_data_offset() const override {
return 4 * 1024 * 1024;
}
};
} // namespace crypto

View File

@ -22,6 +22,7 @@ public:
MOCK_METHOD1(shut_down, void(Context*));
MOCK_METHOD1(register_dispatch, void(ImageDispatchInterface*));
MOCK_METHOD1(exists, bool(ImageDispatchLayer));
MOCK_METHOD2(shut_down_dispatch, void(ImageDispatchLayer, Context*));
MOCK_METHOD1(invalidate_cache, void(Context *));

View File

@ -22,6 +22,7 @@ public:
MOCK_METHOD1(shut_down, void(Context*));
MOCK_METHOD1(register_dispatch, void(ObjectDispatchInterface*));
MOCK_METHOD1(exists, bool(ObjectDispatchLayer));
MOCK_METHOD2(shut_down_dispatch, void(ObjectDispatchLayer, Context*));
MOCK_METHOD2(flush, void(FlushSource, Context*));