diff mbox

blockdev: Refuse to open encrypted image unless paused

Message ID 1394643643-27122-1-git-send-email-armbru@redhat.com
State New
Headers show

Commit Message

Markus Armbruster March 12, 2014, 5 p.m. UTC
Opening an encrypted image takes an additional step: setting the key.
Between open and the key set, the image must not be used.

We have some protection against accidental use in place: you can't
unpause a guest while we're missing keys.  You can, however, hot-plug
block devices lacking keys into a running guest just fine, or insert
media lacking keys.  In the latter case, notifying the guest of the
insert is delayed until the key is set, which may suffice to protect
at least some guests in common usage.

This patch makes the protection apply in more cases, in a rather
heavy-handed way: it doesn't let you open encrypted images unless
we're in a paused state.

It doesn't extend the protection to users other than the guest (block
jobs?).  Use of runstate_check() from block.c is disgusting.  Best I
can do right now.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 block.c                | 8 +++++++-
 stubs/Makefile.objs    | 1 +
 stubs/runstate-check.c | 6 ++++++
 3 files changed, 14 insertions(+), 1 deletion(-)
 create mode 100644 stubs/runstate-check.c

Comments

Eric Blake March 12, 2014, 5:19 p.m. UTC | #1
On 03/12/2014 11:00 AM, Markus Armbruster wrote:
> Opening an encrypted image takes an additional step: setting the key.
> Between open and the key set, the image must not be used.
> 
> We have some protection against accidental use in place: you can't
> unpause a guest while we're missing keys.  You can, however, hot-plug
> block devices lacking keys into a running guest just fine, or insert
> media lacking keys.  In the latter case, notifying the guest of the
> insert is delayed until the key is set, which may suffice to protect
> at least some guests in common usage.
> 
> This patch makes the protection apply in more cases, in a rather
> heavy-handed way: it doesn't let you open encrypted images unless
> we're in a paused state.
> 
> It doesn't extend the protection to users other than the guest (block
> jobs?).  Use of runstate_check() from block.c is disgusting.  Best I
> can do right now.

Better than what we had before, and worth having in 2.0

> 
> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>  block.c                | 8 +++++++-
>  stubs/Makefile.objs    | 1 +
>  stubs/runstate-check.c | 6 ++++++
>  3 files changed, 14 insertions(+), 1 deletion(-)
>  create mode 100644 stubs/runstate-check.c
> 

Reviewed-by: Eric Blake <eblake@redhat.com>
Fam Zheng March 13, 2014, 9:26 a.m. UTC | #2
On Wed, 03/12 18:00, Markus Armbruster wrote:
> Opening an encrypted image takes an additional step: setting the key.
> Between open and the key set, the image must not be used.
> 
> We have some protection against accidental use in place: you can't
> unpause a guest while we're missing keys.  You can, however, hot-plug
> block devices lacking keys into a running guest just fine, or insert
> media lacking keys.  In the latter case, notifying the guest of the
> insert is delayed until the key is set, which may suffice to protect
> at least some guests in common usage.
> 
> This patch makes the protection apply in more cases, in a rather
> heavy-handed way: it doesn't let you open encrypted images unless
> we're in a paused state.
> 
> It doesn't extend the protection to users other than the guest (block
> jobs?).  Use of runstate_check() from block.c is disgusting.  Best I
> can do right now.
> 
> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>  block.c                | 8 +++++++-
>  stubs/Makefile.objs    | 1 +
>  stubs/runstate-check.c | 6 ++++++
>  3 files changed, 14 insertions(+), 1 deletion(-)
>  create mode 100644 stubs/runstate-check.c
> 
> diff --git a/block.c b/block.c
> index f1ef4b0..7604881 100644
> --- a/block.c
> +++ b/block.c
> @@ -1388,12 +1388,18 @@ done:
>          ret = -EINVAL;
>          goto close_and_fail;
>      }
> -    QDECREF(options);
>  
>      if (!bdrv_key_required(bs)) {
>          bdrv_dev_change_media_cb(bs, true);
> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
> +        error_setg(errp,
> +                   "Guest must be stopped for opening of encrypted image");

Changing error message here breaks qemu-iotests 087.

Thanks,
Fam

> +        ret = -EBUSY;
> +        goto close_and_fail;
>      }
>  
> +    QDECREF(options);
>      *pbs = bs;
>      return 0;
>  
> diff --git a/stubs/Makefile.objs b/stubs/Makefile.objs
> index df3aa7a..09e7790 100644
> --- a/stubs/Makefile.objs
> +++ b/stubs/Makefile.objs
> @@ -19,6 +19,7 @@ stub-obj-y += mon-protocol-event.o
>  stub-obj-y += mon-set-error.o
>  stub-obj-y += pci-drive-hot-add.o
>  stub-obj-y += reset.o
> +stub-obj-y += runstate-check.o
>  stub-obj-y += set-fd-handler.o
>  stub-obj-y += slirp.o
>  stub-obj-y += sysbus.o
> diff --git a/stubs/runstate-check.c b/stubs/runstate-check.c
> new file mode 100644
> index 0000000..bd2e375
> --- /dev/null
> +++ b/stubs/runstate-check.c
> @@ -0,0 +1,6 @@
> +#include "sysemu/sysemu.h"
> +
> +bool runstate_check(RunState state)
> +{
> +    return state == RUN_STATE_PRELAUNCH;
> +}
> -- 
> 1.8.1.4
> 
>
Paolo Bonzini March 13, 2014, 10:43 a.m. UTC | #3
Il 12/03/2014 18:00, Markus Armbruster ha scritto:
> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */

Why not "if (runstate_is_running())"?

Paolo

> +        error_setg(errp,
> +                   "Guest must be stopped for opening of encrypted image");
> +        ret = -EBUSY;
> +        goto close_and_fail;
Markus Armbruster March 13, 2014, 1:18 p.m. UTC | #4
Paolo Bonzini <pbonzini@redhat.com> writes:

> Il 12/03/2014 18:00, Markus Armbruster ha scritto:
>> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
>> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
>
> Why not "if (runstate_is_running())"?

The predicate actually wanted here is "monitor command 'cont' required
to get the guest running", because 'cont' is where the protection is.
My run state test is a crude approximation.
Markus Armbruster March 13, 2014, 1:25 p.m. UTC | #5
Fam Zheng <famz@redhat.com> writes:

> On Wed, 03/12 18:00, Markus Armbruster wrote:
>> Opening an encrypted image takes an additional step: setting the key.
>> Between open and the key set, the image must not be used.
>> 
>> We have some protection against accidental use in place: you can't
>> unpause a guest while we're missing keys.  You can, however, hot-plug
>> block devices lacking keys into a running guest just fine, or insert
>> media lacking keys.  In the latter case, notifying the guest of the
>> insert is delayed until the key is set, which may suffice to protect
>> at least some guests in common usage.
>> 
>> This patch makes the protection apply in more cases, in a rather
>> heavy-handed way: it doesn't let you open encrypted images unless
>> we're in a paused state.
>> 
>> It doesn't extend the protection to users other than the guest (block
>> jobs?).  Use of runstate_check() from block.c is disgusting.  Best I
>> can do right now.
>> 
>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>> ---
>>  block.c                | 8 +++++++-
>>  stubs/Makefile.objs    | 1 +
>>  stubs/runstate-check.c | 6 ++++++
>>  3 files changed, 14 insertions(+), 1 deletion(-)
>>  create mode 100644 stubs/runstate-check.c
>> 
>> diff --git a/block.c b/block.c
>> index f1ef4b0..7604881 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -1388,12 +1388,18 @@ done:
>>          ret = -EINVAL;
>>          goto close_and_fail;
>>      }
>> -    QDECREF(options);
>>  
>>      if (!bdrv_key_required(bs)) {
>>          bdrv_dev_change_media_cb(bs, true);
>> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
>> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
>> +        error_setg(errp,
>> +                   "Guest must be stopped for opening of encrypted image");
>
> Changing error message here breaks qemu-iotests 087.

Crap.  I'm on vacation until Monday, just checking in to shepherd this
patch...

On *master*, "make check-block" reports

    Not run: 016 052 059 064 070 077
    Failures: 085 087
    Failed 2 of 34 tests

What am I doing wrong?
Eric Blake March 13, 2014, 1:27 p.m. UTC | #6
On 03/13/2014 04:43 AM, Paolo Bonzini wrote:
> Il 12/03/2014 18:00, Markus Armbruster ha scritto:
>> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
>> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
> 
> Why not "if (runstate_is_running())"?

Because that lacks PRELAUNCH, but PRELAUNCH also needs the protection.
Paolo Bonzini March 13, 2014, 2:14 p.m. UTC | #7
Il 13/03/2014 14:18, Markus Armbruster ha scritto:
> Paolo Bonzini <pbonzini@redhat.com> writes:
> 
>> Il 12/03/2014 18:00, Markus Armbruster ha scritto:
>>> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
>>> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
>>
>> Why not "if (runstate_is_running())"?
> 
> The predicate actually wanted here is "monitor command 'cont' required
> to get the guest running", because 'cont' is where the protection is.
> My run state test is a crude approximation.
> 

Got it.  Then you need to add at least a check for
"runstate_check(RUN_STATE_INMIGRATE)", otherwise you break incoming
migration.  Actually, I think only SAVE_VM/RESTORE_VM/DEBUG are 
problematic, but I understand why you preferred a conservative
test (sufficient condition, not necessary).

You are singling out prelaunch and inmigrate because drive_init
will reset autostart to 0 for an encrypted image, right?

Paolo
Paolo Bonzini March 13, 2014, 2:19 p.m. UTC | #8
Il 13/03/2014 14:27, Eric Blake ha scritto:
>>> >> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
>>> >> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
>> >
>> > Why not "if (runstate_is_running())"?
> Because that lacks PRELAUNCH, but PRELAUNCH also needs the protection.

Nope, PRELAUNCH does *not* need the protection.

if (!runstate_check(PRELAUNCH) && !runstate_check(PAUSED)) error

	gives an error if runstate == anything *but*
	PRELAUNCH and PAUSED

It's at least DEBUG, SAVE_VM and RESTORE_VM that do need it but are not 
included in runstate_is_running.

Paolo
Markus Armbruster March 13, 2014, 3 p.m. UTC | #9
Paolo Bonzini <pbonzini@redhat.com> writes:

> Il 13/03/2014 14:18, Markus Armbruster ha scritto:
>> Paolo Bonzini <pbonzini@redhat.com> writes:
>> 
>>> Il 12/03/2014 18:00, Markus Armbruster ha scritto:
>>>> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
>>>> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
>>>
>>> Why not "if (runstate_is_running())"?
>> 
>> The predicate actually wanted here is "monitor command 'cont' required
>> to get the guest running", because 'cont' is where the protection is.
>> My run state test is a crude approximation.
>> 
>
> Got it.  Then you need to add at least a check for
> "runstate_check(RUN_STATE_INMIGRATE)", otherwise you break incoming
> migration.

You're right: main() goes from RUN_STATE_PRELAUNCH to
RUN_STATE_INMIGRATE right when it sees -incoming.

>             Actually, I think only SAVE_VM/RESTORE_VM/DEBUG are 
> problematic, but I understand why you preferred a conservative
> test (sufficient condition, not necessary).

Exactly.

> You are singling out prelaunch and inmigrate because drive_init
> will reset autostart to 0 for an encrypted image, right?

Yes.
Paolo Bonzini March 13, 2014, 3:08 p.m. UTC | #10
Il 13/03/2014 16:00, Markus Armbruster ha scritto:
> Paolo Bonzini <pbonzini@redhat.com> writes:
>
>> Il 13/03/2014 14:18, Markus Armbruster ha scritto:
>>> Paolo Bonzini <pbonzini@redhat.com> writes:
>>>
>>>> Il 12/03/2014 18:00, Markus Armbruster ha scritto:
>>>>> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
>>>>> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
>>>>
>>>> Why not "if (runstate_is_running())"?
>>>
>>> The predicate actually wanted here is "monitor command 'cont' required
>>> to get the guest running", because 'cont' is where the protection is.
>>> My run state test is a crude approximation.
>>>
>>
>> Got it.  Then you need to add at least a check for
>> "runstate_check(RUN_STATE_INMIGRATE)", otherwise you break incoming
>> migration.
>
> You're right: main() goes from RUN_STATE_PRELAUNCH to
> RUN_STATE_INMIGRATE right when it sees -incoming.
>
>>             Actually, I think only SAVE_VM/RESTORE_VM/DEBUG are
>> problematic, but I understand why you preferred a conservative
>> test (sufficient condition, not necessary).
>
> Exactly.
>
>> You are singling out prelaunch and inmigrate because drive_init
>> will reset autostart to 0 for an encrypted image, right?
>
> Yes.

Then with the check modified,

Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>

Paolo
Markus Armbruster March 14, 2014, 8:15 a.m. UTC | #11
Paolo Bonzini <pbonzini@redhat.com> writes:

> Il 13/03/2014 16:00, Markus Armbruster ha scritto:
>> Paolo Bonzini <pbonzini@redhat.com> writes:
>>
>>> Il 13/03/2014 14:18, Markus Armbruster ha scritto:
>>>> Paolo Bonzini <pbonzini@redhat.com> writes:
>>>>
>>>>> Il 12/03/2014 18:00, Markus Armbruster ha scritto:
>>>>>> +    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
>>>>>> +            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
>>>>>
>>>>> Why not "if (runstate_is_running())"?
>>>>
>>>> The predicate actually wanted here is "monitor command 'cont' required
>>>> to get the guest running", because 'cont' is where the protection is.
>>>> My run state test is a crude approximation.
>>>>
>>>
>>> Got it.  Then you need to add at least a check for
>>> "runstate_check(RUN_STATE_INMIGRATE)", otherwise you break incoming
>>> migration.
>>
>> You're right: main() goes from RUN_STATE_PRELAUNCH to
>> RUN_STATE_INMIGRATE right when it sees -incoming.
>>
>>>             Actually, I think only SAVE_VM/RESTORE_VM/DEBUG are
>>> problematic, but I understand why you preferred a conservative
>>> test (sufficient condition, not necessary).
>>
>> Exactly.
>>
>>> You are singling out prelaunch and inmigrate because drive_init
>>> will reset autostart to 0 for an encrypted image, right?
>>
>> Yes.
>
> Then with the check modified,
>
> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>

Will do.  I'm refraining from adding your R-by, because I need to update
tests, too.  Thanks!
diff mbox

Patch

diff --git a/block.c b/block.c
index f1ef4b0..7604881 100644
--- a/block.c
+++ b/block.c
@@ -1388,12 +1388,18 @@  done:
         ret = -EINVAL;
         goto close_and_fail;
     }
