bbfdae91fb
This implements the encryption key management using the generic code in qcrypto layer and exposes it to the user via qemu-img This code adds another 'write_func' because the initialization write_func works directly on the underlying file, and amend works on instance of luks device. This commit also adds a 'hack/workaround' I and Kevin Wolf (thanks) made to make the driver both support write sharing (to avoid breaking the users), and be safe against concurrent metadata update (the keyslots) Eventually the write sharing for luks driver will be deprecated and removed together with this hack. The hack is that we ask (as a format driver) for BLK_PERM_CONSISTENT_READ and then when we want to update the keys, we unshare that permission. So if someone else has the image open, even readonly, encryption key update will fail gracefully. Also thanks to Daniel Berrange for the idea of unsharing read, rather that write permission which allows to avoid cases when the other user had opened the image read-only. Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Max Reitz <mreitz@redhat.com> Message-Id: <20200608094030.670121-8-mlevitsk@redhat.com> Signed-off-by: Max Reitz <mreitz@redhat.com>
925 lines
26 KiB
C
925 lines
26 KiB
C
/*
|
|
* QEMU block full disk encryption
|
|
*
|
|
* Copyright (c) 2015-2016 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include "block/block_int.h"
|
|
#include "block/qdict.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "crypto/block.h"
|
|
#include "qapi/opts-visitor.h"
|
|
#include "qapi/qapi-visit-crypto.h"
|
|
#include "qapi/qobject-input-visitor.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/option.h"
|
|
#include "qemu/cutils.h"
|
|
#include "crypto.h"
|
|
|
|
typedef struct BlockCrypto BlockCrypto;
|
|
|
|
struct BlockCrypto {
|
|
QCryptoBlock *block;
|
|
bool updating_keys;
|
|
};
|
|
|
|
|
|
static int block_crypto_probe_generic(QCryptoBlockFormat format,
|
|
const uint8_t *buf,
|
|
int buf_size,
|
|
const char *filename)
|
|
{
|
|
if (qcrypto_block_has_format(format, buf, buf_size)) {
|
|
return 100;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static ssize_t block_crypto_read_func(QCryptoBlock *block,
|
|
size_t offset,
|
|
uint8_t *buf,
|
|
size_t buflen,
|
|
void *opaque,
|
|
Error **errp)
|
|
{
|
|
BlockDriverState *bs = opaque;
|
|
ssize_t ret;
|
|
|
|
ret = bdrv_pread(bs->file, offset, buf, buflen);
|
|
if (ret < 0) {
|
|
error_setg_errno(errp, -ret, "Could not read encryption header");
|
|
return ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t block_crypto_write_func(QCryptoBlock *block,
|
|
size_t offset,
|
|
const uint8_t *buf,
|
|
size_t buflen,
|
|
void *opaque,
|
|
Error **errp)
|
|
{
|
|
BlockDriverState *bs = opaque;
|
|
ssize_t ret;
|
|
|
|
ret = bdrv_pwrite(bs->file, offset, buf, buflen);
|
|
if (ret < 0) {
|
|
error_setg_errno(errp, -ret, "Could not write encryption header");
|
|
return ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
struct BlockCryptoCreateData {
|
|
BlockBackend *blk;
|
|
uint64_t size;
|
|
PreallocMode prealloc;
|
|
};
|
|
|
|
|
|
static ssize_t block_crypto_create_write_func(QCryptoBlock *block,
|
|
size_t offset,
|
|
const uint8_t *buf,
|
|
size_t buflen,
|
|
void *opaque,
|
|
Error **errp)
|
|
{
|
|
struct BlockCryptoCreateData *data = opaque;
|
|
ssize_t ret;
|
|
|
|
ret = blk_pwrite(data->blk, offset, buf, buflen, 0);
|
|
if (ret < 0) {
|
|
error_setg_errno(errp, -ret, "Could not write encryption header");
|
|
return ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t block_crypto_create_init_func(QCryptoBlock *block,
|
|
size_t headerlen,
|
|
void *opaque,
|
|
Error **errp)
|
|
{
|
|
struct BlockCryptoCreateData *data = opaque;
|
|
Error *local_error = NULL;
|
|
int ret;
|
|
|
|
if (data->size > INT64_MAX || headerlen > INT64_MAX - data->size) {
|
|
ret = -EFBIG;
|
|
goto error;
|
|
}
|
|
|
|
/* User provided size should reflect amount of space made
|
|
* available to the guest, so we must take account of that
|
|
* which will be used by the crypto header
|
|
*/
|
|
ret = blk_truncate(data->blk, data->size + headerlen, false,
|
|
data->prealloc, 0, &local_error);
|
|
|
|
if (ret >= 0) {
|
|
return ret;
|
|
}
|
|
|
|
error:
|
|
if (ret == -EFBIG) {
|
|
/* Replace the error message with a better one */
|
|
error_free(local_error);
|
|
error_setg(errp, "The requested file size is too large");
|
|
} else {
|
|
error_propagate(errp, local_error);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static QemuOptsList block_crypto_runtime_opts_luks = {
|
|
.name = "crypto",
|
|
.head = QTAILQ_HEAD_INITIALIZER(block_crypto_runtime_opts_luks.head),
|
|
.desc = {
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_KEY_SECRET(""),
|
|
{ /* end of list */ }
|
|
},
|
|
};
|
|
|
|
|
|
static QemuOptsList block_crypto_create_opts_luks = {
|
|
.name = "crypto",
|
|
.head = QTAILQ_HEAD_INITIALIZER(block_crypto_create_opts_luks.head),
|
|
.desc = {
|
|
{
|
|
.name = BLOCK_OPT_SIZE,
|
|
.type = QEMU_OPT_SIZE,
|
|
.help = "Virtual disk size"
|
|
},
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_KEY_SECRET(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(""),
|
|
{ /* end of list */ }
|
|
},
|
|
};
|
|
|
|
|
|
static QemuOptsList block_crypto_amend_opts_luks = {
|
|
.name = "crypto",
|
|
.head = QTAILQ_HEAD_INITIALIZER(block_crypto_create_opts_luks.head),
|
|
.desc = {
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_STATE(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(""),
|
|
BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(""),
|
|
{ /* end of list */ }
|
|
},
|
|
};
|
|
|
|
QCryptoBlockOpenOptions *
|
|
block_crypto_open_opts_init(QDict *opts, Error **errp)
|
|
{
|
|
Visitor *v;
|
|
QCryptoBlockOpenOptions *ret;
|
|
|
|
v = qobject_input_visitor_new_flat_confused(opts, errp);
|
|
if (!v) {
|
|
return NULL;
|
|
}
|
|
|
|
visit_type_QCryptoBlockOpenOptions(v, NULL, &ret, errp);
|
|
|
|
visit_free(v);
|
|
return ret;
|
|
}
|
|
|
|
|
|
QCryptoBlockCreateOptions *
|
|
block_crypto_create_opts_init(QDict *opts, Error **errp)
|
|
{
|
|
Visitor *v;
|
|
QCryptoBlockCreateOptions *ret;
|
|
|
|
v = qobject_input_visitor_new_flat_confused(opts, errp);
|
|
if (!v) {
|
|
return NULL;
|
|
}
|
|
|
|
visit_type_QCryptoBlockCreateOptions(v, NULL, &ret, errp);
|
|
|
|
visit_free(v);
|
|
return ret;
|
|
}
|
|
|
|
QCryptoBlockAmendOptions *
|
|
block_crypto_amend_opts_init(QDict *opts, Error **errp)
|
|
{
|
|
Visitor *v;
|
|
QCryptoBlockAmendOptions *ret;
|
|
|
|
v = qobject_input_visitor_new_flat_confused(opts, errp);
|
|
if (!v) {
|
|
return NULL;
|
|
}
|
|
|
|
visit_type_QCryptoBlockAmendOptions(v, NULL, &ret, errp);
|
|
|
|
visit_free(v);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int block_crypto_open_generic(QCryptoBlockFormat format,
|
|
QemuOptsList *opts_spec,
|
|
BlockDriverState *bs,
|
|
QDict *options,
|
|
int flags,
|
|
Error **errp)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
QemuOpts *opts = NULL;
|
|
Error *local_err = NULL;
|
|
int ret = -EINVAL;
|
|
QCryptoBlockOpenOptions *open_opts = NULL;
|
|
unsigned int cflags = 0;
|
|
QDict *cryptoopts = NULL;
|
|
|
|
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
|
|
BDRV_CHILD_IMAGE, false, errp);
|
|
if (!bs->file) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
bs->supported_write_flags = BDRV_REQ_FUA &
|
|
bs->file->bs->supported_write_flags;
|
|
|
|
opts = qemu_opts_create(opts_spec, NULL, 0, &error_abort);
|
|
qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
goto cleanup;
|
|
}
|
|
|
|
cryptoopts = qemu_opts_to_qdict(opts, NULL);
|
|
qdict_put_str(cryptoopts, "format", QCryptoBlockFormat_str(format));
|
|
|
|
open_opts = block_crypto_open_opts_init(cryptoopts, errp);
|
|
if (!open_opts) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (flags & BDRV_O_NO_IO) {
|
|
cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
|
|
}
|
|
crypto->block = qcrypto_block_open(open_opts, NULL,
|
|
block_crypto_read_func,
|
|
bs,
|
|
cflags,
|
|
1,
|
|
errp);
|
|
|
|
if (!crypto->block) {
|
|
ret = -EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
bs->encrypted = true;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
qobject_unref(cryptoopts);
|
|
qapi_free_QCryptoBlockOpenOptions(open_opts);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int block_crypto_co_create_generic(BlockDriverState *bs,
|
|
int64_t size,
|
|
QCryptoBlockCreateOptions *opts,
|
|
PreallocMode prealloc,
|
|
Error **errp)
|
|
{
|
|
int ret;
|
|
BlockBackend *blk;
|
|
QCryptoBlock *crypto = NULL;
|
|
struct BlockCryptoCreateData data;
|
|
|
|
blk = blk_new_with_bs(bs, BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL,
|
|
errp);
|
|
if (!blk) {
|
|
ret = -EPERM;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (prealloc == PREALLOC_MODE_METADATA) {
|
|
prealloc = PREALLOC_MODE_OFF;
|
|
}
|
|
|
|
data = (struct BlockCryptoCreateData) {
|
|
.blk = blk,
|
|
.size = size,
|
|
.prealloc = prealloc,
|
|
};
|
|
|
|
crypto = qcrypto_block_create(opts, NULL,
|
|
block_crypto_create_init_func,
|
|
block_crypto_create_write_func,
|
|
&data,
|
|
errp);
|
|
|
|
if (!crypto) {
|
|
ret = -EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
qcrypto_block_free(crypto);
|
|
blk_unref(blk);
|
|
return ret;
|
|
}
|
|
|
|
static int coroutine_fn
|
|
block_crypto_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
|
|
PreallocMode prealloc, BdrvRequestFlags flags,
|
|
Error **errp)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
uint64_t payload_offset =
|
|
qcrypto_block_get_payload_offset(crypto->block);
|
|
|
|
if (payload_offset > INT64_MAX - offset) {
|
|
error_setg(errp, "The requested file size is too large");
|
|
return -EFBIG;
|
|
}
|
|
|
|
offset += payload_offset;
|
|
|
|
return bdrv_co_truncate(bs->file, offset, exact, prealloc, 0, errp);
|
|
}
|
|
|
|
static void block_crypto_close(BlockDriverState *bs)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
qcrypto_block_free(crypto->block);
|
|
}
|
|
|
|
static int block_crypto_reopen_prepare(BDRVReopenState *state,
|
|
BlockReopenQueue *queue, Error **errp)
|
|
{
|
|
/* nothing needs checking */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 1 MB bounce buffer gives good performance / memory tradeoff
|
|
* when using cache=none|directsync.
|
|
*/
|
|
#define BLOCK_CRYPTO_MAX_IO_SIZE (1024 * 1024)
|
|
|
|
static coroutine_fn int
|
|
block_crypto_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
|
QEMUIOVector *qiov, int flags)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
uint64_t cur_bytes; /* number of bytes in current iteration */
|
|
uint64_t bytes_done = 0;
|
|
uint8_t *cipher_data = NULL;
|
|
QEMUIOVector hd_qiov;
|
|
int ret = 0;
|
|
uint64_t sector_size = qcrypto_block_get_sector_size(crypto->block);
|
|
uint64_t payload_offset = qcrypto_block_get_payload_offset(crypto->block);
|
|
|
|
assert(!flags);
|
|
assert(payload_offset < INT64_MAX);
|
|
assert(QEMU_IS_ALIGNED(offset, sector_size));
|
|
assert(QEMU_IS_ALIGNED(bytes, sector_size));
|
|
|
|
qemu_iovec_init(&hd_qiov, qiov->niov);
|
|
|
|
/* Bounce buffer because we don't wish to expose cipher text
|
|
* in qiov which points to guest memory.
|
|
*/
|
|
cipher_data =
|
|
qemu_try_blockalign(bs->file->bs, MIN(BLOCK_CRYPTO_MAX_IO_SIZE,
|
|
qiov->size));
|
|
if (cipher_data == NULL) {
|
|
ret = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
while (bytes) {
|
|
cur_bytes = MIN(bytes, BLOCK_CRYPTO_MAX_IO_SIZE);
|
|
|
|
qemu_iovec_reset(&hd_qiov);
|
|
qemu_iovec_add(&hd_qiov, cipher_data, cur_bytes);
|
|
|
|
ret = bdrv_co_preadv(bs->file, payload_offset + offset + bytes_done,
|
|
cur_bytes, &hd_qiov, 0);
|
|
if (ret < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (qcrypto_block_decrypt(crypto->block, offset + bytes_done,
|
|
cipher_data, cur_bytes, NULL) < 0) {
|
|
ret = -EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
qemu_iovec_from_buf(qiov, bytes_done, cipher_data, cur_bytes);
|
|
|
|
bytes -= cur_bytes;
|
|
bytes_done += cur_bytes;
|
|
}
|
|
|
|
cleanup:
|
|
qemu_iovec_destroy(&hd_qiov);
|
|
qemu_vfree(cipher_data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static coroutine_fn int
|
|
block_crypto_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
|
QEMUIOVector *qiov, int flags)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
uint64_t cur_bytes; /* number of bytes in current iteration */
|
|
uint64_t bytes_done = 0;
|
|
uint8_t *cipher_data = NULL;
|
|
QEMUIOVector hd_qiov;
|
|
int ret = 0;
|
|
uint64_t sector_size = qcrypto_block_get_sector_size(crypto->block);
|
|
uint64_t payload_offset = qcrypto_block_get_payload_offset(crypto->block);
|
|
|
|
assert(!(flags & ~BDRV_REQ_FUA));
|
|
assert(payload_offset < INT64_MAX);
|
|
assert(QEMU_IS_ALIGNED(offset, sector_size));
|
|
assert(QEMU_IS_ALIGNED(bytes, sector_size));
|
|
|
|
qemu_iovec_init(&hd_qiov, qiov->niov);
|
|
|
|
/* Bounce buffer because we're not permitted to touch
|
|
* contents of qiov - it points to guest memory.
|
|
*/
|
|
cipher_data =
|
|
qemu_try_blockalign(bs->file->bs, MIN(BLOCK_CRYPTO_MAX_IO_SIZE,
|
|
qiov->size));
|
|
if (cipher_data == NULL) {
|
|
ret = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
while (bytes) {
|
|
cur_bytes = MIN(bytes, BLOCK_CRYPTO_MAX_IO_SIZE);
|
|
|
|
qemu_iovec_to_buf(qiov, bytes_done, cipher_data, cur_bytes);
|
|
|
|
if (qcrypto_block_encrypt(crypto->block, offset + bytes_done,
|
|
cipher_data, cur_bytes, NULL) < 0) {
|
|
ret = -EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
qemu_iovec_reset(&hd_qiov);
|
|
qemu_iovec_add(&hd_qiov, cipher_data, cur_bytes);
|
|
|
|
ret = bdrv_co_pwritev(bs->file, payload_offset + offset + bytes_done,
|
|
cur_bytes, &hd_qiov, flags);
|
|
if (ret < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
bytes -= cur_bytes;
|
|
bytes_done += cur_bytes;
|
|
}
|
|
|
|
cleanup:
|
|
qemu_iovec_destroy(&hd_qiov);
|
|
qemu_vfree(cipher_data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void block_crypto_refresh_limits(BlockDriverState *bs, Error **errp)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
uint64_t sector_size = qcrypto_block_get_sector_size(crypto->block);
|
|
bs->bl.request_alignment = sector_size; /* No sub-sector I/O */
|
|
}
|
|
|
|
|
|
static int64_t block_crypto_getlength(BlockDriverState *bs)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
int64_t len = bdrv_getlength(bs->file->bs);
|
|
|
|
uint64_t offset = qcrypto_block_get_payload_offset(crypto->block);
|
|
assert(offset < INT64_MAX);
|
|
|
|
if (offset > len) {
|
|
return -EIO;
|
|
}
|
|
|
|
len -= offset;
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
static BlockMeasureInfo *block_crypto_measure(QemuOpts *opts,
|
|
BlockDriverState *in_bs,
|
|
Error **errp)
|
|
{
|
|
g_autoptr(QCryptoBlockCreateOptions) create_opts = NULL;
|
|
Error *local_err = NULL;
|
|
BlockMeasureInfo *info;
|
|
uint64_t size;
|
|
size_t luks_payload_size;
|
|
QDict *cryptoopts;
|
|
|
|
/*
|
|
* Preallocation mode doesn't affect size requirements but we must consume
|
|
* the option.
|
|
*/
|
|
g_free(qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC));
|
|
|
|
size = qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0);
|
|
|
|
if (in_bs) {
|
|
int64_t ssize = bdrv_getlength(in_bs);
|
|
|
|
if (ssize < 0) {
|
|
error_setg_errno(&local_err, -ssize,
|
|
"Unable to get image virtual_size");
|
|
goto err;
|
|
}
|
|
|
|
size = ssize;
|
|
}
|
|
|
|
cryptoopts = qemu_opts_to_qdict_filtered(opts, NULL,
|
|
&block_crypto_create_opts_luks, true);
|
|
qdict_put_str(cryptoopts, "format", "luks");
|
|
create_opts = block_crypto_create_opts_init(cryptoopts, &local_err);
|
|
qobject_unref(cryptoopts);
|
|
if (!create_opts) {
|
|
goto err;
|
|
}
|
|
|
|
if (!qcrypto_block_calculate_payload_offset(create_opts, NULL,
|
|
&luks_payload_size,
|
|
&local_err)) {
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Unallocated blocks are still encrypted so allocation status makes no
|
|
* difference to the file size.
|
|
*/
|
|
info = g_new0(BlockMeasureInfo, 1);
|
|
info->fully_allocated = luks_payload_size + size;
|
|
info->required = luks_payload_size + size;
|
|
return info;
|
|
|
|
err:
|
|
error_propagate(errp, local_err);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int block_crypto_probe_luks(const uint8_t *buf,
|
|
int buf_size,
|
|
const char *filename) {
|
|
return block_crypto_probe_generic(Q_CRYPTO_BLOCK_FORMAT_LUKS,
|
|
buf, buf_size, filename);
|
|
}
|
|
|
|
static int block_crypto_open_luks(BlockDriverState *bs,
|
|
QDict *options,
|
|
int flags,
|
|
Error **errp)
|
|
{
|
|
return block_crypto_open_generic(Q_CRYPTO_BLOCK_FORMAT_LUKS,
|
|
&block_crypto_runtime_opts_luks,
|
|
bs, options, flags, errp);
|
|
}
|
|
|
|
static int coroutine_fn
|
|
block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp)
|
|
{
|
|
BlockdevCreateOptionsLUKS *luks_opts;
|
|
BlockDriverState *bs = NULL;
|
|
QCryptoBlockCreateOptions create_opts;
|
|
PreallocMode preallocation = PREALLOC_MODE_OFF;
|
|
int ret;
|
|
|
|
assert(create_options->driver == BLOCKDEV_DRIVER_LUKS);
|
|
luks_opts = &create_options->u.luks;
|
|
|
|
bs = bdrv_open_blockdev_ref(luks_opts->file, errp);
|
|
if (bs == NULL) {
|
|
return -EIO;
|
|
}
|
|
|
|
create_opts = (QCryptoBlockCreateOptions) {
|
|
.format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
|
|
.u.luks = *qapi_BlockdevCreateOptionsLUKS_base(luks_opts),
|
|
};
|
|
|
|
if (luks_opts->has_preallocation) {
|
|
preallocation = luks_opts->preallocation;
|
|
}
|
|
|
|
ret = block_crypto_co_create_generic(bs, luks_opts->size, &create_opts,
|
|
preallocation, errp);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
|
|
ret = 0;
|
|
fail:
|
|
bdrv_unref(bs);
|
|
return ret;
|
|
}
|
|
|
|
static int coroutine_fn block_crypto_co_create_opts_luks(BlockDriver *drv,
|
|
const char *filename,
|
|
QemuOpts *opts,
|
|
Error **errp)
|
|
{
|
|
QCryptoBlockCreateOptions *create_opts = NULL;
|
|
BlockDriverState *bs = NULL;
|
|
QDict *cryptoopts;
|
|
PreallocMode prealloc;
|
|
char *buf = NULL;
|
|
int64_t size;
|
|
int ret;
|
|
Error *local_err = NULL;
|
|
|
|
/* Parse options */
|
|
size = qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0);
|
|
|
|
buf = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
|
|
prealloc = qapi_enum_parse(&PreallocMode_lookup, buf,
|
|
PREALLOC_MODE_OFF, &local_err);
|
|
g_free(buf);
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cryptoopts = qemu_opts_to_qdict_filtered(opts, NULL,
|
|
&block_crypto_create_opts_luks,
|
|
true);
|
|
|
|
qdict_put_str(cryptoopts, "format", "luks");
|
|
create_opts = block_crypto_create_opts_init(cryptoopts, errp);
|
|
if (!create_opts) {
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/* Create protocol layer */
|
|
ret = bdrv_create_file(filename, opts, errp);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
|
|
bs = bdrv_open(filename, NULL, NULL,
|
|
BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp);
|
|
if (!bs) {
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/* Create format layer */
|
|
ret = block_crypto_co_create_generic(bs, size, create_opts, prealloc, errp);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
|
|
ret = 0;
|
|
fail:
|
|
/*
|
|
* If an error occurred, delete 'filename'. Even if the file existed
|
|
* beforehand, it has been truncated and corrupted in the process.
|
|
*/
|
|
if (ret && bs) {
|
|
Error *local_delete_err = NULL;
|
|
int r_del = bdrv_co_delete_file(bs, &local_delete_err);
|
|
/*
|
|
* ENOTSUP will happen if the block driver doesn't support
|
|
* the 'bdrv_co_delete_file' interface. This is a predictable
|
|
* scenario and shouldn't be reported back to the user.
|
|
*/
|
|
if ((r_del < 0) && (r_del != -ENOTSUP)) {
|
|
error_report_err(local_delete_err);
|
|
}
|
|
}
|
|
|
|
bdrv_unref(bs);
|
|
qapi_free_QCryptoBlockCreateOptions(create_opts);
|
|
qobject_unref(cryptoopts);
|
|
return ret;
|
|
}
|
|
|
|
static int block_crypto_get_info_luks(BlockDriverState *bs,
|
|
BlockDriverInfo *bdi)
|
|
{
|
|
BlockDriverInfo subbdi;
|
|
int ret;
|
|
|
|
ret = bdrv_get_info(bs->file->bs, &subbdi);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
bdi->unallocated_blocks_are_zero = false;
|
|
bdi->cluster_size = subbdi.cluster_size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ImageInfoSpecific *
|
|
block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
ImageInfoSpecific *spec_info;
|
|
QCryptoBlockInfo *info;
|
|
|
|
info = qcrypto_block_get_info(crypto->block, errp);
|
|
if (!info) {
|
|
return NULL;
|
|
}
|
|
assert(info->format == Q_CRYPTO_BLOCK_FORMAT_LUKS);
|
|
|
|
spec_info = g_new(ImageInfoSpecific, 1);
|
|
spec_info->type = IMAGE_INFO_SPECIFIC_KIND_LUKS;
|
|
spec_info->u.luks.data = g_new(QCryptoBlockInfoLUKS, 1);
|
|
*spec_info->u.luks.data = info->u.luks;
|
|
|
|
/* Blank out pointers we've just stolen to avoid double free */
|
|
memset(&info->u.luks, 0, sizeof(info->u.luks));
|
|
|
|
qapi_free_QCryptoBlockInfo(info);
|
|
|
|
return spec_info;
|
|
}
|
|
|
|
static int
|
|
block_crypto_amend_options_luks(BlockDriverState *bs,
|
|
QemuOpts *opts,
|
|
BlockDriverAmendStatusCB *status_cb,
|
|
void *cb_opaque,
|
|
bool force,
|
|
Error **errp)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
QDict *cryptoopts = NULL;
|
|
QCryptoBlockAmendOptions *amend_options = NULL;
|
|
int ret;
|
|
|
|
assert(crypto);
|
|
assert(crypto->block);
|
|
crypto->updating_keys = true;
|
|
|
|
ret = bdrv_child_refresh_perms(bs, bs->file, errp);
|
|
if (ret < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
cryptoopts = qemu_opts_to_qdict(opts, NULL);
|
|
qdict_put_str(cryptoopts, "format", "luks");
|
|
amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
|
|
if (!amend_options) {
|
|
ret = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = qcrypto_block_amend_options(crypto->block,
|
|
block_crypto_read_func,
|
|
block_crypto_write_func,
|
|
bs,
|
|
amend_options,
|
|
force,
|
|
errp);
|
|
cleanup:
|
|
crypto->updating_keys = false;
|
|
bdrv_child_refresh_perms(bs, bs->file, errp);
|
|
qapi_free_QCryptoBlockAmendOptions(amend_options);
|
|
qobject_unref(cryptoopts);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c,
|
|
const BdrvChildRole role,
|
|
BlockReopenQueue *reopen_queue,
|
|
uint64_t perm, uint64_t shared,
|
|
uint64_t *nperm, uint64_t *nshared)
|
|
{
|
|
|
|
BlockCrypto *crypto = bs->opaque;
|
|
|
|
bdrv_default_perms(bs, c, role, reopen_queue, perm, shared, nperm, nshared);
|
|
|
|
/*
|
|
* For backward compatibility, manually share the write
|
|
* and resize permission
|
|
*/
|
|
*nshared |= (BLK_PERM_WRITE | BLK_PERM_RESIZE);
|
|
/*
|
|
* Since we are not fully a format driver, don't always request
|
|
* the read/resize permission but only when explicitly
|
|
* requested
|
|
*/
|
|
*nperm &= ~(BLK_PERM_WRITE | BLK_PERM_RESIZE);
|
|
*nperm |= perm & (BLK_PERM_WRITE | BLK_PERM_RESIZE);
|
|
|
|
/*
|
|
* This driver doesn't modify LUKS metadata except
|
|
* when updating the encryption slots.
|
|
* Thus unlike a proper format driver we don't ask for
|
|
* shared write/read permission. However we need it
|
|
* when we are updating the keys, to ensure that only we
|
|
* have access to the device.
|
|
*
|
|
* Encryption update will set the crypto->updating_keys
|
|
* during that period and refresh permissions
|
|
*
|
|
*/
|
|
if (crypto->updating_keys) {
|
|
/* need exclusive write access for header update */
|
|
*nperm |= BLK_PERM_WRITE;
|
|
/* unshare read and write permission */
|
|
*nshared &= ~(BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE);
|
|
}
|
|
}
|
|
|
|
|
|
static const char *const block_crypto_strong_runtime_opts[] = {
|
|
BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
|
|
|
|
NULL
|
|
};
|
|
|
|
static BlockDriver bdrv_crypto_luks = {
|
|
.format_name = "luks",
|
|
.instance_size = sizeof(BlockCrypto),
|
|
.bdrv_probe = block_crypto_probe_luks,
|
|
.bdrv_open = block_crypto_open_luks,
|
|
.bdrv_close = block_crypto_close,
|
|
.bdrv_child_perm = block_crypto_child_perms,
|
|
.bdrv_co_create = block_crypto_co_create_luks,
|
|
.bdrv_co_create_opts = block_crypto_co_create_opts_luks,
|
|
.bdrv_co_truncate = block_crypto_co_truncate,
|
|
.create_opts = &block_crypto_create_opts_luks,
|
|
.amend_opts = &block_crypto_amend_opts_luks,
|
|
|
|
.bdrv_reopen_prepare = block_crypto_reopen_prepare,
|
|
.bdrv_refresh_limits = block_crypto_refresh_limits,
|
|
.bdrv_co_preadv = block_crypto_co_preadv,
|
|
.bdrv_co_pwritev = block_crypto_co_pwritev,
|
|
.bdrv_getlength = block_crypto_getlength,
|
|
.bdrv_measure = block_crypto_measure,
|
|
.bdrv_get_info = block_crypto_get_info_luks,
|
|
.bdrv_get_specific_info = block_crypto_get_specific_info_luks,
|
|
.bdrv_amend_options = block_crypto_amend_options_luks,
|
|
|
|
.is_format = true,
|
|
|
|
.strong_runtime_opts = block_crypto_strong_runtime_opts,
|
|
};
|
|
|
|
static void block_crypto_init(void)
|
|
{
|
|
bdrv_register(&bdrv_crypto_luks);
|
|
}
|
|
|
|
block_init(block_crypto_init);
|