All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Daniel P. Berrange" <berrange@redhat.com>
To: Kevin Wolf <kwolf@redhat.com>
Cc: qemu-devel@nongnu.org, qemu-block@nongnu.org
Subject: Re: [Qemu-devel] [Qemu-block] [PATCH v1 10/15] qcow2: convert QCow2 to use QCryptoBlock for encryption
Date: Thu, 14 Jan 2016 12:14:56 +0000	[thread overview]
Message-ID: <20160114121455.GL910@redhat.com> (raw)
In-Reply-To: <20160113184220.GD4356@noname.redhat.com>

On Wed, Jan 13, 2016 at 07:42:20PM +0100, Kevin Wolf wrote:
> Am 12.01.2016 um 19:56 hat Daniel P. Berrange geschrieben:
> > This converts the qcow2 driver to make use of the QCryptoBlock
> > APIs for encrypting image content. As well as continued support
> > for the legacy QCow2 encryption format, the appealing benefit
> > is that it enables support for the LUKS format inside qcow2.
> > 
> > With the LUKS format it is neccessary 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 is 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.
> > 
> > With this change it is now required to use the QCryptoSecret
> > object for providing passwords, instead of the current block
> > password APIs / interactive prompting.
> > 
> >   $QEMU \
> >     -object secret,id=sec0,filename=/home/berrange/encrypted.pw \
> >     -drive file=/home/berrange/encrypted.qcow2,key-id=sec0
> > 
> > The new LUKS format is set as the new default format when
> > creating encrypted images. ie
> > 
> >   # qemu-img create --object secret,data=123456,id=sec0 \
> >        -f qcow2 -o encryption,key-id=sec0 \
> >        test.qcow2 10G
> > 
> > Results in creation of an image using the LUKS format.
> > 
> > For compatibility the old qcow2 AES format can still be used
> > via the 'encryption-format' parameter which accepts the
> > values 'luks' or 'qcowaes'.
> > 
> >   # qemu-img create --object secret,data=123456,id=sec0 \
> >        -f qcow2 -o encryption,key-id=sec0,encryption-format=qcowaes \
> >        test.qcow2 10G
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> 
> I think for your additional pointer to some clusters you need to change
> some function(s) called by qcow2_check_refcounts(). Otherwise 'qemu-img
> check' won't count the reference and helpfully free the "leaked"
> cluster.

Opps, yes, having those garbage collected would be a very bad
thing for your ability to decrypt your data :-)

> > @@ -60,6 +62,7 @@ typedef struct {
> >  #define  QCOW2_EXT_MAGIC_END 0
> >  #define  QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA
> >  #define  QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
> > +#define  QCOW2_EXT_MAGIC_FDE_HEADER 0x4c554b53
> 
> General naming comment on this series: I would prefer avoiding "FDE" in
> favour of "encryption" or "crypt" in the block layer parts. With all
> image formats having their own terminology, "FDE" could mean anything.

Ok, will rename this - wasn't too happy with FDE myself either.

> >  static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
> >  {
> > @@ -74,6 +77,83 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
> >  }
> >  
> >  
> > +static ssize_t qcow2_fde_header_read_func(QCryptoBlock *block,
> > +                                          size_t offset,
> > +                                          uint8_t *buf,
> > +                                          size_t buflen,
> > +                                          Error **errp,
> > +                                          void *opaque)
> > +{
> > +    BlockDriverState *bs = opaque;
> > +    BDRVQcow2State *s = bs->opaque;
> > +    ssize_t ret;
> > +
> > +    if ((offset + buflen) > s->fde_header.length) {
> > +        error_setg_errno(errp, EINVAL,
> > +                         "Request for data outside of extension header");
> 
> error_setg_errno() with a constant errno doesn't look very useful.
> Better use plain error_setg() in such cases.

I wasn't too sure - I figured since the block layer seems to
propagate errno's around alot, that I ought to report an
errno here, but will happiyl drop it.

> > +        return -1;
> 
> Here returning -EINVAL could be useful, I'm not sure what your crypto
> API requires. At least you seem to be returning -errno below and mixing
> -1 and -errno is probably a bad idea.

The crypto API doesn't deal with errno's at all - it uses the
Error object exclusively, so yeah, I can drop it from the
place below.

> 
> > +    }
> > +
> > +    ret = bdrv_pread(bs->file->bs,
> > +                     s->fde_header.offset + offset, buf, buflen);
> > +    if (ret < 0) {
> > +        error_setg_errno(errp, -ret, "Could not read encryption header");
> > +        return ret;
> > +    }
> > +    return ret;
> 
> return 0? You already processed ret in the if block and two 'return ret'
> in a row look odd.
> 
> > +}
> > +
> > +
> > +static ssize_t qcow2_fde_header_init_func(QCryptoBlock *block,
> > +                                          size_t headerlen,
> > +                                          Error **errp,
> > +                                          void *opaque)
> > +{
> > +    BlockDriverState *bs = opaque;
> > +    BDRVQcow2State *s = bs->opaque;
> > +    int64_t ret;
> > +
> > +    s->fde_header.length = headerlen + (headerlen % s->cluster_size);
> > +
> > +    ret = qcow2_alloc_clusters(bs, s->fde_header.length);
> > +    if (ret < 0) {
> > +        s->fde_header.length = 0;
> > +        error_setg(errp, "Cannot allocate cluster for LUKS header size %zu",
> > +                   headerlen);
> 
> I think ret is -errno on failure, so use error_setg_errno()?

Ok.

> 
> > +        return -1;
> > +    }
> > +
> > +    s->fde_header.offset = ret;
> > +    return 0;
> > +}
> > +
> > +
> > +static ssize_t qcow2_fde_header_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->fde_header.length) {
> > +        error_setg_errno(errp, EINVAL,
> > +                         "Request for data outside of extension header");
> 
> error_setg(). Probably worth checking all error paths whether there is a
> useful errno or not. I won't comment on additional instances.
> 
> > +        return -1;
> > +    }
> > +
> > +    ret = bdrv_pwrite(bs->file->bs, s->fde_header.offset + offset, buf, buflen);
> > +    if (ret < 0) {
> > +        error_setg_errno(errp, -ret, "Could not read encryption header");
> > +        return ret;
> > +    }
> > +    return ret;
> > +}
> 
> Mixing -1 and -errno again.