-    QDECREF(options);
 
     if (!bdrv_key_required(bs)) {
         bdrv_dev_change_media_cb(bs, true);
+    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
+            && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
+        error_setg(errp,
+                   "Guest must be stopped for opening of encrypted image");
+        ret = -EBUSY;
+        goto close_and_fail;
     }
 
+    QDECREF(options);
     *pbs = bs;
     return 0;
 
diff --git a/stubs/Makefile.objs b/stubs/Makefile.objs
index df3aa7a..09e7790 100644
--- a/stubs/Makefile.objs
+++ b/stubs/Makefile.objs
@@ -19,6 +19,7 @@  stub-obj-y += mon-protocol-event.o
 stub-obj-y += mon-set-error.o
 stub-obj-y += pci-drive-hot-add.o
 stub-obj-y += reset.o
+stub-obj-y += runstate-check.o
 stub-obj-y += set-fd-handler.o
 stub-obj-y += slirp.o
 stub-obj-y += sysbus.o
diff --git a/stubs/runstate-check.c b/stubs/runstate-check.c
new file mode 100644
index 0000000..bd2e375
--- /dev/null
+++ b/stubs/runstate-check.c
@@ -0,0 +1,6 @@ 
+#include "sysemu/sysemu.h"
+
+bool runstate_check(RunState state)
+{
+    return state == RUN_STATE_PRELAUNCH;
+}