From: "Daniel P. Berrange" <berrange@redhat.com>
To: Max Reitz <mreitz@redhat.com>
Cc: qemu-devel@nongnu.org, Kevin Wolf <kwolf@redhat.com>,
qemu-block@nongnu.org
Subject: Re: [Qemu-devel] [PATCH v1 12/15] qcow2: add support for LUKS encryption format
Date: Tue, 24 Jan 2017 13:58:48 +0000 [thread overview]
Message-ID: <20170124135848.GA28745@redhat.com> (raw)
In-Reply-To: <fdf48e56-2b65-b0cd-39da-010ca751cc02@redhat.com>
On Sat, Jan 21, 2017 at 07:57:45PM +0100, Max Reitz wrote:
> On 03.01.2017 19:27, Daniel P. Berrange wrote:
> > This adds support for using LUKS as an encryption format
> > with the qcow2 file. The use of the existing 'encryption=on'
> > parameter is replaced by a new parameter 'encryption-format'
> > which takes the values 'aes' or 'luks'. e.g.
> >
> > # qemu-img create --object secret,data=123456,id=sec0 \
> > -f qcow2 -o encryption-format=luks,luks-key-secret=sec0 \
> > test.qcow2 10G
> >
> > results in the creation of an image using the LUKS format.
> > Use of the legacy 'encryption=on' parameter still results
> > in creation of the old qcow2 AES format, and is equivalent
> > to the new 'encryption-format=aes'. e.g. the following are
> > equivalent:
> >
> > # qemu-img create --object secret,data=123456,id=sec0 \
> > -f qcow2 -o encryption=on,aes-key-secret=sec0 \
> > test.qcow2 10G
> >
> > # qemu-img create --object secret,data=123456,id=sec0 \
> > -f qcow2 -o encryption-format=aes,aes-key-secret=sec0 \
> > test.qcow2 10G
> >
> > With the LUKS format it is necessary to store the LUKS
> > partition header and key material in the QCow2 file. This
> > data can be many MB in size, so cannot go into the QCow2
> > header region directly. Thus the spec defines a FDE
> > (Full Disk Encryption) header extension that specifies
> > the offset of a set of clusters to hold the FDE headers,
> > as well as the length of that region. The LUKS header is
> > thus stored in these extra allocated clusters before the
> > main image payload.
> >
> > Aside from all the cryptographic differences implied by
> > use of the LUKS format, there is one further key difference
> > between the use of legacy AES and LUKS encryption in qcow2.
> > For LUKS, the initialiazation vectors are generated using
> > the host physical sector as the input, rather than the
> > guest virtual sector. This guarantees unique initialization
> > vectors for all sectors when qcow2 internal snapshots are
> > used, thus giving stronger protection against watermarking
> > attacks.
> >
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> > block/qcow2-cluster.c | 4 +-
> > block/qcow2-refcount.c | 10 ++
> > block/qcow2.c | 236 ++++++++++++++++++++++++++++++++++++++-------
> > block/qcow2.h | 9 ++
> > docs/specs/qcow2.txt | 99 +++++++++++++++++++
>
> I'd personally rather have specification changes separate from the
> implementation.
>
> > qapi/block-core.json | 6 +-
> > tests/qemu-iotests/049 | 2 +-
> > tests/qemu-iotests/082.out | 216 +++++++++++++++++++++++++++++++++++++++++
> > tests/qemu-iotests/087 | 65 ++++++++++++-
> > tests/qemu-iotests/087.out | 22 ++++-
> > tests/qemu-iotests/134 | 4 +-
> > tests/qemu-iotests/158 | 8 +-
> > tests/qemu-iotests/174 | 76 +++++++++++++++
> > tests/qemu-iotests/174.out | 19 ++++
> > tests/qemu-iotests/175 | 85 ++++++++++++++++
> > tests/qemu-iotests/175.out | 26 +++++
> > tests/qemu-iotests/group | 2 +
> > 17 files changed, 840 insertions(+), 49 deletions(-)
> > create mode 100755 tests/qemu-iotests/174
> > create mode 100644 tests/qemu-iotests/174.out
> > create mode 100755 tests/qemu-iotests/175
> > create mode 100644 tests/qemu-iotests/175.out
>
> Also, in order to help reviewing by making this patch less scary (840
> insertions are kind of... Urgh.), it would make sense to add these two
> tests in one or two separate patches.
Ok, will split it in three - spec change, code change and test
additions.
> > @@ -80,6 +81,73 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
>
> [...]
>
> > +static ssize_t qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen,
> > + Error **errp, void *opaque)
> > +{
> > + BlockDriverState *bs = opaque;
> > + BDRVQcow2State *s = bs->opaque;
> > + int64_t ret;
> > +
> > + ret = qcow2_alloc_clusters(bs, headerlen);
> > + if (ret < 0) {
> > + error_setg_errno(errp, -ret,
> > + "Cannot allocate cluster for LUKS header size %zu",
> > + headerlen);
> > + return -1;
> > + }
> > +
> > + s->crypto_header.length = headerlen;
> > + s->crypto_header.offset = ret;
>
> The specification says any unused space in the last cluster has to be
> set to zero. This isn't done here.
>
> I don't have a preference whether you write zeroes to the range in
> question here or whether you simply relax the specification to say that
> anything beyond crypto_header.length is undefined.
I'll explicitly fill it with zeros.
> > +static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
> > + const uint8_t *buf, size_t buflen,
> > + Error **errp, void *opaque)
> > +{
> > + BlockDriverState *bs = opaque;
> > + BDRVQcow2State *s = bs->opaque;
> > + ssize_t ret;
> > +
> > + if ((offset + buflen) > s->crypto_header.length) {
> > + error_setg(errp, "Request for data outside of extension header");
> > + return -1;
> > + }
> > +
> > + ret = bdrv_pwrite(bs->file,
> > + s->crypto_header.offset + offset, buf, buflen);
>
> Theoretically, there should be a qcow2_pre_write_overlap_check() here.
> But in theory, qcow2_check_metadata_overlap() should also check overlaps
> with this new block of metadata.
>
> I'll leave it up to you as the discussion about the future of those
> overlap checks is still up in the air. I really don't have a preference
> on what to do in this patch.
Ok, i'll leave as is for now.
> > @@ -165,6 +233,45 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
> > }
> > break;
> >
> > + case QCOW2_EXT_MAGIC_CRYPTO_HEADER: {
> > + unsigned int cflags = 0;
> > + if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
> > + error_setg(errp, "CRYPTO header extension only "
> > + "expected with LUKS encryption method");
> > + return -EINVAL;
> > + }
> > + if (ext.len != sizeof(Qcow2CryptoHeaderExtension)) {
> > + error_setg(errp, "CRYPTO header extension size %u, "
> > + "but expected size %zu", ext.len,
> > + sizeof(Qcow2CryptoHeaderExtension));
> > + return -EINVAL;
> > + }
> > +
> > + ret = bdrv_pread(bs->file, offset, &s->crypto_header, ext.len);
> > + if (ret < 0) {
> > + error_setg_errno(errp, -ret,
> > + "Unable to read CRYPTO header extension");
> > + return ret;
> > + }
> > + be64_to_cpu(s->crypto_header.offset);
> > + be64_to_cpu(s->crypto_header.length);
>
> Shouldn't you use the result somehow (or use be64_to_cpus)...?
Sigh, yes, intention was to modify in-place
> Also, you should check the validity of s->crypto_header.offset here,
> i.e. whether it is indeed aligned to a cluster boundary.
Ok, wil add that.
>
> > +
> > + if (flags & BDRV_O_NO_IO) {
> > + cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
> > + }
> > + /* XXX how do we pass the same crypto opts down to the
>
> Just as in the previous patch, a TODO would probably be sufficient here.
Yes
> > + * backing file by default, so we don't have to manually
> > + * provide the same key-secret property against the full
> > + * backing chain
> > + */
> > + s->crypto = qcrypto_block_open(s->crypto_opts,
> > + qcow2_crypto_hdr_read_func,
> > + bs, cflags, errp);
> > + if (!s->crypto) {
> > + return -EINVAL;
> > + }
> > + s->crypt_physical_offset = true;
>
> I think this should be set depending on the crypt_method_header (i.e. in
> qcow2_open()), not depending on whether this extension exists or not.
Yes, makes sense.
> > @@ -988,12 +1101,6 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
> > s->refcount_max = UINT64_C(1) << (s->refcount_bits - 1);
> > s->refcount_max += s->refcount_max - 1;
> >
> > - if (header.crypt_method > QCOW_CRYPT_AES) {
> > - error_setg(errp, "Unsupported encryption method: %" PRIu32,
> > - header.crypt_method);
> > - ret = -EINVAL;
> > - goto fail;
> > - }
> > s->crypt_method_header = header.crypt_method;
> > if (s->crypt_method_header) {
> > if (bdrv_uses_whitelist() &&
>
> You could put the assignment of crypt_physical_offset into this block
> (at its bottom where bs->encrypted is set).
Yep will do.
> > @@ -1929,6 +2053,22 @@ int qcow2_update_header(BlockDriverState *bs)
> > buflen -= ret;
> > }
> >
> > + /* Full disk encryption header pointer extension */
> > + if (s->crypto_header.offset != 0) {
> > + cpu_to_be64(s->crypto_header.offset);
> > + cpu_to_be64(s->crypto_header.length);
>
> Should be cpu_to_be64s() or you should store the result somewhere.
>
> > + ret = header_ext_add(buf, QCOW2_EXT_MAGIC_CRYPTO_HEADER,
> > + &s->crypto_header, sizeof(s->crypto_header),
> > + buflen);
> > + be64_to_cpu(s->crypto_header.offset);
> > + be64_to_cpu(s->crypto_header.length);
>
> The same with be64_to_cpus().
Yes intention was obviously to modify in-place.
> > @@ -3426,7 +3582,19 @@ static QemuOptsList qcow2_create_opts = {
> > .help = "Width of a reference count entry in bits",
> > .def_value_str = "16"
> > },
> > + {
> > + .name = QCOW2_OPT_ENCRYPTION_FORMAT,
> > + .type = QEMU_OPT_STRING,
> > + .help = "Encryption data format 'luks' (default) or 'aes'",
>
> In what way is LUKS the default? No encryption is the default; and the
> default encryption is the old AES-CBC because that is what you get when
> specifying encryption=on.
This is left-over language from a previous approach.
> I would agree if you replaced "default" by "recommended". In addition,
> the help text for the "encryption" option should probably now simply say:
>
> "Deprecated, use the " QCOW2_OPT_ENCRYPTION_FORMAT " option instead"
Yes, will do that.
> > diff --git a/tests/qemu-iotests/087 b/tests/qemu-iotests/087
> > index fe30383..6690715 100755
> > --- a/tests/qemu-iotests/087
> > +++ b/tests/qemu-iotests/087
>
> [...]
>
> > @@ -169,7 +169,62 @@ run_qemu <<EOF
> > "driver": "file",
> > "filename": "$TEST_IMG"
> > },
> > - "qcow-key-secret": "sec0"
> > + "aes-key-secret": "sec0"
> > + }
> > + }
> > +{ "execute": "quit" }
> > +EOF
> > +
> > +echo
> > +echo === Encrypted image LUKS ===
> > +echo
> > +
> > +_make_test_img --object secret,id=sec0,data=123456 -o encryption-format=luks,luks-key-secret=sec0 $size
> > +run_qemu -S <<EOF
> > +{ "execute": "qmp_capabilities" }
> > +{ "execute": "object-add",
> > + "arguments": {
> > + "qom-type": "secret",
> > + "id": "sec0",
> > + "props": {
> > + "data": "123456"
> > + }
> > + }
> > +}
> > +{ "execute": "blockdev-add",
> > + "arguments": {
> > + "driver": "$IMGFMT",
> > + "node-name": "disk",
> > + "file": {
> > + "driver": "file",
> > + "filename": "$TEST_IMG"
> > + },
> > + "luks-key-secret": "sec0"
> > + }
> > + }
> > +{ "execute": "quit" }
> > +EOF
> > +
> > +run_qemu <<EOF
>
> I think we can drop one of the duplicated test cases with and without -S
> now. The reason for having two originally was, as far as I can remember,
> to test that you could blockdev-add encrypted images when the VM was not
> paused. However, that is no longer the case since we are no longer
> relying on that old infrastructure (as of this series at least).
Yes, and in fact I can drop the existing one for qcow2 AES in the
earlier patch where I remove prompting for passwords.
> > diff --git a/tests/qemu-iotests/174 b/tests/qemu-iotests/174
> > new file mode 100755
> > index 0000000..27d4870
> > --- /dev/null
> > +++ b/tests/qemu-iotests/174
> > +_supported_fmt qcow2
> > +_supported_proto generic
> > +_supported_os Linux
> > +
> > +
> > +size=128M
> > +
> > +SECRET="secret,id=sec0,data=astrochicken"
> > +SECRETALT="secret,id=sec0,data=platypus"
> > +
> > +_make_test_img --object $SECRET -o "encryption-format=luks,luks-key-secret=sec0" $size
> > +
> > +IMGSPEC="driver=$IMGFMT,file.filename=$TEST_IMG,luks-key-secret=sec0"
> > +
> > +QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
> > +
> > +echo
> > +echo "== reading whole image =="
> > +$QEMU_IO --object $SECRET -c "read 0 $size" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir
>
> Shouldn't "read -P 0 0 $size" work here, too?
The underlying disk image contents will be zeros, but we'll then decrypt
those zeros and get random garbage.
We could only use -P 0 if we explicitly fill with encrypted-zeros.
> > +echo
> > +echo "== rewriting whole image =="
> > +$QEMU_IO --object $SECRET -c "write -P 0xa 0 $size" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir
> > +
> > +echo
> > +echo "== verify pattern =="
> > +$QEMU_IO --object $SECRET -c "read -P 0xa 0 $size" --image-opts $IMGSPEC | _filter_qemu_io | _filter_testdir
>
> But do you really want to read and write 128 MB...? I mean, even on an
> HDD, it will only take a couple of seconds at most, but won't 16 or 32
> MB suffice? That way, it should always take less than a second.
I'll drop it to 16 MB, but this reminds me the real performance
benefit comes from telling luks to not force 1 second running
time for the PBKDF algorithm. So I'll drop PBKDK down to 10ms
instead of 1sec
> > +== verify pattern failure with wrong password ==
> > +can't open: Invalid password, cannot unlock any keyslot
>
> Well, that's not quite a pattern failure. It's the result we want, but
> you should probably change the heading for this test case.
Good point.
> > +# get standard environment, filters and checks
> > +. ./common.rc
> > +. ./common.filter
> > +
> > +_supported_fmt qcow2
> > +_supported_proto generic
> > +_supported_os Linux
> > +
> > +
> > +size=128M
>
> Same as in last test, I'm not sure 128 MB are actually necessary.
>
> > +TEST_IMG_BASE=$TEST_IMG.base
>
> Hmmm, does this work with spaces in $TEST_IMG?
>
> > +SECRET="secret,id=sec0,data=astrochicken"
>
> How about using different secrets for the base and the overlay?
Yes, that's a good idea.
Regards,
Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://entangle-photo.org -o- http://search.cpan.org/~danberr/ :|
next prev parent reply other threads:[~2017-01-24 13:59 UTC|newest]
Thread overview: 47+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-01-03 18:27 [Qemu-devel] [PATCH v1 00/15] Convert QCow[2] to QCryptoBlock & add LUKS support Daniel P. Berrange
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 01/15] block: expose crypto option names / defs to other drivers Daniel P. Berrange
2017-01-03 19:46 ` Eric Blake
2017-01-16 19:42 ` Max Reitz
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 02/15] block: add ability to set a prefix for opt names Daniel P. Berrange
2017-01-16 19:31 ` Max Reitz
2017-01-24 12:15 ` Daniel P. Berrange
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 03/15] qcow: document another weakness of qcow AES encryption Daniel P. Berrange
2017-01-16 19:37 ` Max Reitz
2017-01-24 12:11 ` Daniel P. Berrange
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 04/15] qcow: require image size to be > 1 for new images Daniel P. Berrange
2017-01-16 19:41 ` Max Reitz
2017-01-24 12:14 ` Daniel P. Berrange
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 05/15] iotests: skip 042 with qcow which dosn't support zero sized images Daniel P. Berrange
2017-01-16 19:42 ` Max Reitz
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 06/15] iotests: skip 048 with qcow which doesn't support resize Daniel P. Berrange
2017-01-16 19:48 ` Max Reitz
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 07/15] iotests: fix 097 when run with qcow Daniel P. Berrange
2017-01-16 20:04 ` Max Reitz
2017-01-17 9:59 ` Daniel P. Berrange
2017-01-18 12:44 ` Max Reitz
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 08/15] qcow: make encrypt_sectors encrypt in place Daniel P. Berrange
2017-01-16 20:25 ` Max Reitz
2017-01-24 12:21 ` Daniel P. Berrange
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 09/15] qcow: convert QCow to use QCryptoBlock for encryption Daniel P. Berrange
2017-01-16 21:16 ` Max Reitz
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 10/15] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 11/15] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
2017-01-18 18:13 ` Max Reitz
2017-01-19 9:39 ` Daniel P. Berrange
2017-01-21 19:07 ` Max Reitz
2017-01-24 12:33 ` Daniel P. Berrange
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 12/15] qcow2: add support for LUKS encryption format Daniel P. Berrange
2017-01-21 18:57 ` Max Reitz
2017-01-24 13:58 ` Daniel P. Berrange [this message]
2017-01-25 15:45 ` Max Reitz
2017-01-03 18:27 ` [Qemu-devel] [PATCH v1 13/15] iotests: enable tests 134 and 158 to work with qcow (v1) Daniel P. Berrange
2017-01-21 19:12 ` Max Reitz
2017-01-03 18:28 ` [Qemu-devel] [PATCH v1 14/15] block: rip out all traces of password prompting Daniel P. Berrange
2017-01-21 19:17 ` Max Reitz
2017-01-03 18:28 ` [Qemu-devel] [PATCH v1 15/15] block: remove all encryption handling APIs Daniel P. Berrange
2017-01-21 19:22 ` Max Reitz
2017-01-24 12:49 ` Daniel P. Berrange
2017-01-25 15:58 ` [Qemu-devel] [PATCH v1 00/15] Convert QCow[2] to QCryptoBlock & add LUKS support Max Reitz
2017-01-25 16:29 ` Daniel P. Berrange
2017-01-25 16:41 ` Max Reitz
2017-01-25 17:18 ` Daniel P. Berrange
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20170124135848.GA28745@redhat.com \
--to=berrange@redhat.com \
--cc=kwolf@redhat.com \
--cc=mreitz@redhat.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.