Will fix as per the read_func() above.

> > @@ -83,12 +163,14 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
> >   */
> >  static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
> >                                   uint64_t end_offset, void **p_feature_table,
> > +                                 int flags,
> >                                   Error **errp)
> >  {
> >      BDRVQcow2State *s = bs->opaque;
> >      QCowExtension ext;
> >      uint64_t offset;
> >      int ret;
> > +    unsigned int cflags = 0;
> 
> Should we create a block for QCOW2_EXT_MAGIC_FDE_HEADER and declare it
> there?

Yep, I can do that.

> >  #ifdef DEBUG_EXT
> >      printf("qcow2_read_extensions: start=%ld end=%ld\n", start_offset, end_offset);
> > @@ -159,6 +241,35 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
> >              }
> >              break;
> >  
> > +        case QCOW2_EXT_MAGIC_FDE_HEADER:
> > +            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
> > +                error_setg(errp, "FDE header extension only "
> > +                           "expected with LUKS encryption method");
> > +                return -EINVAL;
> > +            }
> > +            if (ext.len != sizeof(Qcow2FDEHeaderExtension)) {
> > +                error_setg(errp, "LUKS header extension size %u, "
> > +                           "but expected size %zu", ext.len,
> > +                           sizeof(Qcow2FDEHeaderExtension));
> > +                return -EINVAL;
> > +            }
> > +
> > +            ret = bdrv_pread(bs->file->bs, offset, &s->fde_header, ext.len);
> 
> No error check?
> 
> > +            be64_to_cpu(s->fde_header.offset);
> > +            be64_to_cpu(s->fde_header.length);
> > +
> > +            if (flags & BDRV_O_NO_IO) {
> > +                cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
> > +            }
> > +            s->fde = qcrypto_block_open(s->fde_opts,
> > +                                        qcow2_fde_header_read_func,
> > +                                        bs,
> > +                                        cflags,
> > +                                        errp);
> 
> The surrounding code doesn't put every parameter on its own line if the
> next parameter can fit on the same line. There are more instances of
> this in the patch, I won't comment on each one.

Ok, that's just my habit from libvirt - where we either put everything
on one line, or everything on a separate line and don't mix.



