diff mbox series

[v2,1/3] block/qcow2: refactoring of threaded encryption code

Message ID 20190906195750.17651-2-mlevitsk@redhat.com
State New
Headers show
Series Fix qcow2+luks corruption introduced by commit 8ac0f15f335 | expand

Commit Message

Maxim Levitsky Sept. 6, 2019, 7:57 p.m. UTC
This commit tries to clarify few function arguments,
and add comments describing the encrypt/decrypt interface

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/qcow2-cluster.c | 10 +++----
 block/qcow2-threads.c | 61 ++++++++++++++++++++++++++++++++++---------
 2 files changed, 53 insertions(+), 18 deletions(-)

Comments

Vladimir Sementsov-Ogievskiy Sept. 7, 2019, 7:08 p.m. UTC | #1
06.09.2019 22:57, Maxim Levitsky wrote:
> This commit tries to clarify few function arguments,
> and add comments describing the encrypt/decrypt interface
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>   block/qcow2-cluster.c | 10 +++----
>   block/qcow2-threads.c | 61 ++++++++++++++++++++++++++++++++++---------
>   2 files changed, 53 insertions(+), 18 deletions(-)
> 
> diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
> index f09cc992af..1989b423da 100644
> --- a/block/qcow2-cluster.c
> +++ b/block/qcow2-cluster.c
> @@ -463,8 +463,8 @@ static int coroutine_fn do_perform_cow_read(BlockDriverState *bs,
>   }
>   
>   static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
> -                                                uint64_t src_cluster_offset,
> -                                                uint64_t cluster_offset,
> +                                                uint64_t guest_cluster_offset,
> +                                                uint64_t host_cluster_offset,
>                                                   unsigned offset_in_cluster,
>                                                   uint8_t *buffer,
>                                                   unsigned bytes)
> @@ -474,8 +474,8 @@ static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
>           assert((offset_in_cluster & ~BDRV_SECTOR_MASK) == 0);
>           assert((bytes & ~BDRV_SECTOR_MASK) == 0);
>           assert(s->crypto);
> -        if (qcow2_co_encrypt(bs, cluster_offset,
> -                             src_cluster_offset + offset_in_cluster,
> +        if (qcow2_co_encrypt(bs, host_cluster_offset,
> +                             guest_cluster_offset + offset_in_cluster,
>                                buffer, bytes) < 0) {
>               return false;
>           }
> @@ -496,7 +496,7 @@ static int coroutine_fn do_perform_cow_write(BlockDriverState *bs,
>       }
>   
>       ret = qcow2_pre_write_overlap_check(bs, 0,
> -            cluster_offset + offset_in_cluster, qiov->size, true);
> +              cluster_offset + offset_in_cluster, qiov->size, true);


Hmm, unrelated hunk.

>       if (ret < 0) {
>           return ret;
>       }
> diff --git a/block/qcow2-threads.c b/block/qcow2-threads.c
> index 3b1e63fe41..c3cda0c6a5 100644
> --- a/block/qcow2-threads.c
> +++ b/block/qcow2-threads.c
> @@ -234,15 +234,19 @@ static int qcow2_encdec_pool_func(void *opaque)
>   }
>   
>   static int coroutine_fn
> -qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
> -                  uint64_t offset, void *buf, size_t len, Qcow2EncDecFunc func)
> +qcow2_co_encdec(BlockDriverState *bs, uint64_t host_cluster_offset,
> +                uint64_t guest_offset, void *buf, size_t len,
> +                Qcow2EncDecFunc func)
>   {
>       BDRVQcow2State *s = bs->opaque;
> +
> +    uint64_t offset = s->crypt_physical_offset ?
> +        host_cluster_offset + offset_into_cluster(s, guest_offset) :
> +        guest_offset;
> +
>       Qcow2EncDecData arg = {
>           .block = s->crypto,
> -        .offset = s->crypt_physical_offset ?
> -                      file_cluster_offset + offset_into_cluster(s, offset) :
> -                      offset,
> +        .offset = offset,
>           .buf = buf,
>           .len = len,
>           .func = func,
> @@ -251,18 +255,49 @@ qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
>       return qcow2_co_process(bs, qcow2_encdec_pool_func, &arg);
>   }
>   
> +
> +/*
> + * qcow2_co_encrypt()
> + *
> + * Encrypts one or more contiguous aligned sectors
> + *
> + * @host_cluster_offset - on disk offset of the first cluster in which
> + * the encrypted data will be written


It's not quite right, it's not on disk, but on .file child of qcow2 node, which
may be any other format or protocol node.. So, I called it file_cluster_offset.
But I'm OK with new naming anyway. And it may be better for encryption related
logic..

 > + * Used as an initialization vector for encryption

Hmm, is it default now?

> + *
> + * @guest_offset - guest (virtual) offset of the first sector of the
> + * data to be encrypted

Hmm, stop. It's wrong. Data to be encrypted is in buffer, so, it's not first sector of
the data to be encrypted, but first sector in which guest writes data (to be encrypted
in meantime).

> + * Used as an initialization vector for older, qcow2 native encryption
> + *
> + * @buf - buffer with the data to encrypt
> + * @len - length of the buffer (in sector size multiplies)
> + *
> + * Note that the group of the sectors, don't have to be aligned
> + * on cluster boundary and can also cross a cluster boundary.

And I doubt in it now. I'm afraid that if we call qcow2_co_encrypt for a group
of the sectors crossing a cluster boundary, we will finish up with similar bug: we'll
use first cluster offset as a vector for all the sectors. We still never do it.. So,
I think it worth assertion and corresponding comment.

Or is it correct?

> + *
> + *
> + */
>   int coroutine_fn
> -qcow2_co_encrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
> -                 uint64_t offset, void *buf, size_t len)
> +qcow2_co_encrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
> +                 uint64_t guest_offset, void *buf, size_t len)
>   {
> -    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
> -                             qcrypto_block_encrypt);
> +    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
> +                           qcrypto_block_encrypt);
>   }
>   
> +
> +/*
> + * qcow2_co_decrypt()
> + *
> + * Decrypts one or more contiguous aligned sectors
> + * Same function as qcow2_co_encrypt

Hmm, not exactly same :)

> + *
> + */
> +
>   int coroutine_fn
> -qcow2_co_decrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
> -                 uint64_t offset, void *buf, size_t len)
> +qcow2_co_decrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
> +                 uint64_t guest_offset, void *buf, size_t len)
>   {
> -    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
> -                             qcrypto_block_decrypt);
> +    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
> +                           qcrypto_block_decrypt);
>   }
>
Maxim Levitsky Sept. 10, 2019, 12:31 p.m. UTC | #2
On Sat, 2019-09-07 at 19:08 +0000, Vladimir Sementsov-Ogievskiy wrote:
> 06.09.2019 22:57, Maxim Levitsky wrote:
> > This commit tries to clarify few function arguments,
> > and add comments describing the encrypt/decrypt interface
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >   block/qcow2-cluster.c | 10 +++----
> >   block/qcow2-threads.c | 61 ++++++++++++++++++++++++++++++++++---------
> >   2 files changed, 53 insertions(+), 18 deletions(-)
> > 
> > diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
> > index f09cc992af..1989b423da 100644
> > --- a/block/qcow2-cluster.c
> > +++ b/block/qcow2-cluster.c
> > @@ -463,8 +463,8 @@ static int coroutine_fn do_perform_cow_read(BlockDriverState *bs,
> >   }
> >   
> >   static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
> > -                                                uint64_t src_cluster_offset,
> > -                                                uint64_t cluster_offset,
> > +                                                uint64_t guest_cluster_offset,
> > +                                                uint64_t host_cluster_offset,
> >                                                   unsigned offset_in_cluster,
> >                                                   uint8_t *buffer,
> >                                                   unsigned bytes)
> > @@ -474,8 +474,8 @@ static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
> >           assert((offset_in_cluster & ~BDRV_SECTOR_MASK) == 0);
> >           assert((bytes & ~BDRV_SECTOR_MASK) == 0);
> >           assert(s->crypto);
> > -        if (qcow2_co_encrypt(bs, cluster_offset,
> > -                             src_cluster_offset + offset_in_cluster,
> > +        if (qcow2_co_encrypt(bs, host_cluster_offset,
> > +                             guest_cluster_offset + offset_in_cluster,
> >                                buffer, bytes) < 0) {
> >               return false;
> >           }
> > @@ -496,7 +496,7 @@ static int coroutine_fn do_perform_cow_write(BlockDriverState *bs,
> >       }
> >   
> >       ret = qcow2_pre_write_overlap_check(bs, 0,
> > -            cluster_offset + offset_in_cluster, qiov->size, true);
> > +              cluster_offset + offset_in_cluster, qiov->size, true);
> 
> 
> Hmm, unrelated hunk.

I was asked to do this to fix coding style, so that wrapped line,
is 4 characters shifted to the right.

> 
> >       if (ret < 0) {
> >           return ret;
> >       }
> > diff --git a/block/qcow2-threads.c b/block/qcow2-threads.c
> > index 3b1e63fe41..c3cda0c6a5 100644
> > --- a/block/qcow2-threads.c
> > +++ b/block/qcow2-threads.c
> > @@ -234,15 +234,19 @@ static int qcow2_encdec_pool_func(void *opaque)
> >   }
> >   
> >   static int coroutine_fn
> > -qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
> > -                  uint64_t offset, void *buf, size_t len, Qcow2EncDecFunc func)
> > +qcow2_co_encdec(BlockDriverState *bs, uint64_t host_cluster_offset,
> > +                uint64_t guest_offset, void *buf, size_t len,
> > +                Qcow2EncDecFunc func)
> >   {
> >       BDRVQcow2State *s = bs->opaque;
> > +
> > +    uint64_t offset = s->crypt_physical_offset ?
> > +        host_cluster_offset + offset_into_cluster(s, guest_offset) :
> > +        guest_offset;
> > +
> >       Qcow2EncDecData arg = {
> >           .block = s->crypto,
> > -        .offset = s->crypt_physical_offset ?
> > -                      file_cluster_offset + offset_into_cluster(s, offset) :
> > -                      offset,
> > +        .offset = offset,
> >           .buf = buf,
> >           .len = len,
> >           .func = func,
> > @@ -251,18 +255,49 @@ qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
> >       return qcow2_co_process(bs, qcow2_encdec_pool_func, &arg);
> >   }
> >   
> > +
> > +/*
> > + * qcow2_co_encrypt()
> > + *
> > + * Encrypts one or more contiguous aligned sectors
> > + *
> > + * @host_cluster_offset - on disk offset of the first cluster in which
> > + * the encrypted data will be written
> 
> 
> It's not quite right, it's not on disk, but on .file child of qcow2 node, which
> may be any other format or protocol node.. So, I called it file_cluster_offset.
> But I'm OK with new naming anyway. And it may be better for encryption related
> logic..

Yes, the .file is the underlying storage for both qcow2 metadata and the data,
and it is unlikely be another qcow2 file. Usually it will be a raw file,
accessed with some protocol.
I will change the wording to not include the 'disk' word though.


To be really honest, the best naming here would be one that follows the virtual memory concepts.
A virtual block/cluster address and a physical block/cluster address.
However we talked with Kevin recently and I also studied quite a lot of qcow2 code,
and the usual convention is guest cluster offset and host cluster offset,
and often guest offsets are just called offsets, which is very confusing IMHO.


> 
>  > + * Used as an initialization vector for encryption
> 
> Hmm, is it default now?


Most of block crypto implementations have IV which derive
some way or another from the sector address.

From what I see, the block address is either used as is,
or encrypted itself with same encryption key,
and the result is used as IV. Even the legacy qcow
encryption uses this, although it uses the virtual block
address, and apparently this is one of its security flaws.

If you don't use any IV, you end up with major security
hole - sectors of the same content will be encrypted
to the same cipertext.

I added this comment to clarify the usage of offset,
since other that this aspect of IV generation, 
the crypto routines only need the data to be encrypted and 
the encryption key which is stored in the crypto context.


> 
> > + *
> > + * @guest_offset - guest (virtual) offset of the first sector of the
> > + * data to be encrypted
> 
> Hmm, stop. It's wrong. Data to be encrypted is in buffer, so, it's not first sector of
> the data to be encrypted, but first sector in which guest writes data (to be encrypted
> in meantime).
No no no!

guest_offset is literally the guest's disk address of the first sector that is in the buffer.
The qcow2_co_encrypt is called from 2 places:

1. qcow2_co_pwritev_part - here indeed the actually guest written data
is encrypted, and the 'offset' is passed which is the offset on which pwritev was called


2. do_perform_cow_encrypt - here the just read data from before or after actually written guest
data is encrypted, and the guest_offset represents the address of that data.
I changed the do_perform_cow_encrypt, so that it receives from the caller the host and guest offset
of the data to be encrypted. It then aligns the host offset on start of the cluster, and passes
the guest offset as is, so that it does the same as qcow2_co_pwritev_part.


So what is wrong here?

>  
> 
> > + * Used as an initialization vector for older, qcow2 native encryption
> > + *
> > + * @buf - buffer with the data to encrypt
> > + * @len - length of the buffer (in sector size multiplies)
> > + *
> > + * Note that the group of the sectors, don't have to be aligned
> > + * on cluster boundary and can also cross a cluster boundary.
> 
> And I doubt in it now. I'm afraid that if we call qcow2_co_encrypt for a group
> of the sectors crossing a cluster boundary, we will finish up with similar bug: we'll
> use first cluster offset as a vector for all the sectors. We still never do it.. So,
> I think it worth assertion and corresponding comment.
> 
> Or is it correct?

Crypto code receives the data to be encrypted and decrypted,
and offset of first sector (512 bytes aligned) of that data (for IV calculation).
If the data spans multiple sectors (this happens already a lot) 
then it able to handle this since the code works.
The relevant code is in do_qcrypto_block_cipher_encdec, and
is actually generic for all the crypto modes.

So there should not be anything special about crossing the cluster
boundary, other that noting this here, just in case.

No only that code can cross a cluster boundary, but it can even be
called for more that one cluster at once. It looks like qcow2 code limits all
the IO in case crypto is used to QCOW_MAX_CRYPT_CLUSTERS, which is 32
currently. I don't know why to be honest.

Remember that crypto code has no notion of clusters. It works purely
on sector (512 bytes) level, and each sector will have its own IV calculated,
based on its sector address.

> 
> > + *
> > + *
> > + */
> >   int coroutine_fn
> > -qcow2_co_encrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
> > -                 uint64_t offset, void *buf, size_t len)
> > +qcow2_co_encrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
> > +                 uint64_t guest_offset, void *buf, size_t len)
> >   {
> > -    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
> > -                             qcrypto_block_encrypt);
> > +    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
> > +                           qcrypto_block_encrypt);
> >   }
> >   
> > +
> > +/*
> > + * qcow2_co_decrypt()
> > + *
> > + * Decrypts one or more contiguous aligned sectors
> > + * Same function as qcow2_co_encrypt
> 
> Hmm, not exactly same :)
I'll fix that.


> 
> > + *
> > + */
> > +
> >   int coroutine_fn
> > -qcow2_co_decrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
> > -                 uint64_t offset, void *buf, size_t len)
> > +qcow2_co_decrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
> > +                 uint64_t guest_offset, void *buf, size_t len)
> >   {
> > -    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
> > -                             qcrypto_block_decrypt);
> > +    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
> > +                           qcrypto_block_decrypt);
> >   }
> > 
> 


