mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-30 15:00:34 +00:00
e75abedab7
We are generally moving to int64_t for both offset and bytes parameters on all io paths. Main motivation is realization of 64-bit write_zeroes operation for fast zeroing large disk chunks, up to the whole disk. We chose signed type, to be consistent with off_t (which is signed) and with possibility for signed return type (where negative value means error). So, convert driver write handlers parameters which are already 64bit to signed type. While being here, convert also flags parameter to be BdrvRequestFlags. Now let's consider all callers. Simple git grep '\->bdrv_\(aio\|co\)_pwritev\(_part\)\?' shows that's there three callers of driver function: bdrv_driver_pwritev() and bdrv_driver_pwritev_compressed() in block/io.c, both pass int64_t, checked by bdrv_check_qiov_request() to be non-negative. qcow2_save_vmstate() does bdrv_check_qiov_request(). Still, the functions may be called directly, not only by drv->... Let's check: git grep '\.bdrv_\(aio\|co\)_pwritev\(_part\)\?\s*=' | \ awk '{print $4}' | sed 's/,//' | sed 's/&//' | sort | uniq | \ while read func; do git grep "$func(" | \ grep -v "$func(BlockDriverState"; done shows several callers: qcow2: qcow2_co_truncate() write at most up to @offset, which is checked in generic qcow2_co_truncate() by bdrv_check_request(). qcow2_co_pwritev_compressed_task() pass the request (or part of the request) that already went through normal write path, so it should be OK qcow: qcow_co_pwritev_compressed() pass int64_t, it's updated by this patch quorum: quorum_co_pwrite_zeroes() pass int64_t and int - OK throttle: throttle_co_pwritev_compressed() pass int64_t, it's updated by this patch vmdk: vmdk_co_pwritev_compressed() pass int64_t, it's updated by this patch Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Message-Id: <20210903102807.27127-5-vsementsov@virtuozzo.com> Reviewed-by: Eric Blake <eblake@redhat.com> Signed-off-by: Eric Blake <eblake@redhat.com>
946 lines
27 KiB
C
946 lines
27 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_options_generic_luks(BlockDriverState *bs,
|
|
QCryptoBlockAmendOptions *amend_options,
|
|
bool force,
|
|
Error **errp)
|
|
{
|
|
BlockCrypto *crypto = bs->opaque;
|
|
int ret;
|
|
|
|
assert(crypto);
|
|
assert(crypto->block);
|
|
|
|
/* 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) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = qcrypto_block_amend_options(crypto->block,
|
|
block_crypto_read_func,
|
|
block_crypto_write_func,
|
|
bs,
|
|
amend_options,
|
|
force,
|
|
errp);
|
|
cleanup:
|
|
/* release exclusive read/write permissions to the underlying file*/
|
|
crypto->updating_keys = false;
|
|
bdrv_child_refresh_perms(bs, bs->file, errp);
|
|
return ret;
|
|
}
|
|
|
|
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_options_generic_luks(bs, amend_options,
|
|
force, errp);
|
|
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,
|
|
|
|
.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);
|