qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
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/ :|

  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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).