Best regards,
	Thanks for the review,
		Maxim Levitsky

> 
> -- 
> Best regards,
> Vladimir
Vladimir Sementsov-Ogievskiy Sept. 10, 2019, 2:17 p.m. UTC | #3
10.09.2019 15:31, Maxim Levitsky wrote:
> On Sat, 2019-09-07 at 19:08 +0000, Vladimir Sementsov-Ogievskiy wrote:
>> 06.09.2019 22:57, Maxim Levitsky wrote:
>>> This commit tries to clarify few function arguments,
>>> and add comments describing the encrypt/decrypt interface
>>>
>>> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
>>> ---
>>>    block/qcow2-cluster.c | 10 +++----
>>>    block/qcow2-threads.c | 61 ++++++++++++++++++++++++++++++++++---------
>>>    2 files changed, 53 insertions(+), 18 deletions(-)
>>>
>>> diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
>>> index f09cc992af..1989b423da 100644
>>> --- a/block/qcow2-cluster.c
>>> +++ b/block/qcow2-cluster.c
>>> @@ -463,8 +463,8 @@ static int coroutine_fn do_perform_cow_read(BlockDriverState *bs,
>>>    }
>>>    
>>>    static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
>>> -                                                uint64_t src_cluster_offset,
>>> -                                                uint64_t cluster_offset,
>>> +                                                uint64_t guest_cluster_offset,
>>> +                                                uint64_t host_cluster_offset,
>>>                                                    unsigned offset_in_cluster,
>>>                                                    uint8_t *buffer,
>>>                                                    unsigned bytes)
>>> @@ -474,8 +474,8 @@ static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
>>>            assert((offset_in_cluster & ~BDRV_SECTOR_MASK) == 0);
>>>            assert((bytes & ~BDRV_SECTOR_MASK) == 0);
>>>            assert(s->crypto);
>>> -        if (qcow2_co_encrypt(bs, cluster_offset,
>>> -                             src_cluster_offset + offset_in_cluster,
>>> +        if (qcow2_co_encrypt(bs, host_cluster_offset,
>>> +                             guest_cluster_offset + offset_in_cluster,
>>>                                 buffer, bytes) < 0) {
>>>                return false;
>>>            }
>>> @@ -496,7 +496,7 @@ static int coroutine_fn do_perform_cow_write(BlockDriverState *bs,
>>>        }
>>>    
>>>        ret = qcow2_pre_write_overlap_check(bs, 0,
>>> -            cluster_offset + offset_in_cluster, qiov->size, true);
>>> +              cluster_offset + offset_in_cluster, qiov->size, true);
>>
>>
>> Hmm, unrelated hunk.
> 
> I was asked to do this to fix coding style, so that wrapped line,
> is 4 characters shifted to the right.

AFAIS, Eric asked only about qcow2_co_encdec calls and definition.. It's OK to fix style in code
you touch in you patch anyway, but no reason to fix style somewhere else, it dirties patch for
no reason, making it more difficult (a bit, but still) to review and more difficult to backport..

> 
>>
>>>        if (ret < 0) {
>>>            return ret;
>>>        }
>>> diff --git a/block/qcow2-threads.c b/block/qcow2-threads.c
>>> index 3b1e63fe41..c3cda0c6a5 100644
>>> --- a/block/qcow2-threads.c
>>> +++ b/block/qcow2-threads.c
>>> @@ -234,15 +234,19 @@ static int qcow2_encdec_pool_func(void *opaque)
>>>    }
>>>    
>>>    static int coroutine_fn
>>> -qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
>>> -                  uint64_t offset, void *buf, size_t len, Qcow2EncDecFunc func)
>>> +qcow2_co_encdec(BlockDriverState *bs, uint64_t host_cluster_offset,
>>> +                uint64_t guest_offset, void *buf, size_t len,
>>> +                Qcow2EncDecFunc func)
>>>    {
>>>        BDRVQcow2State *s = bs->opaque;
>>> +
>>> +    uint64_t offset = s->crypt_physical_offset ?
>>> +        host_cluster_offset + offset_into_cluster(s, guest_offset) :
>>> +        guest_offset;
>>> +
>>>        Qcow2EncDecData arg = {
>>>            .block = s->crypto,
>>> -        .offset = s->crypt_physical_offset ?
>>> -                      file_cluster_offset + offset_into_cluster(s, offset) :
>>> -                      offset,
>>> +        .offset = offset,
>>>            .buf = buf,
>>>            .len = len,
>>>            .func = func,
>>> @@ -251,18 +255,49 @@ qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
>>>        return qcow2_co_process(bs, qcow2_encdec_pool_func, &arg);
>>>    }
>>>    
>>> +
>>> +/*
>>> + * qcow2_co_encrypt()
>>> + *
>>> + * Encrypts one or more contiguous aligned sectors
>>> + *
>>> + * @host_cluster_offset - on disk offset of the first cluster in which
>>> + * the encrypted data will be written
>>
>>
>> It's not quite right, it's not on disk, but on .file child of qcow2 node, which
>> may be any other format or protocol node.. So, I called it file_cluster_offset.
>> But I'm OK with new naming anyway. And it may be better for encryption related
>> logic..
> 
> Yes, the .file is the underlying storage for both qcow2 metadata and the data,
> and it is unlikely be another qcow2 file. Usually it will be a raw file,
> accessed with some protocol.
> I will change the wording to not include the 'disk' word though.
> 
> 
> To be really honest, the best naming here would be one that follows the virtual memory concepts.
> A virtual block/cluster address and a physical block/cluster address.
> However we talked with Kevin recently and I also studied quite a lot of qcow2 code,
> and the usual convention is guest cluster offset and host cluster offset,
> and often guest offsets are just called offsets, which is very confusing IMHO.

It don't confuse me, as it's an interface of block-layer. Interface user just writes at some offset.
And the fact that internally, we consider interface parameter "offset" as "guest offset" and map it
to some physical offset - it's an implementation details. In other words, guest don't know that it
writes at "guest offset", it just writes at "offset" and don't care.

> 
> 
>>
>>   > + * Used as an initialization vector for encryption
>>
>> Hmm, is it default now?
> 
> 
> Most of block crypto implementations have IV which derive
> some way or another from the sector address.
> 
>  From what I see, the block address is either used as is,
> or encrypted itself with same encryption key,
> and the result is used as IV. Even the legacy qcow
> encryption uses this, although it uses the virtual block
> address, and apparently this is one of its security flaws.
> 
> If you don't use any IV, you end up with major security
> hole - sectors of the same content will be encrypted
> to the same cipertext.
> 
> I added this comment to clarify the usage of offset,
> since other that this aspect of IV generation,
> the crypto routines only need the data to be encrypted and
> the encryption key which is stored in the crypto context.
> 
> 
>>
>>> + *
>>> + * @guest_offset - guest (virtual) offset of the first sector of the
>>> + * data to be encrypted
>>
>> Hmm, stop. It's wrong. Data to be encrypted is in buffer, so, it's not first sector of
>> the data to be encrypted, but first sector in which guest writes data (to be encrypted
>> in meantime).
> No no no!
> 
> guest_offset is literally the guest's disk address of the first sector that is in the buffer.
> The qcow2_co_encrypt is called from 2 places:
> 
> 1. qcow2_co_pwritev_part - here indeed the actually guest written data
> is encrypted, and the 'offset' is passed which is the offset on which pwritev was called

I mean, that in this case your comment for me sounds like "data to be encrypted is on disk, at this offset".
but it's not actually on disk, data is in buffer. And what is on disk we don't know, we are going to
write...

But I don't really care. Hope it's actually obvious for averyone, where is data on write path.

> 
> 
> 2. do_perform_cow_encrypt - here the just read data from before or after actually written guest
> data is encrypted, and the guest_offset represents the address of that data.
> I changed the do_perform_cow_encrypt, so that it receives from the caller the host and guest offset
> of the data to be encrypted. It then aligns the host offset on start of the cluster, and passes
> the guest offset as is, so that it does the same as qcow2_co_pwritev_part.
> 
> 
> So what is wrong here?
> 
>>   
>>
>>> + * Used as an initialization vector for older, qcow2 native encryption
>>> + *
>>> + * @buf - buffer with the data to encrypt
>>> + * @len - length of the buffer (in sector size multiplies)
>>> + *
>>> + * Note that the group of the sectors, don't have to be aligned
>>> + * on cluster boundary and can also cross a cluster boundary.
>>
>> And I doubt in it now. I'm afraid that if we call qcow2_co_encrypt for a group
>> of the sectors crossing a cluster boundary, we will finish up with similar bug: we'll
>> use first cluster offset as a vector for all the sectors. We still never do it.. So,
>> I think it worth assertion and corresponding comment.
>>
>> Or is it correct?
> 
> Crypto code receives the data to be encrypted and decrypted,
> and offset of first sector (512 bytes aligned) of that data (for IV calculation).
> If the data spans multiple sectors (this happens already a lot)

Ah, yes, that's OK, sorry.

> then it able to handle this since the code works.
> The relevant code is in do_qcrypto_block_cipher_encdec, and
> is actually generic for all the crypto modes.
> 
> So there should not be anything special about crossing the cluster
> boundary, other that noting this here, just in case.
> 
> No only that code can cross a cluster boundary, but it can even be
> called for more that one cluster at once. It looks like qcow2 code limits all
> the IO in case crypto is used to QCOW_MAX_CRYPT_CLUSTERS, which is 32
> currently. I don't know why to be honest.
> 
> Remember that crypto code has no notion of clusters. It works purely
> on sector (512 bytes) level, and each sector will have its own IV calculated,
> based on its sector address.
> 
>>
>>> + *
>>> + *
>>> + */
>>>    int coroutine_fn
>>> -qcow2_co_encrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
>>> -                 uint64_t offset, void *buf, size_t len)
>>> +qcow2_co_encrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
>>> +                 uint64_t guest_offset, void *buf, size_t len)
>>>    {
>>> -    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
>>> -                             qcrypto_block_encrypt);
>>> +    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
>>> +                           qcrypto_block_encrypt);
>>>    }
>>>    
>>> +
>>> +/*
>>> + * qcow2_co_decrypt()
>>> + *
>>> + * Decrypts one or more contiguous aligned sectors
>>> + * Same function as qcow2_co_encrypt
>>
>> Hmm, not exactly same :)
> I'll fix that.
> 
> 
>>
>>> + *
>>> + */
>>> +
>>>    int coroutine_fn
>>> -qcow2_co_decrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
>>> -                 uint64_t offset, void *buf, size_t len)
>>> +qcow2_co_decrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
>>> +                 uint64_t guest_offset, void *buf, size_t len)
>>>    {
>>> -    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
>>> -                             qcrypto_block_decrypt);
>>> +    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
>>> +                           qcrypto_block_decrypt);
>>>    }
>>>
>>
> 
> 
> Best regards,
> 	Thanks for the review,
> 		Maxim Levitsky
> 
>>
>> -- 
>> Best regards,
>> Vladimir
> 
>
Maxim Levitsky Sept. 10, 2019, 2:35 p.m. UTC | #4
On Tue, 2019-09-10 at 14:17 +0000, Vladimir Sementsov-Ogievskiy wrote:
> 10.09.2019 15:31, Maxim Levitsky wrote:
> > On Sat, 2019-09-07 at 19:08 +0000, Vladimir Sementsov-Ogievskiy wrote:
> > > 06.09.2019 22:57, Maxim Levitsky wrote:
> > > > This commit tries to clarify few function arguments,
> > > > and add comments describing the encrypt/decrypt interface
> > > > 
> > > > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > > > ---
> > > >    block/qcow2-cluster.c | 10 +++----
> > > >    block/qcow2-threads.c | 61 ++++++++++++++++++++++++++++++++++---------
> > > >    2 files changed, 53 insertions(+), 18 deletions(-)
> > > > 
> > > > diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
> > > > index f09cc992af..1989b423da 100644
> > > > --- a/block/qcow2-cluster.c
> > > > +++ b/block/qcow2-cluster.c
> > > > @@ -463,8 +463,8 @@ static int coroutine_fn do_perform_cow_read(BlockDriverState *bs,
> > > >    }
> > > >    
> > > >    static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
> > > > -                                                uint64_t src_cluster_offset,
> > > > -                                                uint64_t cluster_offset,
> > > > +                                                uint64_t guest_cluster_offset,
> > > > +                                                uint64_t host_cluster_offset,
> > > >                                                    unsigned offset_in_cluster,
> > > >                                                    uint8_t *buffer,
> > > >                                                    unsigned bytes)
> > > > @@ -474,8 +474,8 @@ static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
> > > >            assert((offset_in_cluster & ~BDRV_SECTOR_MASK) == 0);
> > > >            assert((bytes & ~BDRV_SECTOR_MASK) == 0);
> > > >            assert(s->crypto);
> > > > -        if (qcow2_co_encrypt(bs, cluster_offset,
> > > > -                             src_cluster_offset + offset_in_cluster,
> > > > +        if (qcow2_co_encrypt(bs, host_cluster_offset,
> > > > +                             guest_cluster_offset + offset_in_cluster,
> > > >                                 buffer, bytes) < 0) {
> > > >                return false;
> > > >            }
> > > > @@ -496,7 +496,7 @@ static int coroutine_fn do_perform_cow_write(BlockDriverState *bs,
> > > >        }
> > > >    
> > > >        ret = qcow2_pre_write_overlap_check(bs, 0,
> > > > -            cluster_offset + offset_in_cluster, qiov->size, true);
> > > > +              cluster_offset + offset_in_cluster, qiov->size, true);
> > > 
> > > 
> > > Hmm, unrelated hunk.
> > 
> > I was asked to do this to fix coding style, so that wrapped line,
> > is 4 characters shifted to the right.
> 
> AFAIS, Eric asked only about qcow2_co_encdec calls and definition.. It's OK to fix style in code
> you touch in you patch anyway, but no reason to fix style somewhere else, it dirties patch for
> no reason, making it more difficult (a bit, but still) to review and more difficult to backport..
All right, then I'll drop that change. I kind of agree with this, but I also didn't mind doing these fixes
either.

> 
> > 
> > > 
> > > >        if (ret < 0) {
> > > >            return ret;
> > > >        }
> > > > diff --git a/block/qcow2-threads.c b/block/qcow2-threads.c
> > > > index 3b1e63fe41..c3cda0c6a5 100644
> > > > --- a/block/qcow2-threads.c
> > > > +++ b/block/qcow2-threads.c
> > > > @@ -234,15 +234,19 @@ static int qcow2_encdec_pool_func(void *opaque)
> > > >    }
> > > >    
> > > >    static int coroutine_fn
> > > > -qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
> > > > -                  uint64_t offset, void *buf, size_t len, Qcow2EncDecFunc func)
> > > > +qcow2_co_encdec(BlockDriverState *bs, uint64_t host_cluster_offset,
> > > > +                uint64_t guest_offset, void *buf, size_t len,
> > > > +                Qcow2EncDecFunc func)
> > > >    {
> > > >        BDRVQcow2State *s = bs->opaque;
> > > > +
> > > > +    uint64_t offset = s->crypt_physical_offset ?
> > > > +        host_cluster_offset + offset_into_cluster(s, guest_offset) :
> > > > +        guest_offset;
> > > > +
> > > >        Qcow2EncDecData arg = {
> > > >            .block = s->crypto,
> > > > -        .offset = s->crypt_physical_offset ?
> > > > -                      file_cluster_offset + offset_into_cluster(s, offset) :
> > > > -                      offset,
> > > > +        .offset = offset,
> > > >            .buf = buf,
> > > >            .len = len,
> > > >            .func = func,
> > > > @@ -251,18 +255,49 @@ qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
> > > >        return qcow2_co_process(bs, qcow2_encdec_pool_func, &arg);
> > > >    }
> > > >    
> > > > +
> > > > +/*
> > > > + * qcow2_co_encrypt()
> > > > + *
> > > > + * Encrypts one or more contiguous aligned sectors
> > > > + *
> > > > + * @host_cluster_offset - on disk offset of the first cluster in which
> > > > + * the encrypted data will be written
> > > 
> > > 
> > > It's not quite right, it's not on disk, but on .file child of qcow2 node, which
> > > may be any other format or protocol node.. So, I called it file_cluster_offset.
> > > But I'm OK with new naming anyway. And it may be better for encryption related
> > > logic..
> > 
> > Yes, the .file is the underlying storage for both qcow2 metadata and the data,
> > and it is unlikely be another qcow2 file. Usually it will be a raw file,
> > accessed with some protocol.
> > I will change the wording to not include the 'disk' word though.
> > 
> > 
> > To be really honest, the best naming here would be one that follows the virtual memory concepts.
> > A virtual block/cluster address and a physical block/cluster address.
> > However we talked with Kevin recently and I also studied quite a lot of qcow2 code,
> > and the usual convention is guest cluster offset and host cluster offset,
> > and often guest offsets are just called offsets, which is very confusing IMHO.
> 
> It don't confuse me, as it's an interface of block-layer. Interface user just writes at some offset.
> And the fact that internally, we consider interface parameter "offset" as "guest offset" and map it
> to some physical offset - it's an implementation details. In other words, guest don't know that it
> writes at "guest offset", it just writes at "offset" and don't care.
> 
> > 
> > 
> > > 
> > >   > + * Used as an initialization vector for encryption
> > > 
> > > Hmm, is it default now?
> > 
> > 
> > Most of block crypto implementations have IV which derive
> > some way or another from the sector address.
> > 
> >  From what I see, the block address is either used as is,
> > or encrypted itself with same encryption key,
> > and the result is used as IV. Even the legacy qcow
> > encryption uses this, although it uses the virtual block
> > address, and apparently this is one of its security flaws.
> > 
> > If you don't use any IV, you end up with major security
> > hole - sectors of the same content will be encrypted
> > to the same cipertext.
> > 
> > I added this comment to clarify the usage of offset,
> > since other that this aspect of IV generation,
> > the crypto routines only need the data to be encrypted and
> > the encryption key which is stored in the crypto context.
> > 
> > 
> > > 
> > > > + *
> > > > + * @guest_offset - guest (virtual) offset of the first sector of the
> > > > + * data to be encrypted
> > > 
> > > Hmm, stop. It's wrong. Data to be encrypted is in buffer, so, it's not first sector of
> > > the data to be encrypted, but first sector in which guest writes data (to be encrypted
> > > in meantime).
> > 
> > No no no!
> > 
> > guest_offset is literally the guest's disk address of the first sector that is in the buffer.
> > The qcow2_co_encrypt is called from 2 places:
> > 
> > 1. qcow2_co_pwritev_part - here indeed the actually guest written data
> > is encrypted, and the 'offset' is passed which is the offset on which pwritev was called
> 
> I mean, that in this case your comment for me sounds like "data to be encrypted is on disk, at this offset".
> but it's not actually on disk, data is in buffer. And what is on disk we don't know, we are going to
> write...
> 
> But I don't really care. Hope it's actually obvious for averyone, where is data on write path.

Ah, now I understand. I don't mind adding a word or two explicitly mentioning that the *data* to be
encrypted is in the given buffer.

> 
> > 
> > 
> > 2. do_perform_cow_encrypt - here the just read data from before or after actually written guest
> > data is encrypted, and the guest_offset represents the address of that data.
> > I changed the do_perform_cow_encrypt, so that it receives from the caller the host and guest offset
> > of the data to be encrypted. It then aligns the host offset on start of the cluster, and passes
> > the guest offset as is, so that it does the same as qcow2_co_pwritev_part.
> > 
> > 
> > So what is wrong here?
> > 
> > >   
> > > 
> > > > + * Used as an initialization vector for older, qcow2 native encryption
> > > > + *
> > > > + * @buf - buffer with the data to encrypt
> > > > + * @len - length of the buffer (in sector size multiplies)
> > > > + *
> > > > + * Note that the group of the sectors, don't have to be aligned
> > > > + * on cluster boundary and can also cross a cluster boundary.
> > > 
> > > And I doubt in it now. I'm afraid that if we call qcow2_co_encrypt for a group
> > > of the sectors crossing a cluster boundary, we will finish up with similar bug: we'll
> > > use first cluster offset as a vector for all the sectors. We still never do it.. So,
> > > I think it worth assertion and corresponding comment.
> > > 
> > > Or is it correct?
> > 
> > Crypto code receives the data to be encrypted and decrypted,
> > and offset of first sector (512 bytes aligned) of that data (for IV calculation).
> > If the data spans multiple sectors (this happens already a lot)
> 
> Ah, yes, that's OK, sorry.
> 
> > then it able to handle this since the code works.
> > The relevant code is in do_qcrypto_block_cipher_encdec, and
> > is actually generic for all the crypto modes.
> > 
> > So there should not be anything special about crossing the cluster
> > boundary, other that noting this here, just in case.
> > 
> > No only that code can cross a cluster boundary, but it can even be
> > called for more that one cluster at once. It looks like qcow2 code limits all
> > the IO in case crypto is used to QCOW_MAX_CRYPT_CLUSTERS, which is 32
> > currently. I don't know why to be honest.
> > 
> > Remember that crypto code has no notion of clusters. It works purely
> > on sector (512 bytes) level, and each sector will have its own IV calculated,
> > based on its sector address.
> > 
> > > 
> > > > + *
> > > > + *
> > > > + */
> > > >    int coroutine_fn
> > > > -qcow2_co_encrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
> > > > -                 uint64_t offset, void *buf, size_t len)
> > > > +qcow2_co_encrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
> > > > +                 uint64_t guest_offset, void *buf, size_t len)
> > > >    {
> > > > -    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
> > > > -                             qcrypto_block_encrypt);
> > > > +    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
> > > > +                           qcrypto_block_encrypt);
> > > >    }
> > > >    
> > > > +
> > > > +/*
> > > > + * qcow2_co_decrypt()
> > > > + *
> > > > + * Decrypts one or more contiguous aligned sectors
> > > > + * Same function as qcow2_co_encrypt
> > > 
> > > Hmm, not exactly same :)
> > 
> > I'll fix that.
> > 
> > 
> > > 
> > > > + *
> > > > + */
> > > > +
> > > >    int coroutine_fn
> > > > -qcow2_co_decrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
> > > > -                 uint64_t offset, void *buf, size_t len)
> > > > +qcow2_co_decrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
> > > > +                 uint64_t guest_offset, void *buf, size_t len)
> > > >    {
> > > > -    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
> > > > -                             qcrypto_block_decrypt);
> > > > +    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
> > > > +                           qcrypto_block_decrypt);
> > > >    }
> > > > 


