dae84929e4
block_crypto_amend_options_generic_luks uses the block layer permission API, therefore it should be called with the BQL held. However, the same function is being called by two BlockDriver callbacks: bdrv_amend_options (under BQL) and bdrv_co_amend (I/O). The latter is I/O because it is invoked by block/amend.c's blockdev_amend_run(), a .run callback of the amend JobDriver. Therefore we want to change this function to still perform the permission check, but making sure it is done under BQL regardless of the caller context. Remove the permission check in block_crypto_amend_options_generic_luks() and: - in block_crypto_amend_options_luks() (BQL case, called by .bdrv_amend_options()), reuse helper functions block_crypto_amend_{prepare/cleanup} that take care of checking permissions. - for block_crypto_co_amend_luks() (I/O case, called by .bdrv_co_amend()), don't check for permissions but delegate .bdrv_amend_pre_run() and .bdrv_amend_clean() to do it, performing these checks before and after the job runs in its aiocontext. Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com> Message-Id: <20220209105452.1694545-3-eesposit@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
974 lines
28 KiB
C
974 lines
28 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.1 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;
|
|
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);
|
|
if (!qemu_opts_absorb_qdict(opts, options, errp)) {
|
|
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, int64_t offset, int64_t bytes,
|
|
QEMUIOVector *qiov, BdrvRequestFlags 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, int64_t offset, int64_t bytes,
|
|
QEMUIOVector *qiov, BdrvRequestFlags 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) {
|
|
bdrv_co_delete_file_noerr(bs);
|
|
}
|
|
|
|
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->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_prepare(BlockDriverState *bs, Error **errp)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
int ret;
|
|
|
|
/* apply for exclusive read/write permissions to the underlying file */
|
|
crypto->updating_keys = true;
|
|
ret = bdrv_child_refresh_perms(bs, bs->file, errp);
|
|
if (ret < 0) {
|
|
/* Well, in this case we will not be updating any keys */
|
|
crypto->updating_keys = false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
block_crypto_amend_cleanup(BlockDriverState *bs)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
Error *errp = NULL;
|
|
|
|
/* release exclusive read/write permissions to the underlying file */
|
|
crypto->updating_keys = false;
|
|
bdrv_child_refresh_perms(bs, bs->file, &errp);
|
|
|
|
if (errp) {
|
|
error_report_err(errp);
|
|
}
|
|
}
|
|
|
|
static int
|
|
block_crypto_amend_options_generic_luks(BlockDriverState *bs,
|
|
QCryptoBlockAmendOptions *amend_options,
|
|
bool force,
|
|
Error **errp)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
|
|
assert(crypto);
|
|
assert(crypto->block);
|
|
|
|
return qcrypto_block_amend_options(crypto->block,
|
|
block_crypto_read_func,
|
|
block_crypto_write_func,
|
|
bs,
|
|
amend_options,
|
|
force,
|
|
errp);
|
|
}
|
|
|
|
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 = -EINVAL;
|
|
|
|
assert(crypto);
|
|
assert(crypto->block);
|
|
|
|
cryptoopts = qemu_opts_to_qdict(opts, NULL);
|
|
qdict_put_str(cryptoopts, "format", "luks");
|
|
amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
|
|
qobject_unref(cryptoopts);
|
|
if (!amend_options) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = block_crypto_amend_prepare(bs, errp);
|
|
if (ret) {
|
|
goto perm_cleanup;
|
|
}
|
|
ret = block_crypto_amend_options_generic_luks(bs, amend_options,
|
|
force, errp);
|
|
|
|
perm_cleanup:
|
|
block_crypto_amend_cleanup(bs);
|
|
cleanup:
|
|
qapi_free_QCryptoBlockAmendOptions(amend_options);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
coroutine_fn block_crypto_co_amend_luks(BlockDriverState *bs,
|
|
BlockdevAmendOptions *opts,
|
|
bool force,
|
|
Error **errp)
|
|
{
|
|
QCryptoBlockAmendOptions amend_opts;
|
|
|
|
amend_opts = (QCryptoBlockAmendOptions) {
|
|
.format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
|
|
.u.luks = *qapi_BlockdevAmendOptionsLUKS_base(&opts->u.luks),
|
|
};
|
|
return block_crypto_amend_options_generic_luks(bs, &amend_opts,
|
|
force, errp);
|
|
}
|
|
|
|
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 |= shared & (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,
|
|
.bdrv_co_amend = block_crypto_co_amend_luks,
|
|
.bdrv_amend_pre_run = block_crypto_amend_prepare,
|
|
.bdrv_amend_clean = block_crypto_amend_cleanup,
|
|
|
|
.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);
|