From patchwork Tue Jan 14 19:33:38 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223021 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=b5soKyJD; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y13G0M9Yz9s29 for ; Wed, 15 Jan 2020 06:38:34 +1100 (AEDT) Received: from localhost ([::1]:45154 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS1H-0006PV-7T for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:38:31 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38268) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRy6-0002qM-LR for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:18 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRy2-0007MU-P7 for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:14 -0500 Received: from us-smtp-delivery-1.mimecast.com ([205.139.110.120]:54282 helo=us-smtp-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRy2-0007M7-M2 for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:10 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030510; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=mEid+kqmtREdcGXsS/B0DLkuAdFUGXFLmGn8yhd1QZs=; b=b5soKyJDrtSYuBqwq8NB4QeFgPXW8gWtmR2HX9TUmnrutBfG6Bdn9inqac7qb8GRyLOSie YDi5KzHb5y/ojP9WB8qoccRif3uPHF0CWMyW9Yi6yVCdXb99uulCbJXPzSRI6Sac+9XBQL pdNUSm5h0cfNeHav214usTXn7Lt8NUE= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-143-x-rPJ7SXMvCRCy-ffzuXhA-1; Tue, 14 Jan 2020 14:34:01 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 3D14B8DF660; Tue, 14 Jan 2020 19:34:00 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 026B17C83A; Tue, 14 Jan 2020 19:33:57 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 01/13] qcrypto: add generic infrastructure for crypto options amendment Date: Tue, 14 Jan 2020 21:33:38 +0200 Message-Id: <20200114193350.10830-2-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: x-rPJ7SXMvCRCy-ffzuXhA-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 205.139.110.120 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" This will be used first to implement luks keyslot management. block_crypto_amend_opts_init will be used to convert qemu-img cmdline to QCryptoBlockAmendOptions Signed-off-by: Maxim Levitsky Reviewed-by: Daniel P. Berrangé --- block/crypto.c | 17 +++++++++++++++++ block/crypto.h | 3 +++ crypto/block.c | 31 +++++++++++++++++++++++++++++++ crypto/blockpriv.h | 8 ++++++++ include/crypto/block.h | 22 ++++++++++++++++++++++ qapi/crypto.json | 16 ++++++++++++++++ 6 files changed, 97 insertions(+) diff --git a/block/crypto.c b/block/crypto.c index 24823835c1..ecf96a7a9b 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -184,6 +184,23 @@ block_crypto_create_opts_init(QDict *opts, Error **errp) 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, diff --git a/block/crypto.h b/block/crypto.h index b935695e79..06e044c9be 100644 --- a/block/crypto.h +++ b/block/crypto.h @@ -91,6 +91,9 @@ QCryptoBlockCreateOptions * block_crypto_create_opts_init(QDict *opts, Error **errp); +QCryptoBlockAmendOptions * +block_crypto_amend_opts_init(QDict *opts, Error **errp); + QCryptoBlockOpenOptions * block_crypto_open_opts_init(QDict *opts, Error **errp); diff --git a/crypto/block.c b/crypto/block.c index 325752871c..0ce67db641 100644 --- a/crypto/block.c +++ b/crypto/block.c @@ -115,6 +115,37 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options, } +int qcrypto_block_amend_options(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + QCryptoBlockAmendOptions *options, + bool force, + Error **errp) +{ + if (options->format != block->format) { + error_setg(errp, + "Cannot amend encryption format"); + return -1; + } + + if (!block->driver->amend) { + error_setg(errp, + "Crypto format %s doesn't support format options amendment", + QCryptoBlockFormat_str(block->format)); + return -1; + } + + return block->driver->amend(block, + readfunc, + writefunc, + opaque, + options, + force, + errp); +} + + QCryptoBlockInfo *qcrypto_block_get_info(QCryptoBlock *block, Error **errp) { diff --git a/crypto/blockpriv.h b/crypto/blockpriv.h index 71c59cb542..3c7ccea504 100644 --- a/crypto/blockpriv.h +++ b/crypto/blockpriv.h @@ -62,6 +62,14 @@ struct QCryptoBlockDriver { void *opaque, Error **errp); + int (*amend)(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + QCryptoBlockAmendOptions *options, + bool force, + Error **errp); + int (*get_info)(QCryptoBlock *block, QCryptoBlockInfo *info, Error **errp); diff --git a/include/crypto/block.h b/include/crypto/block.h index d49d2c2da9..e4553cf33d 100644 --- a/include/crypto/block.h +++ b/include/crypto/block.h @@ -144,6 +144,28 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options, void *opaque, Error **errp); +/** + * qcrypto_block_amend_options: + * @block: the block encryption object + * + * @readfunc: callback for reading data from the volume header + * @writefunc: callback for writing data to the volume header + * @opaque: data to pass to @readfunc and @writefunc + * @options: the new/amended encryption options + * @force: hint for the driver to allow unsafe operation + * @errp: error pointer + * + * Changes the crypto options of the encryption format + * + */ +int qcrypto_block_amend_options(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + QCryptoBlockAmendOptions *options, + bool force, + Error **errp); + /** * qcrypto_block_get_info: diff --git a/qapi/crypto.json b/qapi/crypto.json index b2a4cff683..9faebd03d4 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -309,3 +309,19 @@ 'base': 'QCryptoBlockInfoBase', 'discriminator': 'format', 'data': { 'luks': 'QCryptoBlockInfoLUKS' } } + + + +## +# @QCryptoBlockAmendOptions: +# +# The options that are available for all encryption formats +# when initializing a new volume +# +# Since: 5.0 +## +{ 'union': 'QCryptoBlockAmendOptions', + 'base': 'QCryptoBlockOptionsBase', + 'discriminator': 'format', + 'data': { + } } From patchwork Tue Jan 14 19:33:39 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223025 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=iEfTwy1R; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y16Y1h9lz9sP3 for ; Wed, 15 Jan 2020 06:41:25 +1100 (AEDT) Received: from localhost ([::1]:45194 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS42-0001r4-Al for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:41:22 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38298) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRy9-0002vG-VJ for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:21 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRy6-0007PP-0P for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:17 -0500 Received: from us-smtp-delivery-1.mimecast.com ([205.139.110.120]:30032 helo=us-smtp-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRy5-0007P5-LV for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:13 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030513; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=bI2puMl27MjuC4RNrwvEo4Az2aJ/mxAOHmSpojj8B/M=; b=iEfTwy1RlpqOKjJPfsm9q0IWKSJQY3hr+JdwX7LehiuoYeTc3zqnuaQEoEaGFJeBLU/mhb xThS6EdKQgrLlBceG4zvUDowvrZBIRidKyBzriw/EYv6zAG1IfLOS7+0nMld6Aj3/tZgjG o4lKzryf6KMqZrwPs9xV8gi5vPG3XVk= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-201-bx9LcapiOTifA2CwGrBZ7g-1; Tue, 14 Jan 2020 14:34:04 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 13EC5DC22; Tue, 14 Jan 2020 19:34:03 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 98E6D7C83A; Tue, 14 Jan 2020 19:34:00 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 02/13] qcrypto-luks: implement encryption key management Date: Tue, 14 Jan 2020 21:33:39 +0200 Message-Id: <20200114193350.10830-3-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: bx9LcapiOTifA2CwGrBZ7g-1 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 205.139.110.120 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" Next few patches will expose that functionality to the user. Signed-off-by: Maxim Levitsky --- crypto/block-luks.c | 374 +++++++++++++++++++++++++++++++++++++++++++- qapi/crypto.json | 50 +++++- 2 files changed, 421 insertions(+), 3 deletions(-) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index 4861db810c..349e95fed3 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -32,6 +32,7 @@ #include "qemu/uuid.h" #include "qemu/coroutine.h" +#include "qemu/bitmap.h" /* * Reference for the LUKS format implemented here is @@ -70,6 +71,9 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot; #define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL +#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS 2000 +#define QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS 40 + static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = { 'L', 'U', 'K', 'S', 0xBA, 0xBE }; @@ -219,6 +223,9 @@ struct QCryptoBlockLUKS { /* Hash algorithm used in pbkdf2 function */ QCryptoHashAlgorithm hash_alg; + + /* Name of the secret that was used to open the image */ + char *secret; }; @@ -1069,6 +1076,112 @@ qcrypto_block_luks_find_key(QCryptoBlock *block, return -1; } +/* + * Returns true if a slot i is marked as active + * (contains encrypted copy of the master key) + */ +static bool +qcrypto_block_luks_slot_active(const QCryptoBlockLUKS *luks, + unsigned int slot_idx) +{ + uint32_t val = luks->header.key_slots[slot_idx].active; + return val == QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED; +} + +/* + * Returns the number of slots that are marked as active + * (slots that contain encrypted copy of the master key) + */ +static unsigned int +qcrypto_block_luks_count_active_slots(const QCryptoBlockLUKS *luks) +{ + size_t i = 0; + unsigned int ret = 0; + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + if (qcrypto_block_luks_slot_active(luks, i)) { + ret++; + } + } + return ret; +} + +/* + * Finds first key slot which is not active + * Returns the key slot index, or -1 if it doesn't exist + */ +static int +qcrypto_block_luks_find_free_keyslot(const QCryptoBlockLUKS *luks) +{ + size_t i; + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + if (!qcrypto_block_luks_slot_active(luks, i)) { + return i; + } + } + return -1; + +} + +/* + * Erases an keyslot given its index + * Returns: + * 0 if the keyslot was erased successfully + * -1 if a error occurred while erasing the keyslot + * + */ +static int +qcrypto_block_luks_erase_key(QCryptoBlock *block, + unsigned int slot_idx, + QCryptoBlockWriteFunc writefunc, + void *opaque, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx]; + g_autofree uint8_t *garbagesplitkey = NULL; + size_t splitkeylen = luks->header.master_key_len * slot->stripes; + size_t i; + + assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS); + assert(splitkeylen > 0); + + garbagesplitkey = g_new0(uint8_t, splitkeylen); + + /* Reset the key slot header */ + memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN); + slot->iterations = 0; + slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED; + + qcrypto_block_luks_store_header(block, writefunc, opaque, errp); + + /* + * Now try to erase the key material, even if the header + * update failed + */ + for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) { + if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) { + /* + * If we failed to get the random data, still write + * at least zeros to the key slot at least once + */ + if (i > 0) { + return -1; + } + } + + if (writefunc(block, + slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE, + garbagesplitkey, + splitkeylen, + opaque, + errp) != splitkeylen) { + return -1; + } + } + return 0; +} static int qcrypto_block_luks_open(QCryptoBlock *block, @@ -1099,6 +1212,7 @@ qcrypto_block_luks_open(QCryptoBlock *block, luks = g_new0(QCryptoBlockLUKS, 1); block->opaque = luks; + luks->secret = g_strdup(options->u.luks.key_secret); if (qcrypto_block_luks_load_header(block, readfunc, opaque, errp) < 0) { goto fail; @@ -1164,6 +1278,7 @@ qcrypto_block_luks_open(QCryptoBlock *block, fail: qcrypto_block_free_cipher(block); qcrypto_ivgen_free(block->ivgen); + g_free(luks->secret); g_free(luks); return -1; } @@ -1204,7 +1319,7 @@ qcrypto_block_luks_create(QCryptoBlock *block, memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts)); if (!luks_opts.has_iter_time) { - luks_opts.iter_time = 2000; + luks_opts.iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS; } if (!luks_opts.has_cipher_alg) { luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256; @@ -1244,6 +1359,8 @@ qcrypto_block_luks_create(QCryptoBlock *block, optprefix ? optprefix : ""); goto error; } + luks->secret = g_strdup(options->u.luks.key_secret); + password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp); if (!password) { goto error; @@ -1471,10 +1588,260 @@ qcrypto_block_luks_create(QCryptoBlock *block, qcrypto_block_free_cipher(block); qcrypto_ivgen_free(block->ivgen); + g_free(luks->secret); g_free(luks); return -1; } +/* + * Given LUKSKeyslotUpdate command, return @slots_bitmap with all slots + * that will be updated with new password (or erased) + * returns number of affected slots + */ +static int qcrypto_block_luks_get_slots_bitmap(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + void *opaque, + const LUKSKeyslotUpdate *command, + unsigned long *slots_bitmap, + Error **errp) +{ + const QCryptoBlockLUKS *luks = block->opaque; + size_t i; + int ret = 0; + + if (command->has_keyslot) { + /* keyslot set, select only this keyslot */ + int keyslot = command->keyslot; + + if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) { + error_setg(errp, + "Invalid slot %u specified, must be between 0 and %u", + keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1); + goto error; + } + bitmap_set(slots_bitmap, keyslot, 1); + ret++; + + } else if (command->has_old_secret) { + /* initially select all active keyslots */ + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + if (qcrypto_block_luks_slot_active(luks, i)) { + bitmap_set(slots_bitmap, i, 1); + ret++; + } + } + } else { + /* find a free keyslot */ + int slot = qcrypto_block_luks_find_free_keyslot(luks); + + if (slot == -1) { + error_setg(errp, + "Can't add a keyslot - all key slots are in use"); + goto error; + } + bitmap_set(slots_bitmap, slot, 1); + ret++; + } + + if (command->has_old_secret) { + /* now deselect all keyslots that don't contain the password */ + g_autofree uint8_t *tmpkey = g_new0(uint8_t, + luks->header.master_key_len); + + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + g_autofree char *old_password = NULL; + int rv; + + if (!test_bit(i, slots_bitmap)) { + continue; + } + + old_password = qcrypto_secret_lookup_as_utf8(command->old_secret, + errp); + if (!old_password) { + goto error; + } + + rv = qcrypto_block_luks_load_key(block, + i, + old_password, + tmpkey, + readfunc, + opaque, + errp); + if (rv == -1) + goto error; + else if (rv == 0) { + bitmap_clear(slots_bitmap, i, 1); + ret--; + } + } + } + return ret; +error: + return -1; +} + +/* + * Apply a single keyslot update command as described in @command + * Optionally use @unlock_secret to retrieve the master key + */ +static int +qcrypto_block_luks_apply_keyslot_update(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + LUKSKeyslotUpdate *command, + const char *unlock_secret, + uint8_t **master_key, + bool force, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + g_autofree unsigned long *slots_bitmap = NULL; + int64_t iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS; + int slot_count; + size_t i; + char *new_password; + bool erasing; + + slots_bitmap = bitmap_new(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS); + slot_count = qcrypto_block_luks_get_slots_bitmap(block, readfunc, opaque, + command, slots_bitmap, + errp); + if (slot_count == -1) { + goto error; + } + /* no matching slots, so nothing to do */ + if (slot_count == 0) { + error_setg(errp, "Requested operation didn't match any slots"); + goto error; + } + /* + * slot is erased when the password is set to null, or empty string + * (for compatibility with command line) + */ + erasing = command->new_secret->type == QTYPE_QNULL || + strlen(command->new_secret->u.s) == 0; + + /* safety checks */ + if (!force) { + if (erasing) { + if (slot_count == qcrypto_block_luks_count_active_slots(luks)) { + error_setg(errp, + "Requested operation will erase all active keyslots" + " which will erase all the data in the image" + " irreversibly - refusing operation"); + goto error; + } + } else { + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + if (!test_bit(i, slots_bitmap)) { + continue; + } + if (qcrypto_block_luks_slot_active(luks, i)) { + error_setg(errp, + "Refusing to overwrite active slot %zu - " + "please erase it first", i); + goto error; + } + } + } + } + + /* setup the data needed for storing the new keyslot */ + if (!erasing) { + /* Load the master key if it wasn't already loaded */ + if (!*master_key) { + g_autofree char *old_password; + old_password = qcrypto_secret_lookup_as_utf8(unlock_secret, errp); + if (!old_password) { + goto error; + } + *master_key = g_new0(uint8_t, luks->header.master_key_len); + + if (qcrypto_block_luks_find_key(block, old_password, *master_key, + readfunc, opaque, errp) < 0) { + error_append_hint(errp, "Failed to retrieve the master key"); + goto error; + } + } + new_password = qcrypto_secret_lookup_as_utf8(command->new_secret->u.s, + errp); + if (!new_password) { + goto error; + } + if (command->has_iter_time) { + iter_time = command->iter_time; + } + } + + /* new apply the update */ + for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { + if (!test_bit(i, slots_bitmap)) { + continue; + } + if (erasing) { + if (qcrypto_block_luks_erase_key(block, i, + writefunc, + opaque, + errp)) { + error_append_hint(errp, "Failed to erase keyslot %zu", i); + goto error; + } + } else { + if (qcrypto_block_luks_store_key(block, i, + new_password, + *master_key, + iter_time, + writefunc, + opaque, + errp)) { + error_append_hint(errp, "Failed to write to keyslot %zu", i); + goto error; + } + } + } + return 0; +error: + return -EINVAL; +} + +static int +qcrypto_block_luks_amend_options(QCryptoBlock *block, + QCryptoBlockReadFunc readfunc, + QCryptoBlockWriteFunc writefunc, + void *opaque, + QCryptoBlockAmendOptions *options, + bool force, + Error **errp) +{ + QCryptoBlockLUKS *luks = block->opaque; + QCryptoBlockAmendOptionsLUKS *options_luks = &options->u.luks; + LUKSKeyslotUpdateList *ptr; + g_autofree uint8_t *master_key = NULL; + int ret; + + char *unlock_secret = options_luks->has_unlock_secret ? + options_luks->unlock_secret : + luks->secret; + + for (ptr = options_luks->keys; ptr; ptr = ptr->next) { + ret = qcrypto_block_luks_apply_keyslot_update(block, readfunc, + writefunc, opaque, + ptr->value, + unlock_secret, + &master_key, + force, errp); + + if (ret != 0) { + goto error; + } + } + return 0; +error: + return -1; +} static int qcrypto_block_luks_get_info(QCryptoBlock *block, QCryptoBlockInfo *info, @@ -1523,7 +1890,9 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block, static void qcrypto_block_luks_cleanup(QCryptoBlock *block) { - g_free(block->opaque); + QCryptoBlockLUKS *luks = block->opaque; + g_free(luks->secret); + g_free(luks); } @@ -1560,6 +1929,7 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block, const QCryptoBlockDriver qcrypto_block_driver_luks = { .open = qcrypto_block_luks_open, .create = qcrypto_block_luks_create, + .amend = qcrypto_block_luks_amend_options, .get_info = qcrypto_block_luks_get_info, .cleanup = qcrypto_block_luks_cleanup, .decrypt = qcrypto_block_luks_decrypt, diff --git a/qapi/crypto.json b/qapi/crypto.json index 9faebd03d4..e83847c71e 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -1,6 +1,8 @@ # -*- Mode: Python -*- # +{ 'include': 'common.json' } + ## # = Cryptography ## @@ -310,6 +312,52 @@ 'discriminator': 'format', 'data': { 'luks': 'QCryptoBlockInfoLUKS' } } +## +# @LUKSKeyslotUpdate: +# +# @keyslot: If specified, will update only keyslot with this index +# +# @old-secret: If specified, will only update keyslots that +# can be opened with password which is contained in +# QCryptoSecret with @old-secret ID +# +# If neither @keyslot nor @old-secret is specified, +# first empty keyslot is selected for the update +# +# @new-secret: The ID of a QCryptoSecret object providing a new decryption +# key to place in all matching keyslots. +# null/empty string erases all matching keyslots unless +# last valid keyslot is erased. +# +# @iter-time: number of milliseconds to spend in +# PBKDF passphrase processing +# Since: 5.0 +## +{ 'struct': 'LUKSKeyslotUpdate', + 'data': { + '*keyslot': 'int', + '*old-secret': 'str', + 'new-secret' : 'StrOrNull', + '*iter-time' : 'int' } } + + +## +# @QCryptoBlockAmendOptionsLUKS: +# +# The options that can be changed on existing luks encrypted device +# @keys: list of keyslot updates to perform +# (updates are performed in order) +# @unlock-secret: use this secret to retrieve the current master key +# if not given will use the same secret as one +# that was used to open the image +# +# Since: 5.0 +## +{ 'struct': 'QCryptoBlockAmendOptionsLUKS', + 'data' : { + 'keys': ['LUKSKeyslotUpdate'], + '*unlock-secret' : 'str' } } + ## @@ -324,4 +372,4 @@ 'base': 'QCryptoBlockOptionsBase', 'discriminator': 'format', 'data': { - } } + 'luks': 'QCryptoBlockAmendOptionsLUKS' } } From patchwork Tue Jan 14 19:33:40 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223019 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=JwQBwUn5; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y10V0Y6hz9s29 for ; Wed, 15 Jan 2020 06:36:10 +1100 (AEDT) Received: from localhost ([::1]:45094 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyx-00036N-Fn for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:36:07 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38325) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyC-00030q-Ms for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:23 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRy8-0007R1-Vx for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:20 -0500 Received: from us-smtp-2.mimecast.com ([207.211.31.81]:42361 helo=us-smtp-delivery-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRy8-0007Ql-S6 for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:16 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030516; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=7gcSulAK3g0ada/p/fOzW+hSdXGKJ7gztXkq2XhBcF8=; b=JwQBwUn54WgIEMh9pQXMkEGG1OrF8rMyxNZCK7t2CS3cQd5bWbb+ao4WHgGXJOPNpm7/qq DSFVMoNEmFggUQaPB7D3amA6/34Wezuie5aKnpJLlYSSRlwLb0qEwIR5xjMXKwyZuEE9mN umxk9qUY3pR1L+Vfi/ItUG2JH2rQ5sg= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-155-d5SQZJ5ON1OqXRBYhx-rMA-1; Tue, 14 Jan 2020 14:34:06 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id A98561800D78; Tue, 14 Jan 2020 19:34:05 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 6E06C7C83A; Tue, 14 Jan 2020 19:34:03 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 03/13] block: amend: add 'force' option Date: Tue, 14 Jan 2020 21:33:40 +0200 Message-Id: <20200114193350.10830-4-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: d5SQZJ5ON1OqXRBYhx-rMA-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.81 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" 'force' option will be used for some unsafe amend operations. This includes things like erasing last keyslot in luks based formats (which destroys the data, unless the master key is backed up by external means), but that _might_ be desired result. Signed-off-by: Maxim Levitsky Reviewed-by: Daniel P. Berrangé --- block.c | 4 +++- block/qcow2.c | 1 + include/block/block.h | 1 + include/block/block_int.h | 1 + qemu-img-cmds.hx | 4 ++-- qemu-img.c | 8 +++++++- qemu-img.texi | 6 +++++- 7 files changed, 20 insertions(+), 5 deletions(-) diff --git a/block.c b/block.c index ecd09dbbfd..cb9d5f1965 100644 --- a/block.c +++ b/block.c @@ -6164,6 +6164,7 @@ void bdrv_remove_aio_context_notifier(BlockDriverState *bs, int bdrv_amend_options(BlockDriverState *bs, QemuOpts *opts, BlockDriverAmendStatusCB *status_cb, void *cb_opaque, + bool force, Error **errp) { if (!bs->drv) { @@ -6175,7 +6176,8 @@ int bdrv_amend_options(BlockDriverState *bs, QemuOpts *opts, bs->drv->format_name); return -ENOTSUP; } - return bs->drv->bdrv_amend_options(bs, opts, status_cb, cb_opaque, errp); + return bs->drv->bdrv_amend_options(bs, opts, status_cb, + cb_opaque, force, errp); } /* This function will be called by the bdrv_recurse_is_first_non_filter method diff --git a/block/qcow2.c b/block/qcow2.c index cef9d72b3a..6bcf4a5fc4 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -5150,6 +5150,7 @@ static void qcow2_amend_helper_cb(BlockDriverState *bs, static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts, BlockDriverAmendStatusCB *status_cb, void *cb_opaque, + bool force, Error **errp) { BDRVQcow2State *s = bs->opaque; diff --git a/include/block/block.h b/include/block/block.h index e9dcfef7fa..d0cd1b2da0 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -399,6 +399,7 @@ typedef void BlockDriverAmendStatusCB(BlockDriverState *bs, int64_t offset, int64_t total_work_size, void *opaque); int bdrv_amend_options(BlockDriverState *bs_new, QemuOpts *opts, BlockDriverAmendStatusCB *status_cb, void *cb_opaque, + bool force, Error **errp); /* external snapshots */ diff --git a/include/block/block_int.h b/include/block/block_int.h index dd033d0b37..810a9ecb86 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -427,6 +427,7 @@ struct BlockDriver { int (*bdrv_amend_options)(BlockDriverState *bs, QemuOpts *opts, BlockDriverAmendStatusCB *status_cb, void *cb_opaque, + bool force, Error **errp); void (*bdrv_debug_event)(BlockDriverState *bs, BlkdebugEvent event); diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 1c93e6d185..323ea10ad0 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -14,9 +14,9 @@ STEXI ETEXI DEF("amend", img_amend, - "amend [--object objectdef] [--image-opts] [-p] [-q] [-f fmt] [-t cache] -o options filename") + "amend [--object objectdef] [--image-opts] [-p] [-q] [-f fmt] [-t cache] [--force] -o options filename") STEXI -@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename} +@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] [--force] -o @var{options} @var{filename} ETEXI DEF("bench", img_bench, diff --git a/qemu-img.c b/qemu-img.c index 6233b8ca56..a79f3904db 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -70,6 +70,7 @@ enum { OPTION_PREALLOCATION = 265, OPTION_SHRINK = 266, OPTION_SALVAGE = 267, + OPTION_FORCE = 268, }; typedef enum OutputFormat { @@ -3900,6 +3901,7 @@ static int img_amend(int argc, char **argv) BlockBackend *blk = NULL; BlockDriverState *bs = NULL; bool image_opts = false; + bool force = false; cache = BDRV_DEFAULT_CACHE; for (;;) { @@ -3907,6 +3909,7 @@ static int img_amend(int argc, char **argv) {"help", no_argument, 0, 'h'}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"force", no_argument, 0, OPTION_FORCE}, {0, 0, 0, 0} }; c = getopt_long(argc, argv, ":ho:f:t:pq", @@ -3962,6 +3965,9 @@ static int img_amend(int argc, char **argv) case OPTION_IMAGE_OPTS: image_opts = true; break; + case OPTION_FORCE: + force = true; + break; } } @@ -4039,7 +4045,7 @@ static int img_amend(int argc, char **argv) /* In case the driver does not call amend_status_cb() */ qemu_progress_print(0.f, 0); - ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, &err); + ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, force, &err); qemu_progress_print(100.f, 0); if (ret < 0) { error_report_err(err); diff --git a/qemu-img.texi b/qemu-img.texi index b5156d6316..b6ed4357e8 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -201,11 +201,15 @@ Command description: @table @option -@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename} +@item amend [--object @var{objectdef}] [--image-opts] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] [--force] -o @var{options} @var{filename} Amends the image format specific @var{options} for the image file @var{filename}. Not all file formats support this operation. +--force allows some unsafe operations. Currently for -f luks, +it allows to erase last encryption key, and to overwrite an active +encryption key. + @item bench [-c @var{count}] [-d @var{depth}] [-f @var{fmt}] [--flush-interval=@var{flush_interval}] [-n] [--no-drain] [-o @var{offset}] [--pattern=@var{pattern}] [-q] [-s @var{buffer_size}] [-S @var{step_size}] [-t @var{cache}] [-w] [-U] @var{filename} Run a simple sequential I/O benchmark on the specified image. If @code{-w} is From patchwork Tue Jan 14 19:33:41 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223018 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=SDYi8Wst; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y10S3Lqvz9s29 for ; Wed, 15 Jan 2020 06:36:08 +1100 (AEDT) Received: from localhost ([::1]:45092 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyw-000361-1j for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:36:06 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38329) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyC-000310-RW for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:23 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRy8-0007Qv-TJ for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:20 -0500 Received: from us-smtp-delivery-1.mimecast.com ([207.211.31.120]:58269 helo=us-smtp-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRy8-0007Qf-Ot for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:16 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030516; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=MoWVc8DlugTtXixgQ2kjzrfKLFMy+6oY2g4iOVFqwxw=; b=SDYi8Wst/hngiWqapT/a+NSOAqGkIr6kb/XP8lg69enBb6NskpOBJVt6XuGm5Kr99uX6qF 9iCmixHaKn9Zmb5pWZCbhnlRmx4L6gcg2rQXuiWvwr+lstnYS5qAC64pA0IxzVsqdWrbQt ZXnb702EIUMmT9K0ofZ8D1ZhW/WLMzc= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-174-TlBw0-Q-McK3w1_Sx7V-pA-1; Tue, 14 Jan 2020 14:34:09 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 4D6671005502; Tue, 14 Jan 2020 19:34:08 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 108937C83A; Tue, 14 Jan 2020 19:34:05 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 04/13] block: amend: separate amend and create options for qemu-img Date: Tue, 14 Jan 2020 21:33:41 +0200 Message-Id: <20200114193350.10830-5-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: TlBw0-Q-McK3w1_Sx7V-pA-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.120 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" Some options are only useful for creation (or hard to be amended, like cluster size for qcow2), while some other options are only useful for amend, like upcoming keyslot management options for luks Since currently only qcow2 supports amend, move all its options to a common macro and then include it in each action option list. In future it might be useful to remove some options which are not supported anyway from amend list, which currently cause an error message if amended. Signed-off-by: Maxim Levitsky --- block/qcow2.c | 160 +++++++++++++++++++++----------------- include/block/block_int.h | 4 + qemu-img.c | 18 ++--- 3 files changed, 100 insertions(+), 82 deletions(-) diff --git a/block/qcow2.c b/block/qcow2.c index 6bcf4a5fc4..c6c2deee75 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -5445,83 +5445,96 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset, s->signaled_corruption = true; } +#define QCOW_COMMON_OPTIONS \ + { \ + .name = BLOCK_OPT_SIZE, \ + .type = QEMU_OPT_SIZE, \ + .help = "Virtual disk size" \ + }, \ + { \ + .name = BLOCK_OPT_COMPAT_LEVEL, \ + .type = QEMU_OPT_STRING, \ + .help = "Compatibility level (v2 [0.10] or v3 [1.1])" \ + }, \ + { \ + .name = BLOCK_OPT_BACKING_FILE, \ + .type = QEMU_OPT_STRING, \ + .help = "File name of a base image" \ + }, \ + { \ + .name = BLOCK_OPT_BACKING_FMT, \ + .type = QEMU_OPT_STRING, \ + .help = "Image format of the base image" \ + }, \ + { \ + .name = BLOCK_OPT_DATA_FILE, \ + .type = QEMU_OPT_STRING, \ + .help = "File name of an external data file" \ + }, \ + { \ + .name = BLOCK_OPT_DATA_FILE_RAW, \ + .type = QEMU_OPT_BOOL, \ + .help = "The external data file must stay valid " \ + "as a raw image" \ + }, \ + { \ + .name = BLOCK_OPT_ENCRYPT, \ + .type = QEMU_OPT_BOOL, \ + .help = "Encrypt the image with format 'aes'. (Deprecated " \ + "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)", \ + }, \ + { \ + .name = BLOCK_OPT_ENCRYPT_FORMAT, \ + .type = QEMU_OPT_STRING, \ + .help = "Encrypt the image, format choices: 'aes', 'luks'", \ + }, \ + BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.", \ + "ID of secret providing qcow AES key or LUKS passphrase"), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."), \ + { \ + .name = BLOCK_OPT_CLUSTER_SIZE, \ + .type = QEMU_OPT_SIZE, \ + .help = "qcow2 cluster size", \ + .def_value_str = stringify(DEFAULT_CLUSTER_SIZE) \ + }, \ + { \ + .name = BLOCK_OPT_PREALLOC, \ + .type = QEMU_OPT_STRING, \ + .help = "Preallocation mode (allowed values: off, " \ + "metadata, falloc, full)" \ + }, \ + { \ + .name = BLOCK_OPT_LAZY_REFCOUNTS, \ + .type = QEMU_OPT_BOOL, \ + .help = "Postpone refcount updates", \ + .def_value_str = "off" \ + }, \ + { \ + .name = BLOCK_OPT_REFCOUNT_BITS, \ + .type = QEMU_OPT_NUMBER, \ + .help = "Width of a reference count entry in bits", \ + .def_value_str = "16" \ + } \ + static QemuOptsList qcow2_create_opts = { .name = "qcow2-create-opts", .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head), .desc = { - { - .name = BLOCK_OPT_SIZE, - .type = QEMU_OPT_SIZE, - .help = "Virtual disk size" - }, - { - .name = BLOCK_OPT_COMPAT_LEVEL, - .type = QEMU_OPT_STRING, - .help = "Compatibility level (v2 [0.10] or v3 [1.1])" - }, - { - .name = BLOCK_OPT_BACKING_FILE, - .type = QEMU_OPT_STRING, - .help = "File name of a base image" - }, - { - .name = BLOCK_OPT_BACKING_FMT, - .type = QEMU_OPT_STRING, - .help = "Image format of the base image" - }, - { - .name = BLOCK_OPT_DATA_FILE, - .type = QEMU_OPT_STRING, - .help = "File name of an external data file" - }, - { - .name = BLOCK_OPT_DATA_FILE_RAW, - .type = QEMU_OPT_BOOL, - .help = "The external data file must stay valid as a raw image" - }, - { - .name = BLOCK_OPT_ENCRYPT, - .type = QEMU_OPT_BOOL, - .help = "Encrypt the image with format 'aes'. (Deprecated " - "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)", - }, - { - .name = BLOCK_OPT_ENCRYPT_FORMAT, - .type = QEMU_OPT_STRING, - .help = "Encrypt the image, format choices: 'aes', 'luks'", - }, - BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.", - "ID of secret providing qcow AES key or LUKS passphrase"), - BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."), - BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."), - BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."), - BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."), - BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."), - BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."), - { - .name = BLOCK_OPT_CLUSTER_SIZE, - .type = QEMU_OPT_SIZE, - .help = "qcow2 cluster size", - .def_value_str = stringify(DEFAULT_CLUSTER_SIZE) - }, - { - .name = BLOCK_OPT_PREALLOC, - .type = QEMU_OPT_STRING, - .help = "Preallocation mode (allowed values: off, metadata, " - "falloc, full)" - }, - { - .name = BLOCK_OPT_LAZY_REFCOUNTS, - .type = QEMU_OPT_BOOL, - .help = "Postpone refcount updates", - .def_value_str = "off" - }, - { - .name = BLOCK_OPT_REFCOUNT_BITS, - .type = QEMU_OPT_NUMBER, - .help = "Width of a reference count entry in bits", - .def_value_str = "16" - }, + QCOW_COMMON_OPTIONS, + { /* end of list */ } + } +}; + +static QemuOptsList qcow2_amend_opts = { + .name = "qcow2-amend-opts", + .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head), + .desc = { + QCOW_COMMON_OPTIONS, { /* end of list */ } } }; @@ -5581,6 +5594,7 @@ BlockDriver bdrv_qcow2 = { .bdrv_inactivate = qcow2_inactivate, .create_opts = &qcow2_create_opts, + .amend_opts = &qcow2_amend_opts, .strong_runtime_opts = qcow2_strong_runtime_opts, .mutable_opts = mutable_opts, .bdrv_co_check = qcow2_co_check, diff --git a/include/block/block_int.h b/include/block/block_int.h index 810a9ecb86..6f0abf8544 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -407,6 +407,10 @@ struct BlockDriver { /* List of options for creating images, terminated by name == NULL */ QemuOptsList *create_opts; + + /* List of options for image amend*/ + QemuOptsList *amend_opts; + /* * If this driver supports reopening images this contains a * NULL-terminated list of the runtime options that can be diff --git a/qemu-img.c b/qemu-img.c index a79f3904db..befd53943b 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -3878,11 +3878,11 @@ static int print_amend_option_help(const char *format) return 1; } - /* Every driver supporting amendment must have create_opts */ - assert(drv->create_opts); + /* Every driver supporting amendment must have amend_opts */ + assert(drv->amend_opts); printf("Creation options for '%s':\n", format); - qemu_opts_print_help(drv->create_opts, false); + qemu_opts_print_help(drv->amend_opts, false); printf("\nNote that not all of these options may be amendable.\n"); return 0; } @@ -3892,7 +3892,7 @@ static int img_amend(int argc, char **argv) Error *err = NULL; int c, ret = 0; char *options = NULL; - QemuOptsList *create_opts = NULL; + QemuOptsList *amend_opts = NULL; QemuOpts *opts = NULL; const char *fmt = NULL, *filename, *cache; int flags; @@ -4031,11 +4031,11 @@ static int img_amend(int argc, char **argv) goto out; } - /* Every driver supporting amendment must have create_opts */ - assert(bs->drv->create_opts); + /* Every driver supporting amendment must have amend_opts */ + assert(bs->drv->amend_opts); - create_opts = qemu_opts_append(create_opts, bs->drv->create_opts); - opts = qemu_opts_create(create_opts, NULL, 0, &error_abort); + amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts); + opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort); qemu_opts_do_parse(opts, options, NULL, &err); if (err) { error_report_err(err); @@ -4058,7 +4058,7 @@ out: out_no_progress: blk_unref(blk); qemu_opts_del(opts); - qemu_opts_free(create_opts); + qemu_opts_free(amend_opts); g_free(options); if (ret) { From patchwork Tue Jan 14 19:33:42 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223022 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=TJuym3L1; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y13d2KsVz9s29 for ; Wed, 15 Jan 2020 06:38:53 +1100 (AEDT) Received: from localhost ([::1]:45166 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS1a-000711-E2 for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:38:50 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38371) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyG-00034m-1m for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:26 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRyC-0007U2-5g for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:23 -0500 Received: from us-smtp-delivery-1.mimecast.com ([205.139.110.120]:32601 helo=us-smtp-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRyC-0007TO-23 for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:20 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030519; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=21zXTpw9IH7Q2VKoQfjeXxCFhgCVnjN8u61s41vMk9s=; b=TJuym3L15Us5WVg2C1RhRb5u9Bj4QRTBQ+c3kt+eZs4+8F5jzMgl5ozxPG0gMQJI+MmV8p 6RLj4bunHCBQrqA67v9ULf4fXsULYQex/0Nm+jGuuVi7wD8ufs0BNlpa8rBoIMk1sImrHL tq9T2WVK1cLUqBN2kYu5AvFAyEWl5lQ= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-27-c0kG0vC0MPOkzUUe0TQrNg-1; Tue, 14 Jan 2020 14:34:12 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id E608E800D48; Tue, 14 Jan 2020 19:34:10 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id A94E087EFE; Tue, 14 Jan 2020 19:34:08 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 05/13] block/crypto: rename two functions Date: Tue, 14 Jan 2020 21:33:42 +0200 Message-Id: <20200114193350.10830-6-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: c0kG0vC0MPOkzUUe0TQrNg-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 205.139.110.120 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" rename the write_func to create_write_func, and init_func to create_init_func. This is preparation for other write_func that will be used to update the encryption keys. No functional changes Signed-off-by: Maxim Levitsky Reviewed-by: Daniel P. Berrangé --- block/crypto.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index ecf96a7a9b..0b37dae564 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -78,12 +78,12 @@ struct BlockCryptoCreateData { }; -static ssize_t block_crypto_write_func(QCryptoBlock *block, - size_t offset, - const uint8_t *buf, - size_t buflen, - void *opaque, - Error **errp) +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; @@ -96,11 +96,10 @@ static ssize_t block_crypto_write_func(QCryptoBlock *block, return ret; } - -static ssize_t block_crypto_init_func(QCryptoBlock *block, - size_t headerlen, - void *opaque, - Error **errp) +static ssize_t block_crypto_create_init_func(QCryptoBlock *block, + size_t headerlen, + void *opaque, + Error **errp) { struct BlockCryptoCreateData *data = opaque; @@ -296,8 +295,8 @@ static int block_crypto_co_create_generic(BlockDriverState *bs, }; crypto = qcrypto_block_create(opts, NULL, - block_crypto_init_func, - block_crypto_write_func, + block_crypto_create_init_func, + block_crypto_create_write_func, &data, errp); From patchwork Tue Jan 14 19:33:43 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223026 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=d/ggpiPy; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y16r5Ymgz9s29 for ; Wed, 15 Jan 2020 06:41:40 +1100 (AEDT) Received: from localhost ([::1]:45212 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS4I-0002Js-72 for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:41:38 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38414) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyK-0003Bq-Pn for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:31 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRyH-0007eg-OD for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:28 -0500 Received: from us-smtp-2.mimecast.com ([207.211.31.81]:58941 helo=us-smtp-delivery-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRyH-0007e6-K7 for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:25 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030525; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=PgymnAKwC+dv1xKhL6ejAwKbGcncml6cSn5RxHWgk0o=; b=d/ggpiPyXoEDqtMbjxj0kwZqy5XXvNa5aFpsnQdyrKWC2NwWhZ+iONjJz+bH3FOpElzmNk zdONnZ1h7LFF3YqjNKDwwVBetaIqsEGUa7wRCWZIrj43geUKhFYGsdGdqlPcLc0A8ke7pl M3NVVUOA9fuxd1Vqq0wdw9Jp0ndA0vs= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-422-h1txvLmxP6u1oaq8MLhjmQ-1; Tue, 14 Jan 2020 14:34:17 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 0496E801E72; Tue, 14 Jan 2020 19:34:16 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 4E8DA87EFE; Tue, 14 Jan 2020 19:34:11 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 06/13] block/crypto: implement the encryption key management Date: Tue, 14 Jan 2020 21:33:43 +0200 Message-Id: <20200114193350.10830-7-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: h1txvLmxP6u1oaq8MLhjmQ-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.81 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" 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 Reviewed-by: Daniel P. Berrangé --- block/crypto.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++-- block/crypto.h | 31 ++++++++++++ 2 files changed, 158 insertions(+), 3 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index 0b37dae564..081880bced 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -36,6 +36,7 @@ typedef struct BlockCrypto BlockCrypto; struct BlockCrypto { QCryptoBlock *block; + bool updating_keys; }; @@ -70,6 +71,24 @@ static ssize_t block_crypto_read_func(QCryptoBlock *block, 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; @@ -148,6 +167,22 @@ static QemuOptsList block_crypto_create_opts_luks = { }; +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_KEYSLOT_UPDATE("keys.0."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.1."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.2."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.3."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.4."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.5."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.6."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("keys.7."), + { /* end of list */ } + }, +}; + QCryptoBlockOpenOptions * block_crypto_open_opts_init(QDict *opts, Error **errp) { @@ -661,6 +696,95 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp) return spec_info; } +static int +block_crypto_amend_options(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_filter_default_perms(bs, c, role, reopen_queue, + perm, shared, nperm, nshared); + /* + * Ask for consistent read permission so that if + * someone else tries to open this image with this permission + * neither will be able to edit encryption keys, since + * we will unshare that permission while trying to + * update the encryption keys + */ + if (!(bs->open_flags & BDRV_O_NO_IO)) { + *nperm |= BLK_PERM_CONSISTENT_READ; + } + /* + * 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, @@ -673,13 +797,12 @@ static BlockDriver bdrv_crypto_luks = { .bdrv_probe = block_crypto_probe_luks, .bdrv_open = block_crypto_open_luks, .bdrv_close = block_crypto_close, - /* This driver doesn't modify LUKS metadata except when creating image. - * Allow share-rw=on as a special case. */ - .bdrv_child_perm = bdrv_filter_default_perms, + .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, @@ -688,6 +811,7 @@ static BlockDriver bdrv_crypto_luks = { .bdrv_getlength = block_crypto_getlength, .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, .strong_runtime_opts = block_crypto_strong_runtime_opts, }; diff --git a/block/crypto.h b/block/crypto.h index 06e044c9be..4af5ab4c94 100644 --- a/block/crypto.h +++ b/block/crypto.h @@ -41,6 +41,10 @@ #define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg" #define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg" #define BLOCK_CRYPTO_OPT_LUKS_ITER_TIME "iter-time" +#define BLOCK_CRYPTO_OPT_LUKS_KEYSLOT "keyslot" +#define BLOCK_CRYPTO_OPT_LUKS_OLD_SECRET "old-secret" +#define BLOCK_CRYPTO_OPT_LUKS_NEW_SECRET "new-secret" + #define BLOCK_CRYPTO_OPT_DEF_LUKS_KEY_SECRET(prefix) \ BLOCK_CRYPTO_OPT_DEF_KEY_SECRET(prefix, \ @@ -88,6 +92,33 @@ .help = "Time to spend in PBKDF in milliseconds", \ } +#define BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(prefix) \ + { \ + .name = prefix BLOCK_CRYPTO_OPT_LUKS_KEYSLOT, \ + .type = QEMU_OPT_NUMBER, \ + .help = "Select keyslot to modify explicitly", \ + } + +#define BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(prefix) \ + { \ + .name = prefix BLOCK_CRYPTO_OPT_LUKS_OLD_SECRET, \ + .type = QEMU_OPT_STRING, \ + .help = "Modify all keyslots that match this password", \ + } + +#define BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(prefix) \ + { \ + .name = prefix BLOCK_CRYPTO_OPT_LUKS_NEW_SECRET, \ + .type = QEMU_OPT_STRING, \ + .help = "New secret to set in the matching keyslots", \ + } + +#define BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE(prefix) \ + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(prefix), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(prefix), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(prefix), \ + BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix) \ + QCryptoBlockCreateOptions * block_crypto_create_opts_init(QDict *opts, Error **errp); From patchwork Tue Jan 14 19:33:44 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223020 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=PAYX0L6G; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y10l6WwSz9s29 for ; Wed, 15 Jan 2020 06:36:23 +1100 (AEDT) Received: from localhost ([::1]:45122 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRzB-0003Vm-DE for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:36:21 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38441) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyN-0003GD-Dr for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:34 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRyK-0007jh-OG for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:31 -0500 Received: from us-smtp-delivery-1.mimecast.com ([207.211.31.120]:21380 helo=us-smtp-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRyK-0007j6-K8 for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:28 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030528; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=1Yy7MXuoHyFbyV2yl4FMvL6qH7mKHQC/6W4zJtb6D0E=; b=PAYX0L6GUy4Vzz1aJLjpcqGzB6mgbh+n6NivI7FMTy7bwyc3Nt874Q1zr4BU9AyU/6ikO2 HoPZs17hWJrSapbr9jl8UyFYVz1pxdIVdbJ55OhhskUcTG5gFVqLByM+UPzz3vKBvrjCdb THPoRXzmBH0T7VeBSNeNQQD0x8JtFnw= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-170-2rGv6P3ROyCJXtQW96Hptg-1; Tue, 14 Jan 2020 14:34:19 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 9C969DC24; Tue, 14 Jan 2020 19:34:18 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 5F4C687EFE; Tue, 14 Jan 2020 19:34:16 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 07/13] qcow2: extend qemu-img amend interface with crypto options Date: Tue, 14 Jan 2020 21:33:44 +0200 Message-Id: <20200114193350.10830-8-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: 2rGv6P3ROyCJXtQW96Hptg-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.120 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" Now that we have all the infrastructure in place, wire it in the qcow2 driver and expose this to the user. Signed-off-by: Maxim Levitsky --- block/qcow2.c | 101 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 22 deletions(-) diff --git a/block/qcow2.c b/block/qcow2.c index c6c2deee75..1b01174aed 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -173,6 +173,19 @@ static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset, return ret; } +static QDict* +qcow2_extract_crypto_opts(QemuOpts *opts, const char *fmt, Error **errp) +{ + QDict *cryptoopts_qdict; + QDict *opts_qdict; + + /* Extract "encrypt." options into a qdict */ + opts_qdict = qemu_opts_to_qdict(opts, NULL); + qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt."); + qobject_unref(opts_qdict); + qdict_put_str(cryptoopts_qdict, "format", "luks"); + return cryptoopts_qdict; +} /* * read qcow2 extension and fill bs @@ -4631,20 +4644,18 @@ static ssize_t qcow2_measure_crypto_hdr_write_func(QCryptoBlock *block, static bool qcow2_measure_luks_headerlen(QemuOpts *opts, size_t *len, Error **errp) { - QDict *opts_qdict; - QDict *cryptoopts_qdict; QCryptoBlockCreateOptions *cryptoopts; + QDict* crypto_opts_dict; QCryptoBlock *crypto; - /* Extract "encrypt." options into a qdict */ - opts_qdict = qemu_opts_to_qdict(opts, NULL); - qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt."); - qobject_unref(opts_qdict); + crypto_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp); + if (!crypto_opts_dict) { + return false; + } + + cryptoopts = block_crypto_create_opts_init(crypto_opts_dict, errp); + qobject_unref(crypto_opts_dict); - /* Build QCryptoBlockCreateOptions object from qdict */ - qdict_put_str(cryptoopts_qdict, "format", "luks"); - cryptoopts = block_crypto_create_opts_init(cryptoopts_qdict, errp); - qobject_unref(cryptoopts_qdict); if (!cryptoopts) { return false; } @@ -5083,6 +5094,7 @@ typedef enum Qcow2AmendOperation { QCOW2_NO_OPERATION = 0, QCOW2_UPGRADING, + QCOW2_UPDATING_ENCRYPTION, QCOW2_CHANGING_REFCOUNT_ORDER, QCOW2_DOWNGRADING, } Qcow2AmendOperation; @@ -5167,6 +5179,7 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts, int ret; QemuOptDesc *desc = opts->list->desc; Qcow2AmendHelperCBInfo helper_cb_info; + bool encryption_update = false; while (desc && desc->name) { if (!qemu_opt_find(opts, desc->name)) { @@ -5215,9 +5228,17 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts, return -ENOTSUP; } } else if (g_str_has_prefix(desc->name, "encrypt.")) { - error_setg(errp, - "Changing the encryption parameters is not supported"); - return -ENOTSUP; + if (!s->crypto) { + error_setg(errp, + "Can't amend encryption options - encryption not present"); + return -EINVAL; + } + if (s->crypt_method_header != QCOW_CRYPT_LUKS) { + error_setg(errp, + "Only LUKS encryption options can be amended"); + return -ENOTSUP; + } + encryption_update = true; } else if (!strcmp(desc->name, BLOCK_OPT_CLUSTER_SIZE)) { cluster_size = qemu_opt_get_size(opts, BLOCK_OPT_CLUSTER_SIZE, cluster_size); @@ -5267,7 +5288,8 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts, .original_status_cb = status_cb, .original_cb_opaque = cb_opaque, .total_operations = (new_version != old_version) - + (s->refcount_bits != refcount_bits) + + (s->refcount_bits != refcount_bits) + + (encryption_update == true) }; /* Upgrade first (some features may require compat=1.1) */ @@ -5280,6 +5302,33 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts, } } + if (encryption_update) { + QDict *amend_opts_dict; + QCryptoBlockAmendOptions *amend_opts; + + helper_cb_info.current_operation = QCOW2_UPDATING_ENCRYPTION; + amend_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp); + if (!amend_opts_dict) { + return -EINVAL; + } + amend_opts = block_crypto_amend_opts_init(amend_opts_dict, errp); + qobject_unref(amend_opts_dict); + if (!amend_opts) { + return -EINVAL; + } + ret = qcrypto_block_amend_options(s->crypto, + qcow2_crypto_hdr_read_func, + qcow2_crypto_hdr_write_func, + bs, + amend_opts, + force, + errp); + qapi_free_QCryptoBlockAmendOptions(amend_opts); + if (ret < 0) { + return ret; + } + } + if (s->refcount_bits != refcount_bits) { int refcount_order = ctz32(refcount_bits); @@ -5488,14 +5537,6 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset, .type = QEMU_OPT_STRING, \ .help = "Encrypt the image, format choices: 'aes', 'luks'", \ }, \ - BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.", \ - "ID of secret providing qcow AES key or LUKS passphrase"), \ - BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."), \ - BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."), \ - BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."), \ - BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."), \ - BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."), \ - BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."), \ { \ .name = BLOCK_OPT_CLUSTER_SIZE, \ .type = QEMU_OPT_SIZE, \ @@ -5526,6 +5567,14 @@ static QemuOptsList qcow2_create_opts = { .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head), .desc = { QCOW_COMMON_OPTIONS, + BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.", + "ID of secret providing qcow AES key or LUKS passphrase"), + BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."), + BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."), + BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."), + BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."), + BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."), + BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."), { /* end of list */ } } }; @@ -5535,6 +5584,14 @@ static QemuOptsList qcow2_amend_opts = { .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head), .desc = { QCOW_COMMON_OPTIONS, + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.0."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.1."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.2."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.3."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.4."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.5."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.6."), + BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT_UPDATE("encrypt.keys.7."), { /* end of list */ } } }; From patchwork Tue Jan 14 19:33:45 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223030 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=ZQh/rrvL; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y1DW23HYz9s29 for ; Wed, 15 Jan 2020 06:46:33 +1100 (AEDT) Received: from localhost ([::1]:45300 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS90-0000s4-Bx for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:46:30 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38480) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyQ-0003LW-RV for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:37 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRyN-0007oq-KF for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:34 -0500 Received: from us-smtp-2.mimecast.com ([207.211.31.81]:53699 helo=us-smtp-delivery-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRyN-0007nE-Gq for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:31 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030531; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=V/E8chs2P88S+DZ+voRqJwD6HlRvP4yhEAQqXQdSGnM=; b=ZQh/rrvLBZggutO/pBAPkn/fGXZYhkrRRUpFwkXyuiC4mcAdIJd5+juqWM/9ZFy+Xhq4xr CRcdmJjTAMDatqEjtj1FQfo1GE15dmfyJQqQsG0lWaNq6EGHO9UxvVqYEk271BwLNzleQg Jb+xNIjJ4XohnheNwpFPtqKfJHir3QI= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-243-_ph_fYAoNJSgBgMrHdmDDg-1; Tue, 14 Jan 2020 14:34:22 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 416A4100551C; Tue, 14 Jan 2020 19:34:21 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 052C37C83A; Tue, 14 Jan 2020 19:34:18 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 08/13] iotests: filter few more luks specific create options Date: Tue, 14 Jan 2020 21:33:45 +0200 Message-Id: <20200114193350.10830-9-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: _ph_fYAoNJSgBgMrHdmDDg-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.81 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" This allows more tests to be able to have same output on both qcow2 luks encrypted images and raw luks images Signed-off-by: Maxim Levitsky --- tests/qemu-iotests/087.out | 6 +++--- tests/qemu-iotests/134.out | 2 +- tests/qemu-iotests/158.out | 4 ++-- tests/qemu-iotests/188.out | 2 +- tests/qemu-iotests/189.out | 4 ++-- tests/qemu-iotests/198.out | 4 ++-- tests/qemu-iotests/common.filter | 6 ++++-- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out index 2d92ea847b..b61ba638af 100644 --- a/tests/qemu-iotests/087.out +++ b/tests/qemu-iotests/087.out @@ -34,7 +34,7 @@ QMP_VERSION === Encrypted image QCow === -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on Testing: QMP_VERSION {"return": {}} @@ -46,7 +46,7 @@ QMP_VERSION === Encrypted image LUKS === -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encrypt.format=luks encrypt.key-secret=sec0 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 Testing: QMP_VERSION {"return": {}} @@ -58,7 +58,7 @@ QMP_VERSION === Missing driver === -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on Testing: -S QMP_VERSION {"return": {}} diff --git a/tests/qemu-iotests/134.out b/tests/qemu-iotests/134.out index 09d46f6b17..4abc5b5f7d 100644 --- a/tests/qemu-iotests/134.out +++ b/tests/qemu-iotests/134.out @@ -1,5 +1,5 @@ QA output created by 134 -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on == reading whole image == read 134217728/134217728 bytes at offset 0 diff --git a/tests/qemu-iotests/158.out b/tests/qemu-iotests/158.out index 6def216e55..f28a17626b 100644 --- a/tests/qemu-iotests/158.out +++ b/tests/qemu-iotests/158.out @@ -1,6 +1,6 @@ QA output created by 158 == create base == -Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0 +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=134217728 encryption=on == writing whole image == wrote 134217728/134217728 bytes at offset 0 @@ -10,7 +10,7 @@ wrote 134217728/134217728 bytes at offset 0 read 134217728/134217728 bytes at offset 0 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) == create overlay == -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base encryption=on encrypt.key-secret=sec0 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base encryption=on == writing part of a cluster == wrote 1024/1024 bytes at offset 0 diff --git a/tests/qemu-iotests/188.out b/tests/qemu-iotests/188.out index c568ef3701..5426861b18 100644 --- a/tests/qemu-iotests/188.out +++ b/tests/qemu-iotests/188.out @@ -1,5 +1,5 @@ QA output created by 188 -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 == reading whole image == read 16777216/16777216 bytes at offset 0 diff --git a/tests/qemu-iotests/189.out b/tests/qemu-iotests/189.out index a0b7c9c24c..bc213cbe14 100644 --- a/tests/qemu-iotests/189.out +++ b/tests/qemu-iotests/189.out @@ -1,6 +1,6 @@ QA output created by 189 == create base == -Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216 == writing whole image == wrote 16777216/16777216 bytes at offset 0 @@ -10,7 +10,7 @@ wrote 16777216/16777216 bytes at offset 0 read 16777216/16777216 bytes at offset 0 16 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) == create overlay == -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base == writing part of a cluster == wrote 1024/1024 bytes at offset 0 diff --git a/tests/qemu-iotests/198.out b/tests/qemu-iotests/198.out index 831ce3a289..acfdf96b0c 100644 --- a/tests/qemu-iotests/198.out +++ b/tests/qemu-iotests/198.out @@ -1,12 +1,12 @@ QA output created by 198 == create base == -Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216 == writing whole image base == wrote 16777216/16777216 bytes at offset 0 16 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) == create overlay == -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base == writing whole image layer == wrote 16777216/16777216 bytes at offset 0 diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter index 3f8ee3e5f7..bcc4495d52 100644 --- a/tests/qemu-iotests/common.filter +++ b/tests/qemu-iotests/common.filter @@ -150,8 +150,10 @@ _filter_img_create() -e "s# block_state_zero=\\(on\\|off\\)##g" \ -e "s# log_size=[0-9]\\+##g" \ -e "s# refcount_bits=[0-9]\\+##g" \ - -e "s# key-secret=[a-zA-Z0-9]\\+##g" \ - -e "s# iter-time=[0-9]\\+##g" \ + -e "s# \\(encrypt\\.\\)\\?key-secret=[a-zA-Z0-9]\\+##g" \ + -e "s# \\(encrypt\\.\\)\\?slot=[0-9]\\+##g" \ + -e "s# \\(encrypt\\.\\)\\?iter-time=[0-9]\\+##g" \ + -e "s# encrypt\\.format=[a-zA-Z0-9]\\+##g" \ -e "s# force_size=\\(on\\|off\\)##g" } From patchwork Tue Jan 14 19:33:46 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223029 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=CXvQWM/s; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y19z2xCZz9sP3 for ; Wed, 15 Jan 2020 06:44:23 +1100 (AEDT) Received: from localhost ([::1]:45250 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS6u-0006a8-Un for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:44:20 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38475) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyQ-0003Kr-EO for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:37 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRyN-0007oj-Ja for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:34 -0500 Received: from us-smtp-2.mimecast.com ([207.211.31.81]:54627 helo=us-smtp-delivery-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRyN-0007mw-EI for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:31 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030531; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=gch5+sc9eY6JZ+iJ7mxHhvEpAtv4qlkZ2oT+zxEorkc=; b=CXvQWM/sZJL6AEecKqOXplj6u24Gdyr0SA95cRoOJZZvJWHmjp8Vj5j0cZl0qSnlr1kyGg F9LzZnJSSGV4SwtwSoQapj1wPUNAAJqb4LI01e/88PNVpEz6nxbcAfgV1bRmXHFaaSNPik BCI2fd4meFCzymg3YUw/6YSzHfR5EQQ= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-304-f4TjkFC4NJq_eVECZ3-VcQ-1; Tue, 14 Jan 2020 14:34:24 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id DA152DC22; Tue, 14 Jan 2020 19:34:23 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 9E02E87EFE; Tue, 14 Jan 2020 19:34:21 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 09/13] qemu-iotests: qemu-img tests for luks key management Date: Tue, 14 Jan 2020 21:33:46 +0200 Message-Id: <20200114193350.10830-10-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: f4TjkFC4NJq_eVECZ3-VcQ-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.81 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" This commit adds two tests, which test the new amend interface of both luks raw images and qcow2 luks encrypted images. Signed-off-by: Maxim Levitsky --- tests/qemu-iotests/300 | 207 +++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/300.out | 99 ++++++++++++++++++ tests/qemu-iotests/301 | 90 ++++++++++++++++ tests/qemu-iotests/301.out | 30 ++++++ tests/qemu-iotests/group | 3 + 5 files changed, 429 insertions(+) create mode 100755 tests/qemu-iotests/300 create mode 100644 tests/qemu-iotests/300.out create mode 100755 tests/qemu-iotests/301 create mode 100644 tests/qemu-iotests/301.out diff --git a/tests/qemu-iotests/300 b/tests/qemu-iotests/300 new file mode 100755 index 0000000000..b1a6b37772 --- /dev/null +++ b/tests/qemu-iotests/300 @@ -0,0 +1,207 @@ +#!/usr/bin/env bash +# +# Test encryption key management with luks +# Based on 134 +# +# Copyright (C) 2019 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# creator +owner=mlevitsk@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qcow2 luks +_supported_proto file #TODO + +QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT + +if [ "$IMGFMT" = "qcow2" ] ; then + PR="encrypt." + EXTRA_IMG_ARGS="-o encrypt.format=luks" +fi + + +# secrets: you are supposed to see the password as *******, see :-) +S0="--object secret,id=sec0,data=hunter0" +S1="--object secret,id=sec1,data=hunter1" +S2="--object secret,id=sec2,data=hunter2" +S3="--object secret,id=sec3,data=hunter3" +S4="--object secret,id=sec4,data=hunter4" +SECRETS="$S0 $S1 $S2 $S3 $S4" + +# image with given secret +IMGS0="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec0" +IMGS1="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec1" +IMGS2="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec2" +IMGS3="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec3" +IMGS4="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec4" + + +echo "== creating a test image ==" +_make_test_img $S0 $EXTRA_IMG_ARGS -o ${PR}key-secret=sec0,${PR}iter-time=10 32M + +echo +echo "== test that key 0 opens the image ==" +$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir + +echo +echo "== adding a password to slot 4 ==" +$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret=sec4,${PR}keys.0.iter-time=10,${PR}keys.0.keyslot=4 +echo "== adding a password to slot 1 ==" +$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret=sec1,${PR}keys.0.iter-time=10 +echo "== adding a password to slot 3 ==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret=sec3,${PR}keys.0.iter-time=10,${PR}keys.0.keyslot=3 + +echo "== adding a password to slot 2 ==" +$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec2,${PR}keys.0.iter-time=10 + + +echo "== erase slot 4 ==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=4 | _filter_img_create + + +echo +echo "== all secrets should work ==" +for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do + $QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir +done + +echo +echo "== erase slot 0 and try it ==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec0 | _filter_img_create +$QEMU_IO $SECRETS -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir + +echo +echo "== erase slot 2 and try it ==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=2 | _filter_img_create +$QEMU_IO $SECRETS -c "read 0 4096" $IMGS2 | _filter_qemu_io | _filter_testdir + + +# at this point slots 1 and 3 should be active + +echo +echo "== filling 4 slots with secret 2 ==" +for i in $(seq 0 3) ; do + $QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec2,${PR}keys.0.iter-time=10 +done + +echo +echo "== adding secret 0 ==" + $QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec0,${PR}keys.0.iter-time=10 + +echo +echo "== adding secret 3 (last slot) ==" + $QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec3,${PR}keys.0.iter-time=10 + +echo +echo "== trying to add another slot (should fail) ==" +$QEMU_IMG amend $SECRETS $IMGS2 -o ${PR}keys.0.new-secret=sec3,${PR}keys.0.iter-time=10 + +echo +echo "== all secrets should work again ==" +for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do + $QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir +done + + +echo + +echo "== erase all keys of secret 2==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec2 + +echo "== erase all keys of secret 1==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec1 + +echo "== erase all keys of secret 0==" +$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec0 + +echo "== erasing secret3 will fail now since it is the only secret (in 3 slots) ==" +$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec3 + +echo +echo "== only secret3 should work now ==" +for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do + $QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir +done + +echo +echo "== add secret0 ==" +$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}keys.0.new-secret=sec0,${PR}keys.0.iter-time=10 + +echo "== erase secret3 ==" +$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec3 + +echo +echo "== only secret0 should work now ==" +for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do + $QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir +done + +echo +echo "== replace secret0 with secret1 (should fail) ==" +$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret=sec1,${PR}keys.0.keyslot=0 + +echo +echo "== replace secret0 with secret1 with force (should work) ==" +$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}keys.0.new-secret=sec1,${PR}keys.0.iter-time=10,${PR}keys.0.keyslot=0 --force + +echo +echo "== only secret1 should work now ==" +for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do + $QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir +done + + +echo +echo "== erase last secret (should fail) ==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=0 +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec1 + + +echo "== erase non existing secrets (should fail) ==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec5 --force +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.old-secret=sec0 --force +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=1 --force + +echo +echo "== erase last secret with force by slot (should work) ==" +$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}keys.0.new-secret="",${PR}keys.0.keyslot=0 --force + +echo +echo "== we have no secrets now, data is lost forever ==" +for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do + $QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir +done + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 + diff --git a/tests/qemu-iotests/300.out b/tests/qemu-iotests/300.out new file mode 100644 index 0000000000..7816c36044 --- /dev/null +++ b/tests/qemu-iotests/300.out @@ -0,0 +1,99 @@ +QA output created by 300 +== creating a test image == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 + +== test that key 0 opens the image == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== adding a password to slot 4 == +== adding a password to slot 1 == +== adding a password to slot 3 == +== adding a password to slot 2 == +== erase slot 4 == + +== all secrets should work == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== erase slot 0 and try it == +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== erase slot 2 and try it == +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== filling 4 slots with secret 2 == + +== adding secret 0 == + +== adding secret 3 (last slot) == + +== trying to add another slot (should fail) == +qemu-img: Can't add a keyslot - all key slots are in use + +== all secrets should work again == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== erase all keys of secret 2== +== erase all keys of secret 1== +== erase all keys of secret 0== +== erasing secret3 will fail now since it is the only secret (in 3 slots) == +qemu-img: Requested operation would erase all active keyslots which will erase all the data in the image irreversibly - refusing operation + +== only secret3 should work now == +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== add secret0 == +== erase secret3 == + +== only secret0 should work now == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== replace secret0 with secret1 (should fail) == +qemu-img: Refusing to overwrite active slot 0 - please erase it first + +== replace secret0 with secret1 with force (should work) == + +== only secret1 should work now == +qemu-io: can't open: Invalid password, cannot unlock any keyslot +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== erase last secret (should fail) == +qemu-img: Requested operation would erase all active keyslots which will erase all the data in the image irreversibly - refusing operation +qemu-img: Requested operation would erase all active keyslots which will erase all the data in the image irreversibly - refusing operation +== erase non existing secrets (should fail) == +qemu-img: No secret with id 'sec5' +qemu-img: Requested operation didn't match any slots + +== erase last secret with force by slot (should work) == + +== we have no secrets now, data is lost forever == +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +qemu-io: can't open: Invalid password, cannot unlock any keyslot +*** done diff --git a/tests/qemu-iotests/301 b/tests/qemu-iotests/301 new file mode 100755 index 0000000000..56ac3d8ef2 --- /dev/null +++ b/tests/qemu-iotests/301 @@ -0,0 +1,90 @@ +# +# Copyright (C) 2019 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# creator +owner=mlevitsk@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt luks +_supported_proto file #TODO + +QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT + +# you are supposed to see the password as *******, see :-) +S0="--object secret,id=sec0,data=hunter0" +S1="--object secret,id=sec1,data=hunter1" +SECRETS="$S0 $S1" + + +IMGS0="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,key-secret=sec0" +IMGS1="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,key-secret=sec1" + +echo "== creating a test image ==" +_make_test_img $S0 -o "key-secret=sec0,iter-time=10" 32M + +echo +echo "== test that key 0 opens the image ==" +$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir + +echo +echo "== adding a password to slot 1 ==" +$QEMU_IMG amend $SECRETS $IMGS0 -o keys.0.new-secret=sec1,keys.0.keyslot=1,keys.0.iter-time=10 + +echo +echo "== 'backup' the image header ==" +dd if=$TEST_IMG_FILE of=${TEST_IMG_FILE}.bk bs=4K skip=0 count=1 + +echo +echo "== erase slot 0 ==" +$QEMU_IMG amend $SECRETS $IMGS1 -o keys.0.new-secret="",keys.0.keyslot=0 | _filter_img_create + +echo +echo "== test that key 0 doesn't open the image ==" +$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir + +echo +echo "== 'restore' the image header ==" +dd if=${TEST_IMG_FILE}.bk of=${TEST_IMG_FILE} bs=4K skip=0 count=1 conv=notrunc + +echo +echo "== test that key 0 still doesn't open the image (key material is erased) ==" +$QEMU_IO $SECRETS -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir + +echo +echo "== test that key 1 still works ==" +$QEMU_IO $SECRETS -c "read 0 4096" $IMGS1 | _filter_qemu_io | _filter_testdir + +echo "*** done" +rm -f $seq.full +status=0 + + +exit 0 diff --git a/tests/qemu-iotests/301.out b/tests/qemu-iotests/301.out new file mode 100644 index 0000000000..e653c30330 --- /dev/null +++ b/tests/qemu-iotests/301.out @@ -0,0 +1,30 @@ +QA output created by 301 +== creating a test image == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 + +== test that key 0 opens the image == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== adding a password to slot 1 == + +== 'backup' the image header == +1+0 records in +1+0 records out + +== erase slot 0 == + +== test that key 0 doesn't open the image == +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== 'restore' the image header == +1+0 records in +1+0 records out + +== test that key 0 still doesn't open the image (key material is erased) == +qemu-io: can't open: Invalid password, cannot unlock any keyslot + +== test that key 1 still works == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +*** done diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index cb2b789e44..34e3139ad7 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -288,3 +288,6 @@ 277 rw quick 279 rw backing quick 280 rw migration quick + +300 rw auto +301 rw auto quick From patchwork Tue Jan 14 19:33:47 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223023 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=B1ACH73N; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y13s10vKz9s29 for ; Wed, 15 Jan 2020 06:39:05 +1100 (AEDT) Received: from localhost ([::1]:45170 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS1m-0007SM-G5 for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:39:02 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38505) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyT-0003Q1-L6 for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:41 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRyQ-0007u1-Dg for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:37 -0500 Received: from us-smtp-delivery-1.mimecast.com ([207.211.31.120]:56631 helo=us-smtp-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRyQ-0007tE-Ac for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:34 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030533; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=aisj+mR7VgKL4Znpf1cP/KJ/RiE/Cey1r7bpgUsaqc4=; b=B1ACH73NNDREpnhHK2mXaQLZCDoyF5JTVbVu32FxeXLWrrE5/sjsEt/gHcsvA6Smc21Wfu oG3WT1Jg6IS8Hg3M9kaI2PZODOl9s/BBRsnLEetHNQaB4E8mWbFdxprLXMvzfaDTAo4/tL nlypq2i0uB/APJoshTbGL//dnpM2tYo= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-204-NZpAaqh5OTCnpS5hg8d_Iw-1; Tue, 14 Jan 2020 14:34:27 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 7BA1F107ACC5; Tue, 14 Jan 2020 19:34:26 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 410567C83A; Tue, 14 Jan 2020 19:34:24 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 10/13] block: add generic infrastructure for x-blockdev-amend qmp command Date: Tue, 14 Jan 2020 21:33:47 +0200 Message-Id: <20200114193350.10830-11-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: NZpAaqh5OTCnpS5hg8d_Iw-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.120 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" blockdev-amend will be used similiar to blockdev-create to allow on the fly changes of the structure of the format based block devices. Current plan is to first support encryption keyslot management for luks based formats (raw and embedded in qcow2) Signed-off-by: Maxim Levitsky --- block/Makefile.objs | 2 +- block/amend.c | 108 ++++++++++++++++++++++++++++++++++++++ include/block/block_int.h | 21 +++++--- qapi/block-core.json | 42 +++++++++++++++ qapi/job.json | 4 +- 5 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 block/amend.c diff --git a/block/Makefile.objs b/block/Makefile.objs index 330529b0b7..eb5ddb7158 100644 --- a/block/Makefile.objs +++ b/block/Makefile.objs @@ -18,7 +18,7 @@ block-obj-y += block-backend.o snapshot.o qapi.o block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o block-obj-$(CONFIG_POSIX) += file-posix.o block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o -block-obj-y += null.o mirror.o commit.o io.o create.o +block-obj-y += null.o mirror.o commit.o io.o create.o amend.o block-obj-y += throttle-groups.o block-obj-$(CONFIG_LINUX) += nvme.o diff --git a/block/amend.c b/block/amend.c new file mode 100644 index 0000000000..2db7b1eafc --- /dev/null +++ b/block/amend.c @@ -0,0 +1,108 @@ +/* + * Block layer code related to image options amend + * + * Copyright (c) 2018 Kevin Wolf + * Copyright (c) 2019 Maxim Levitsky + * + * Heavily based on create.c + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "block/block_int.h" +#include "qemu/job.h" +#include "qemu/main-loop.h" +#include "qapi/qapi-commands-block-core.h" +#include "qapi/qapi-visit-block-core.h" +#include "qapi/clone-visitor.h" +#include "qapi/error.h" + +typedef struct BlockdevAmendJob { + Job common; + BlockdevAmendOptions *opts; + BlockDriverState *bs; + bool force; +} BlockdevAmendJob; + +static int coroutine_fn blockdev_amend_run(Job *job, Error **errp) +{ + BlockdevAmendJob *s = container_of(job, BlockdevAmendJob, common); + int ret; + + job_progress_set_remaining(&s->common, 1); + ret = s->bs->drv->bdrv_co_amend(s->bs, s->opts, s->force, errp); + job_progress_update(&s->common, 1); + qapi_free_BlockdevAmendOptions(s->opts); + return ret; +} + +static const JobDriver blockdev_amend_job_driver = { + .instance_size = sizeof(BlockdevAmendJob), + .job_type = JOB_TYPE_AMEND, + .run = blockdev_amend_run, +}; + +void qmp_x_blockdev_amend(const char *job_id, + const char *node_name, + BlockdevAmendOptions *options, + bool has_force, + bool force, + Error **errp) +{ + BlockdevAmendJob *s; + const char *fmt = BlockdevDriver_str(options->driver); + BlockDriver *drv = bdrv_find_format(fmt); + BlockDriverState *bs = bdrv_find_node(node_name); + + /* + * If the driver is in the schema, we know that it exists. But it may not + * be whitelisted. + */ + assert(drv); + if (bdrv_uses_whitelist() && !bdrv_is_whitelisted(drv, false)) { + error_setg(errp, "Driver is not whitelisted"); + return; + } + + if (bs->drv != drv) { + error_setg(errp, + "x-blockdev-amend doesn't support changing the block driver"); + return; + } + + /* Error out if the driver doesn't support .bdrv_co_amend */ + if (!drv->bdrv_co_amend) { + error_setg(errp, "Driver does not support x-blockdev-amend"); + return; + } + + /* Create the block job */ + s = job_create(job_id, &blockdev_amend_job_driver, NULL, + bdrv_get_aio_context(bs), JOB_DEFAULT | JOB_MANUAL_DISMISS, + NULL, NULL, errp); + if (!s) { + return; + } + + s->bs = bs, + s->opts = QAPI_CLONE(BlockdevAmendOptions, options), + s->force = has_force ? force : false; + job_start(&s->common); +} diff --git a/include/block/block_int.h b/include/block/block_int.h index 6f0abf8544..025d1f298a 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -133,11 +133,26 @@ struct BlockDriver { int (*bdrv_file_open)(BlockDriverState *bs, QDict *options, int flags, Error **errp); void (*bdrv_close)(BlockDriverState *bs); + + int coroutine_fn (*bdrv_co_create)(BlockdevCreateOptions *opts, Error **errp); int coroutine_fn (*bdrv_co_create_opts)(const char *filename, QemuOpts *opts, Error **errp); + + int coroutine_fn (*bdrv_co_amend)(BlockDriverState *bs, + BlockdevAmendOptions *opts, + bool force, + Error **errp); + + int (*bdrv_amend_options)(BlockDriverState *bs, + QemuOpts *opts, + BlockDriverAmendStatusCB *status_cb, + void *cb_opaque, + bool force, + Error **errp); + int (*bdrv_make_empty)(BlockDriverState *bs); /* @@ -428,12 +443,6 @@ struct BlockDriver { BdrvCheckResult *result, BdrvCheckMode fix); - int (*bdrv_amend_options)(BlockDriverState *bs, QemuOpts *opts, - BlockDriverAmendStatusCB *status_cb, - void *cb_opaque, - bool force, - Error **errp); - void (*bdrv_debug_event)(BlockDriverState *bs, BlkdebugEvent event); /* TODO Better pass a option string/QDict/QemuOpts to add any rule? */ diff --git a/qapi/block-core.json b/qapi/block-core.json index 7ff5e5edaf..601f7dc9a4 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -4743,6 +4743,48 @@ 'data': { 'job-id': 'str', 'options': 'BlockdevCreateOptions' } } +## +# @BlockdevAmendOptions: +# +# Options for amending an image format +# +# @driver block driver that is suitable for the image +# +# Since: 5.0 +## +{ 'union': 'BlockdevAmendOptions', + 'base': { + 'driver': 'BlockdevDriver' }, + 'discriminator': 'driver', + 'data': { + } } + +## +# @x-blockdev-amend: +# +# Starts a job to amend format specific options of an existing open block device +# The job is automatically finalized, but a manual job-dismiss is required. +# +# @job-id: Identifier for the newly created job. +# +# @node-name: Name of the block node to work on +# +# @options: Options (driver specific) +# +# @force: Allow unsafe operations, format specific +# For luks that allows erase of the last active keyslot +# (permanent loss of data), +# and replacement of an active keyslot +# (possible loss of data if IO error happens) +# +# Since: 5.0 +## +{ 'command': 'x-blockdev-amend', + 'data': { 'job-id': 'str', + 'node-name': 'str', + 'options': 'BlockdevAmendOptions', + '*force': 'bool' } } + ## # @blockdev-open-tray: # diff --git a/qapi/job.json b/qapi/job.json index a121b615fb..362b634ec1 100644 --- a/qapi/job.json +++ b/qapi/job.json @@ -19,10 +19,12 @@ # # @create: image creation job type, see "blockdev-create" (since 3.0) # +# @amend: image options amend job type, see "x-blockdev-amend" (since 5.0) +# # Since: 1.7 ## { 'enum': 'JobType', - 'data': ['commit', 'stream', 'mirror', 'backup', 'create'] } + 'data': ['commit', 'stream', 'mirror', 'backup', 'create', 'amend'] } ## # @JobStatus: From patchwork Tue Jan 14 19:33:48 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223027 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=YR3K2Riw; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y1782qHKz9sPW for ; Wed, 15 Jan 2020 06:41:56 +1100 (AEDT) Received: from localhost ([::1]:45220 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS4X-00030v-JU for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:41:53 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38613) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyx-00049o-1h for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:36:11 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRys-0008Pz-Bu for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:36:06 -0500 Received: from us-smtp-2.mimecast.com ([207.211.31.81]:59711 helo=us-smtp-delivery-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRys-0008PG-8p for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:36:02 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030561; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=hQ5bXxITIuqy52jXzHei4iEHslFHnNshFxEY5PBzK50=; b=YR3K2RiwptqVN+cgthvFuIJCjPi6BtFafZ31jlJbdkze5QpDcQxSLeMvi+m9IsUTwNbuhk lcVgaMv4sfFsQAC5jrleXZUnx+Tp1prOyVUyqVhUmywC/qdMZfwtdXJqknw7p5H82EtgML z8nOl4RwZjcG98VE0e9kXzomaZ9Zu/8= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-35-dG_xLfB5M4aLvbe9WTpVgA-1; Tue, 14 Jan 2020 14:34:30 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 20A001005502; Tue, 14 Jan 2020 19:34:29 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id D75CE87EFE; Tue, 14 Jan 2020 19:34:26 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 11/13] block/crypto: implement blockdev-amend Date: Tue, 14 Jan 2020 21:33:48 +0200 Message-Id: <20200114193350.10830-12-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: dG_xLfB5M4aLvbe9WTpVgA-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.81 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" Signed-off-by: Maxim Levitsky Reviewed-by: Daniel P. Berrangé --- block/crypto.c | 70 ++++++++++++++++++++++++++++++++------------ qapi/block-core.json | 14 ++++++++- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index 081880bced..6836337863 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -697,32 +697,21 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp) } static int -block_crypto_amend_options(BlockDriverState *bs, - QemuOpts *opts, - BlockDriverAmendStatusCB *status_cb, - void *cb_opaque, - bool force, - Error **errp) +block_crypto_amend_options_generic(BlockDriverState *bs, + QCryptoBlockAmendOptions *amend_options, + 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; + /* 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) { - 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; + if (ret) { goto cleanup; } @@ -734,13 +723,55 @@ block_crypto_amend_options(BlockDriverState *bs, force, errp); cleanup: + /* release exclusive read/write permissions to the underlying file*/ crypto->updating_keys = false; bdrv_child_refresh_perms(bs, bs->file, errp); - qapi_free_QCryptoBlockAmendOptions(amend_options); + return ret; +} + +static int +block_crypto_amend_options(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(bs, amend_options, force, errp); +cleanup: + qapi_free_QCryptoBlockAmendOptions(amend_options); return ret; } +static int +coroutine_fn block_crypto_co_amend(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(bs, &amend_opts, force, errp); +} static void block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c, @@ -812,6 +843,7 @@ static BlockDriver bdrv_crypto_luks = { .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, + .bdrv_co_amend = block_crypto_co_amend, .strong_runtime_opts = block_crypto_strong_runtime_opts, }; diff --git a/qapi/block-core.json b/qapi/block-core.json index 601f7dc9a4..790aa40991 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -4743,6 +4743,18 @@ 'data': { 'job-id': 'str', 'options': 'BlockdevCreateOptions' } } +## +# @BlockdevAmendOptionsLUKS: +# +# Driver specific image amend options for LUKS. +# +# Since: 5.0 +## +{ 'struct': 'BlockdevAmendOptionsLUKS', + 'base': 'QCryptoBlockAmendOptionsLUKS', + 'data': { } +} + ## # @BlockdevAmendOptions: # @@ -4757,7 +4769,7 @@ 'driver': 'BlockdevDriver' }, 'discriminator': 'driver', 'data': { - } } + 'luks': 'BlockdevAmendOptionsLUKS' } } ## # @x-blockdev-amend: From patchwork Tue Jan 14 19:33:49 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223024 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=LIy4w+9z; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y1552hpbz9sPW for ; Wed, 15 Jan 2020 06:40:09 +1100 (AEDT) Received: from localhost ([::1]:45184 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS2o-0000VF-LQ for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:40:06 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38528) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRya-0003b7-JV for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:48 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRyW-00080G-Kb for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:44 -0500 Received: from us-smtp-delivery-1.mimecast.com ([207.211.31.120]:55045 helo=us-smtp-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRyW-0007zv-Gb for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:40 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030540; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=izlfqHc08rG9gHbMwis+3j53B7tLvK5Fku3G4K6KUXI=; b=LIy4w+9zV6FeMwO2ue/MEsv5j5ZgehcUKmcUrTtuvbQTaq00ZI9/zPEJ9LH9n+r03ycsDg 8RNyBzG6vCIWnZAUJiHxIN6PCR5QXwbRMGEOIWdpuc1a9HGziCH2hk/p2jv2rWVLwpRODD oC1i7ZeIqGWQTKAaiuiUt9CWDhMrM+E= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-286-5_ds7tx_POK8wwztIMbbIg-1; Tue, 14 Jan 2020 14:34:35 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 2E63A800D41; Tue, 14 Jan 2020 19:34:34 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7D52D7C83A; Tue, 14 Jan 2020 19:34:29 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 12/13] block/qcow2: implement blockdev-amend Date: Tue, 14 Jan 2020 21:33:49 +0200 Message-Id: <20200114193350.10830-13-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: 5_ds7tx_POK8wwztIMbbIg-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.120 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" Currently the implementation only supports amending the encryption options, unlike the qemu-img version Signed-off-by: Maxim Levitsky Reviewed-by: Daniel P. Berrangé --- block/qcow2.c | 39 +++++++++++++++++++++++++++++++++++++++ qapi/block-core.json | 16 +++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/block/qcow2.c b/block/qcow2.c index 1b01174aed..8b74471bc6 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -5444,6 +5444,44 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts, return 0; } +static int coroutine_fn qcow2_co_amend(BlockDriverState *bs, + BlockdevAmendOptions *opts, + bool force, + Error **errp) +{ + BlockdevAmendOptionsQcow2 *qopts = &opts->u.qcow2; + BDRVQcow2State *s = bs->opaque; + int ret = 0; + + if (qopts->has_encrypt) { + if (!s->crypto) { + error_setg(errp, "image is not encrypted, can't amend"); + return -EOPNOTSUPP; + } + + if (qopts->encrypt->format != Q_CRYPTO_BLOCK_FORMAT_LUKS) { + error_setg(errp, + "Amend can't be used to change the qcow2 encryption format"); + return -EOPNOTSUPP; + } + + if (s->crypt_method_header != QCOW_CRYPT_LUKS) { + error_setg(errp, + "Only LUKS encryption options can be amended for qcow2 with blockdev-amend"); + return -EOPNOTSUPP; + } + + ret = qcrypto_block_amend_options(s->crypto, + qcow2_crypto_hdr_read_func, + qcow2_crypto_hdr_write_func, + bs, + qopts->encrypt, + force, + errp); + } + return ret; +} + /* * If offset or size are negative, respectively, they will not be included in * the BLOCK_IMAGE_CORRUPTED event emitted. @@ -5656,6 +5694,7 @@ BlockDriver bdrv_qcow2 = { .mutable_opts = mutable_opts, .bdrv_co_check = qcow2_co_check, .bdrv_amend_options = qcow2_amend_options, + .bdrv_co_amend = qcow2_co_amend, .bdrv_detach_aio_context = qcow2_detach_aio_context, .bdrv_attach_aio_context = qcow2_attach_aio_context, diff --git a/qapi/block-core.json b/qapi/block-core.json index 790aa40991..2cf1f443e5 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -4755,6 +4755,19 @@ 'data': { } } +## +# @BlockdevAmendOptionsQcow2: +# +# Driver specific image amend options for qcow2. +# For now, only encryption options can be amended +# +# @encrypt Encryption options to be amended +# +# Since: 5.0 +## +{ 'struct': 'BlockdevAmendOptionsQcow2', + 'data': { '*encrypt': 'QCryptoBlockAmendOptions' } } + ## # @BlockdevAmendOptions: # @@ -4769,7 +4782,8 @@ 'driver': 'BlockdevDriver' }, 'discriminator': 'driver', 'data': { - 'luks': 'BlockdevAmendOptionsLUKS' } } + 'luks': 'BlockdevAmendOptionsLUKS', + 'qcow2': 'BlockdevAmendOptionsQcow2' } } ## # @x-blockdev-amend: From patchwork Tue Jan 14 19:33:50 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 1223028 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=X+AK8U1k; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47y18d6pGWz9s29 for ; Wed, 15 Jan 2020 06:43:13 +1100 (AEDT) Received: from localhost ([::1]:45234 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irS5n-000557-EI for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2020 14:43:11 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:38570) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1irRyg-0003l3-8O for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:53 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1irRyc-00087Z-FD for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:50 -0500 Received: from us-smtp-2.mimecast.com ([207.211.31.81]:39113 helo=us-smtp-delivery-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1irRyc-00086d-AA for qemu-devel@nongnu.org; Tue, 14 Jan 2020 14:35:46 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1579030545; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=GaJLNi0bjpHhyQkpxDd9fqnbrMU0ElxtTJ133EXFYGg=; b=X+AK8U1kXSy558tUxSaQj9oThsY1VDqUdleCke4TI2Kaa8EVjdnAI6sYVWky6AtR/brizh YOHbeCkfqMpoon/N872uDVfoDrP8zW6AOifnpDJtR6hmzJmUSnBH/silzv24NoSA63AeFR WPl4xPcY25ir/BhVwiNAegXka8H/G6o= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-8-IEP_PUnuPxivCOl97DX5GQ-1; Tue, 14 Jan 2020 14:34:37 -0500 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id CA82618AAFA1; Tue, 14 Jan 2020 19:34:36 +0000 (UTC) Received: from maximlenovopc.usersys.redhat.com (unknown [10.35.206.41]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8A3907C83A; Tue, 14 Jan 2020 19:34:34 +0000 (UTC) From: Maxim Levitsky To: qemu-devel@nongnu.org Subject: [PATCH 13/13] iotests: add tests for blockdev-amend Date: Tue, 14 Jan 2020 21:33:50 +0200 Message-Id: <20200114193350.10830-14-mlevitsk@redhat.com> In-Reply-To: <20200114193350.10830-1-mlevitsk@redhat.com> References: <20200114193350.10830-1-mlevitsk@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-MC-Unique: IEP_PUnuPxivCOl97DX5GQ-1 X-Mimecast-Spam-Score: 0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 207.211.31.81 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= , qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Maxim Levitsky , John Snow Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" This commit adds two tests that cover the new blockdev-amend functionality of luks and qcow2 driver Signed-off-by: Maxim Levitsky --- tests/qemu-iotests/302 | 284 +++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/302.out | 40 ++++++ tests/qemu-iotests/303 | 235 ++++++++++++++++++++++++++++++ tests/qemu-iotests/303.out | 33 +++++ tests/qemu-iotests/group | 3 + 5 files changed, 595 insertions(+) create mode 100644 tests/qemu-iotests/302 create mode 100644 tests/qemu-iotests/302.out create mode 100644 tests/qemu-iotests/303 create mode 100644 tests/qemu-iotests/303.out diff --git a/tests/qemu-iotests/302 b/tests/qemu-iotests/302 new file mode 100644 index 0000000000..bc507377a5 --- /dev/null +++ b/tests/qemu-iotests/302 @@ -0,0 +1,284 @@ +#!/usr/bin/env python +# +# Test case QMP's encrypted key management +# +# Copyright (C) 2019 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import iotests +import os +import time +import json + +test_img = os.path.join(iotests.test_dir, 'test.img') + +class Secret: + def __init__(self, index): + self._id = "keysec" + str(index) + # you are not supposed to see the password... + self._secret = "hunter" + str(index) + + def id(self): + return self._id + + def secret(self): + return self._secret + + def to_cmdline_object(self): + return [ "secret,id=" + self._id + ",data=" + self._secret] + + def to_qmp_object(self): + return { "qom_type" : "secret", "id": self.id(), + "props": { "data": self.secret() } } + +################################################################################ +class EncryptionSetupTestCase(iotests.QMPTestCase): + + # test case startup + def setUp(self): + # start the VM + self.vm = iotests.VM() + self.vm.launch() + + # create the secrets and load 'em into the VM + self.secrets = [ Secret(i) for i in range(0, 6) ] + for secret in self.secrets: + result = self.vm.qmp("object-add", **secret.to_qmp_object()) + self.assert_qmp(result, 'return', {}) + + if iotests.imgfmt == "qcow2": + self.pfx = "encrypt." + self.img_opts = [ '-o', "encrypt.format=luks" ] + else: + self.pfx = "" + self.img_opts = [] + + # test case shutdown + def tearDown(self): + # stop the VM + self.vm.shutdown() + + ########################################################################### + # create the encrypted block device + def createImg(self, file, secret): + + iotests.qemu_img( + 'create', + '--object', *secret.to_cmdline_object(), + '-f', iotests.imgfmt, + '-o', self.pfx + 'key-secret=' + secret.id(), + '-o', self.pfx + 'iter-time=10', + *self.img_opts, + file, + '1M') + + ########################################################################### + # open an encrypted block device + def openImageQmp(self, id, file, secret, read_only = False): + + encrypt_options = { + 'key-secret' : secret.id() + } + + if iotests.imgfmt == "qcow2": + encrypt_options = { + 'encrypt': { + 'format':'luks', + **encrypt_options + } + } + + result = self.vm.qmp('blockdev-add', ** + { + 'driver': iotests.imgfmt, + 'node-name': id, + 'read-only': read_only, + + **encrypt_options, + + 'file': { + 'driver': 'file', + 'filename': test_img, + } + } + ) + self.assert_qmp(result, 'return', {}) + + # close the encrypted block device + def closeImageQmp(self, id): + result = self.vm.qmp('blockdev-del', **{ 'node-name': id }) + self.assert_qmp(result, 'return', {}) + + ########################################################################### + # add a key to an encrypted block device + def addKeyQmp(self, id, secret, unlock_secret = None, + slot = None, force = False): + + keyupdate0 = { + 'new-secret' : secret.id(), + 'iter-time' : 10 + } + + if slot != None: + keyupdate0['keyslot'] = slot + + crypt_options = { + 'keys' : [ keyupdate0 ] + } + + if unlock_secret != None: + crypt_options['unlock-secret'] = unlock_secret.id() + + if iotests.imgfmt == "qcow2": + crypt_options['format'] = 'luks' + crypt_options = { + 'encrypt': crypt_options + } + + args = { + 'node-name': id, + 'job-id' : 'job_add_key', + 'options' : { + 'driver' : iotests.imgfmt, + **crypt_options + }, + } + + if force == True: + args['force'] = True + + #TODO: check what jobs return + result = self.vm.qmp('x-blockdev-amend', **args) + assert result['return'] == {} + self.vm.run_job('job_add_key') + + # erase a key from an encrypted block device + def eraseKeyQmp(self, id, secret = None, slot = None, force = False): + + keyupdate0 = { + 'new-secret': None + } + + if slot != None: + keyupdate0['keyslot'] = slot + if secret != None: + keyupdate0['old-secret'] = secret.id() + + crypt_options = { + 'keys' : [ keyupdate0 ] + } + + if iotests.imgfmt == "qcow2": + crypt_options['format'] = 'luks' + crypt_options = { + 'encrypt': crypt_options + } + + args = { + 'node-name': id, + 'job-id' : 'job_erase_key', + 'options' : { + 'driver' : iotests.imgfmt, + **crypt_options + }, + } + + if force == True: + args['force'] = True + + result = self.vm.qmp('x-blockdev-amend', **args) + assert result['return'] == {} + self.vm.run_job('job_erase_key') + + ########################################################################### + # create image, and change its key + def testChangeKey(self): + + # create the image with secret0 and open it + self.createImg(test_img, self.secrets[0]); + self.openImageQmp("testdev", test_img, self.secrets[0]) + + # add key to slot 1 + self.addKeyQmp("testdev", secret = self.secrets[1]) + + # add key to slot 5 + self.addKeyQmp("testdev", secret = self.secrets[2], slot=5) + + # erase key from slot 0 + self.eraseKeyQmp("testdev", secret = self.secrets[0]) + + #reopen the image with secret1 + self.closeImageQmp("testdev") + self.openImageQmp("testdev", test_img, self.secrets[1]) + + # close and erase the image for good + self.closeImageQmp("testdev") + os.remove(test_img) + + # test that if we erase the old password, + # we can still change the encryption keys using 'old-secret' + def testOldPassword(self): + + # create the image with secret0 and open it + self.createImg(test_img, self.secrets[0]); + self.openImageQmp("testdev", test_img, self.secrets[0]) + + # add key to slot 1 + self.addKeyQmp("testdev", secret = self.secrets[1]) + + # erase key from slot 0 + self.eraseKeyQmp("testdev", secret = self.secrets[0]) + + # this will fail as the old password is no longer valid + self.addKeyQmp("testdev", secret = self.secrets[2]) + + # this will work + self.addKeyQmp("testdev", secret = self.secrets[2], unlock_secret = self.secrets[1]) + + # close and erase the image for good + self.closeImageQmp("testdev") + os.remove(test_img) + + def testUseForceLuke(self): + + self.createImg(test_img, self.secrets[0]); + self.openImageQmp("testdev", test_img, self.secrets[0]) + + # Add bunch of secrets + self.addKeyQmp("testdev", secret = self.secrets[1], slot=4) + self.addKeyQmp("testdev", secret = self.secrets[4], slot=2) + + # overwrite an active secret + self.addKeyQmp("testdev", secret = self.secrets[5], slot=2) + self.addKeyQmp("testdev", secret = self.secrets[5], slot=2, force=True) + + self.addKeyQmp("testdev", secret = self.secrets[0]) + + # Now erase all the secrets + self.eraseKeyQmp("testdev", secret = self.secrets[5]) + self.eraseKeyQmp("testdev", slot=4) + + # erase last keyslot + self.eraseKeyQmp("testdev", secret = self.secrets[0]) + self.eraseKeyQmp("testdev", secret = self.secrets[0], force=True) + + self.closeImageQmp("testdev") + os.remove(test_img) + + +if __name__ == '__main__': + # Encrypted formats support + iotests.main(supported_fmts = ['qcow2', 'luks']) diff --git a/tests/qemu-iotests/302.out b/tests/qemu-iotests/302.out new file mode 100644 index 0000000000..da4d43d31e --- /dev/null +++ b/tests/qemu-iotests/302.out @@ -0,0 +1,40 @@ +... +---------------------------------------------------------------------- +Ran 3 tests + +OK +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} +{"return": {}} +Job failed: Invalid password, cannot unlock any keyslot +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +Job failed: Refusing to overwrite active slot 2 - please erase it first +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} +{"return": {}} +Job failed: Requested operation will erase all active keyslots which will erase all the data in the image irreversibly - refusing operation +{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}} +{"return": {}} diff --git a/tests/qemu-iotests/303 b/tests/qemu-iotests/303 new file mode 100644 index 0000000000..75cdd099cf --- /dev/null +++ b/tests/qemu-iotests/303 @@ -0,0 +1,235 @@ +#!/usr/bin/env python +# +# Test case for encryption key management versus image sharing +# +# Copyright (C) 2019 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import iotests +import os +import time +import json + +test_img = os.path.join(iotests.test_dir, 'test.img') + +class Secret: + def __init__(self, index): + self._id = "keysec" + str(index) + # you are not supposed to see the password... + self._secret = "hunter" + str(index) + + def id(self): + return self._id + + def secret(self): + return self._secret + + def to_cmdline_object(self): + return [ "secret,id=" + self._id + ",data=" + self._secret] + + def to_qmp_object(self): + return { "qom_type" : "secret", "id": self.id(), + "props": { "data": self.secret() } } + +################################################################################ + +class EncryptionSetupTestCase(iotests.QMPTestCase): + + # test case startup + def setUp(self): + + # start the VMs + self.vm1 = iotests.VM(path_suffix = 'VM1') + self.vm2 = iotests.VM(path_suffix = 'VM2') + self.vm1.launch() + self.vm2.launch() + + # create the secrets and load 'em into the VMs + self.secrets = [ Secret(i) for i in range(0, 4) ] + for secret in self.secrets: + result = self.vm1.qmp("object-add", **secret.to_qmp_object()) + self.assert_qmp(result, 'return', {}) + result = self.vm2.qmp("object-add", **secret.to_qmp_object()) + self.assert_qmp(result, 'return', {}) + + # test case shutdown + def tearDown(self): + # stop the VM + self.vm1.shutdown() + self.vm2.shutdown() + + ########################################################################### + # create the encrypted block device using qemu-img + def createImg(self, file, secret): + + output = iotests.qemu_img_pipe( + 'create', + '--object', *secret.to_cmdline_object(), + '-f', iotests.imgfmt, + '-o', 'key-secret=' + secret.id(), + '-o', 'iter-time=10', + file, + '1M') + + iotests.log(output, filters=[iotests.filter_test_dir]) + + # attempts to add a key using qemu-img + def addKey(self, file, secret_open, secret_add): + + image_options = { + 'key-secret' : secret_open.id(), + 'driver' : iotests.imgfmt, + 'file' : { + 'driver':'file', + 'filename': file, + } + } + + output = iotests.qemu_img_pipe( + 'amend', + '--object', *secret_open.to_cmdline_object(), + '--object', *secret_add.to_cmdline_object(), + + '-o', 'keys.0.new-secret=' + secret_add.id(), + '-o', 'keys.0.iter-time=10', + + "json:" + json.dumps(image_options) + ) + + iotests.log(output, filters=[iotests.filter_test_dir]) + + ########################################################################### + # open an encrypted block device + def openImageQmp(self, vm, id, file, secret, + readOnly = False, reOpen = False): + + command = 'x-blockdev-reopen' if reOpen else 'blockdev-add' + + result = vm.qmp(command, ** + { + 'driver': iotests.imgfmt, + 'node-name': id, + 'read-only': readOnly, + 'key-secret' : secret.id(), + 'file': { + 'driver': 'file', + 'filename': test_img, + } + } + ) + self.assert_qmp(result, 'return', {}) + + # close the encrypted block device + def closeImageQmp(self, vm, id): + result = vm.qmp('blockdev-del', **{ 'node-name': id }) + self.assert_qmp(result, 'return', {}) + + ########################################################################### + + # add a key to an encrypted block device + def addKeyQmp(self, vm, id, secret): + + args = { + 'node-name': id, + 'job-id' : 'job0', + 'options' : { + 'driver' : iotests.imgfmt, + 'keys': [ + { + 'new-secret' : secret.id(), + 'iter-time' : 10 + } + ] + }, + } + + result = vm.qmp('x-blockdev-amend', **args) + assert result['return'] == {} + vm.run_job('job0') + + # test that when the image opened by two qemu processes, + # neither of them can update the image + def test1(self): + self.createImg(test_img, self.secrets[0]); + + # VM1 opens the image and adds a key + self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0]) + self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[1]) + + + # VM2 opens the image + self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0]) + + + # neither VMs now should be able to add a key + self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2]) + self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2]) + + + # VM 1 closes the image + self.closeImageQmp(self.vm1, "testdev") + + + # now VM2 can add the key + self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2]) + + + # qemu-img should also not be able to add a key + self.addKey(test_img, self.secrets[0], self.secrets[2]) + + # cleanup + self.closeImageQmp(self.vm2, "testdev") + os.remove(test_img) + + + def test2(self): + self.createImg(test_img, self.secrets[0]); + + # VM1 opens the image readonly + self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0], + readOnly = True) + + # VM2 opens the image + self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0]) + + # VM1 can't add a key since image is readonly + self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2]) + + # VM2 can't add a key since VM is has the image opened + self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2]) + + + #VM1 reopens the image read-write + self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0], + reOpen = True, readOnly = False) + + # VM1 still can't add the key + self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2]) + + # VM2 gets away + self.closeImageQmp(self.vm2, "testdev") + + # VM1 now can add the key + self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2]) + + self.closeImageQmp(self.vm1, "testdev") + os.remove(test_img) + + +if __name__ == '__main__': + # support only raw luks since luks encrypted qcow2 is a proper + # format driver which doesn't allow any sharing + iotests.main(supported_fmts = ['luks']) diff --git a/tests/qemu-iotests/303.out b/tests/qemu-iotests/303.out new file mode 100644 index 0000000000..a62bb20b67 --- /dev/null +++ b/tests/qemu-iotests/303.out @@ -0,0 +1,33 @@ +.. +---------------------------------------------------------------------- +Ran 2 tests + +OK +Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10 + +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} +Job failed: Failed to get shared "consistent read" lock +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} +Job failed: Failed to get shared "consistent read" lock +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} +qemu-img: Failed to get shared "consistent read" lock +Is another process using the image [TEST_DIR/test.img]? + +Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10 + +Job failed: Block node is read-only +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} +Job failed: Failed to get shared "consistent read" lock +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} +Job failed: Failed to get shared "consistent read" lock +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index 34e3139ad7..c92fff2b4a 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -291,3 +291,6 @@ 300 rw auto 301 rw auto quick +302 rw auto +303 rw auto +