Best regards,
	Maxim Levitsky
diff mbox series

Patch

diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index f09cc992af..1989b423da 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -463,8 +463,8 @@  static int coroutine_fn do_perform_cow_read(BlockDriverState *bs,
 }
 
 static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
-                                                uint64_t src_cluster_offset,
-                                                uint64_t cluster_offset,
+                                                uint64_t guest_cluster_offset,
+                                                uint64_t host_cluster_offset,
                                                 unsigned offset_in_cluster,
                                                 uint8_t *buffer,
                                                 unsigned bytes)
@@ -474,8 +474,8 @@  static bool coroutine_fn do_perform_cow_encrypt(BlockDriverState *bs,
         assert((offset_in_cluster & ~BDRV_SECTOR_MASK) == 0);
         assert((bytes & ~BDRV_SECTOR_MASK) == 0);
         assert(s->crypto);
-        if (qcow2_co_encrypt(bs, cluster_offset,
-                             src_cluster_offset + offset_in_cluster,
+        if (qcow2_co_encrypt(bs, host_cluster_offset,
+                             guest_cluster_offset + offset_in_cluster,
                              buffer, bytes) < 0) {
             return false;
         }
@@ -496,7 +496,7 @@  static int coroutine_fn do_perform_cow_write(BlockDriverState *bs,
     }
 
     ret = qcow2_pre_write_overlap_check(bs, 0,
-            cluster_offset + offset_in_cluster, qiov->size, true);
+              cluster_offset + offset_in_cluster, qiov->size, true);
     if (ret < 0) {
         return ret;
     }
diff --git a/block/qcow2-threads.c b/block/qcow2-threads.c
index 3b1e63fe41..c3cda0c6a5 100644
--- a/block/qcow2-threads.c
+++ b/block/qcow2-threads.c
@@ -234,15 +234,19 @@  static int qcow2_encdec_pool_func(void *opaque)
 }
 
 static int coroutine_fn
-qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
-                  uint64_t offset, void *buf, size_t len, Qcow2EncDecFunc func)
+qcow2_co_encdec(BlockDriverState *bs, uint64_t host_cluster_offset,
+                uint64_t guest_offset, void *buf, size_t len,
+                Qcow2EncDecFunc func)
 {
     BDRVQcow2State *s = bs->opaque;
+
+    uint64_t offset = s->crypt_physical_offset ?
+        host_cluster_offset + offset_into_cluster(s, guest_offset) :
+        guest_offset;
+
     Qcow2EncDecData arg = {
         .block = s->crypto,
-        .offset = s->crypt_physical_offset ?
-                      file_cluster_offset + offset_into_cluster(s, offset) :
-                      offset,
+        .offset = offset,
         .buf = buf,
         .len = len,
         .func = func,
@@ -251,18 +255,49 @@  qcow2_co_encdec(BlockDriverState *bs, uint64_t file_cluster_offset,
     return qcow2_co_process(bs, qcow2_encdec_pool_func, &arg);
 }
 
+
+/*
+ * qcow2_co_encrypt()
+ *
+ * Encrypts one or more contiguous aligned sectors
+ *
+ * @host_cluster_offset - on disk offset of the first cluster in which
+ * the encrypted data will be written
+ * Used as an initialization vector for encryption
+ *
+ * @guest_offset - guest (virtual) offset of the first sector of the
+ * data to be encrypted
+ * Used as an initialization vector for older, qcow2 native encryption
+ *
+ * @buf - buffer with the data to encrypt
+ * @len - length of the buffer (in sector size multiplies)
+ *
+ * Note that the group of the sectors, don't have to be aligned
+ * on cluster boundary and can also cross a cluster boundary.
+ *
+ *
+ */
 int coroutine_fn
-qcow2_co_encrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
-                 uint64_t offset, void *buf, size_t len)
+qcow2_co_encrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
+                 uint64_t guest_offset, void *buf, size_t len)
 {
-    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
-                             qcrypto_block_encrypt);
+    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
+                           qcrypto_block_encrypt);
 }
 
+
+/*
+ * qcow2_co_decrypt()
+ *
+ * Decrypts one or more contiguous aligned sectors
+ * Same function as qcow2_co_encrypt
+ *
+ */
+
 int coroutine_fn
-qcow2_co_decrypt(BlockDriverState *bs, uint64_t file_cluster_offset,
-                 uint64_t offset, void *buf, size_t len)
+qcow2_co_decrypt(BlockDriverState *bs, uint64_t host_cluster_offset,
+                 uint64_t guest_offset, void *buf, size_t len)
 {
-    return qcow2_co_encdec(bs, file_cluster_offset, offset, buf, len,
-                             qcrypto_block_decrypt);
+    return qcow2_co_encdec(bs, host_cluster_offset, guest_offset, buf, len,
+                           qcrypto_block_decrypt);
 }