> > @@ -2213,12 +2517,15 @@ static int qcow2_create2(const char *filename, int64_t total_size,
> >      /*
> >       * And now open the image and make it consistent first (i.e. increase the
> >       * refcount of the cluster that is occupied by the header and the refcount
> > -     * table)
> > +     * table). Using BDRV_O_NO_IO since we've not written any encryption
> > +     * header yet and thus should not read/write payload data or initialize
> > +     * the decryption context
> >       */
> >      options = qdict_new();
> >      qdict_put(options, "driver", qstring_from_str("qcow2"));
> >      ret = bdrv_open(&bs, filename, NULL, options,
> > -                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH,
> > +                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH |
> > +                    BDRV_O_NO_IO,
> >                      &local_err);
> 
> Somehow I feel that saying BDRV_O_NO_IO, but doing I/O will bite us
> sooner or later.

Indeed, once I add the assertions you suggested in the previous
patch this will probably break horribly.

> Why not leave header->crypt_method = 0 initially and only set up
> encryption after opening the image with the qcow2 driver?

Oh yes, good idea.


> > diff --git a/block/qcow2.h b/block/qcow2.h
> > index ae04285..d33afb2 100644
> > --- a/block/qcow2.h
> > +++ b/block/qcow2.h
> > @@ -25,7 +25,7 @@
> >  #ifndef BLOCK_QCOW2_H
> >  #define BLOCK_QCOW2_H
> >  
> > -#include "crypto/cipher.h"
> > +#include "crypto/block.h"
> >  #include "qemu/coroutine.h"
> >  
> >  //#define DEBUG_ALLOC
> > @@ -36,6 +36,7 @@
> >  
> >  #define QCOW_CRYPT_NONE 0
> >  #define QCOW_CRYPT_AES  1
> > +#define QCOW_CRYPT_LUKS 2
> >  
> >  #define QCOW_MAX_CRYPT_CLUSTERS 32
> >  #define QCOW_MAX_SNAPSHOTS 65536
> > @@ -98,6 +99,15 @@
> >  #define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"
> >  #define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval"
> >  
> > +#define QCOW2_OPT_ENC_FORMAT "encryption-format"
> > +#define QCOW2_OPT_KEY_ID "key-id"
> > +#define QCOW2_OPT_CIPHER_ALG "cipher-alg"
> > +#define QCOW2_OPT_CIPHER_MODE "cipher-mode"
> > +#define QCOW2_OPT_IVGEN_ALG "ivgen-alg"
> > +#define QCOW2_OPT_IVGEN_HASH_ALG "ivgen-hash-alg"
> > +#define QCOW2_OPT_HASH_ALG "hash-alg"
> > +
> > +
> >  typedef struct QCowHeader {
> >      uint32_t magic;
> >      uint32_t version;
> > @@ -163,6 +173,11 @@ typedef struct QCowSnapshot {
> >  struct Qcow2Cache;
> >  typedef struct Qcow2Cache Qcow2Cache;
> >  
> > +typedef struct Qcow2FDEHeaderExtension {
> > +    uint64_t offset;
> > +    uint64_t length;
> > +} Qcow2FDEHeaderExtension;
> 
> Packed? It seems to be read directly from the file.

Yes


> > @@ -166,6 +168,75 @@ the header extension data. Each entry look like this:
> >                      terminated if it has full length)
> >  
> >  
> > +== Full disk encryption (FDE) header pointer ==
> > +
> > +For 'crypt_method' header values which require additional header metadata
> > +to be stored (eg 'LUKS' header), the full disk encryption header pointer
> > +extension is mandatory.
> 
> I think you want to make that "is present if, and only if, the
> 'crypt_method' header value requires metadata".
> 
> At least, we need to forbid it for the existing AES images, because old
> qemu-img version would stll fix the "leaked clusters", without removing
> the header extension that refers to them.

Yes, we shouldn't be using the header with the AES crypt method,
only the LUKS method for now.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

  reply	other threads:[~2016-01-14 12:15 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-01-12 18:56 [Qemu-devel] [PATCH v1 00/15] Support LUKS encryption in block devices Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 01/15] crypto: add cryptographic random byte source Daniel P. Berrange
2016-01-13  2:46   ` Fam Zheng
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 02/15] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
2016-01-13  5:53   ` Fam Zheng
2016-01-14 12:17     ` Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 03/15] crypto: add support for generating initialization vectors Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 04/15] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 05/15] crypto: add block encryption framework Daniel P. Berrange
2016-01-13 23:40   ` Eric Blake
2016-01-14 12:16     ` Daniel P. Berrange
2016-01-18 19:48       ` Eric Blake
2016-01-19  9:41         ` Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 06/15] crypto: implement the LUKS block encryption format Daniel P. Berrange
2016-01-13 23:43   ` Eric Blake
2016-01-14 10:14     ` Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 07/15] block: add flag to indicate that no I/O will be performed Daniel P. Berrange
2016-01-13 17:44   ` [Qemu-devel] [Qemu-block] " Kevin Wolf
2016-01-13 17:56     ` Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 08/15] block: add generic full disk encryption driver Daniel P. Berrange
2016-01-13 23:47   ` Eric Blake
2016-01-14 10:15     ` Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 09/15] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 10/15] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
2016-01-13 18:42   ` [Qemu-devel] [Qemu-block] " Kevin Wolf
2016-01-14 12:14     ` Daniel P. Berrange [this message]
2016-01-14 12:58       ` Kevin Wolf
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 11/15] qcow: make encrypt_sectors encrypt in place Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 12/15] qcow: convert QCow to use QCryptoBlock for encryption Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 13/15] block: rip out all traces of password prompting Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 14/15] block: remove all encryption handling APIs Daniel P. Berrange
2016-01-12 18:56 ` [Qemu-devel] [PATCH v1 15/15] block: remove support for legecy AES qcow/qcow2 encryption 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=20160114121455.GL910@redhat.com \
    --to=berrange@redhat.com \
    --cc=kwolf@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.