[PATCH 0/3] qemu: Fix restoring a domain from a block device

*** BLURB HERE *** Michal Prívozník (3): selinux: Don't remember label for restore path security: Introduce virSecurityManagerDomainSetIncomingPathLabel qemu: Label restore path outside of secdriver transactions src/libvirt_private.syms | 1 + src/qemu/qemu_security.c | 7 +++++++ src/security/security_driver.h | 4 ++++ src/security/security_manager.c | 29 +++++++++++++++++++++++++++++ src/security/security_manager.h | 4 ++++ src/security/security_selinux.c | 23 +++++++++++++++++------ src/security/security_stack.c | 21 +++++++++++++++++++++ 7 files changed, 83 insertions(+), 6 deletions(-) -- 2.24.1

The seclabel for @stdin_path in virSecuritySELinuxSetAllLabel() is not restored, because at virSecuritySELinuxRestoreAllLabel() phase it's too late and the caller (QEMU driver) simply doesn't care. Well, don't remember the label and let the perms leak. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/security/security_selinux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/security/security_selinux.c b/src/security/security_selinux.c index 8aeb6e45a5..f47bfbdba9 100644 --- a/src/security/security_selinux.c +++ b/src/security/security_selinux.c @@ -3233,7 +3233,7 @@ virSecuritySELinuxSetAllLabel(virSecurityManagerPtr mgr, if (stdin_path && virSecuritySELinuxSetFilecon(mgr, stdin_path, - data->content_context, true) < 0) + data->content_context, false) < 0) return -1; return 0; -- 2.24.1

On Fri, Apr 03, 2020 at 05:58:01PM +0200, Michal Privoznik wrote:
The seclabel for @stdin_path in virSecuritySELinuxSetAllLabel() is not restored, because at virSecuritySELinuxRestoreAllLabel() phase it's too late and the caller (QEMU driver) simply doesn't care. Well, don't remember the label and let the perms leak.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/security/security_selinux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/security/security_selinux.c b/src/security/security_selinux.c index 8aeb6e45a5..f47bfbdba9 100644 --- a/src/security/security_selinux.c +++ b/src/security/security_selinux.c @@ -3233,7 +3233,7 @@ virSecuritySELinuxSetAllLabel(virSecurityManagerPtr mgr,
if (stdin_path && virSecuritySELinuxSetFilecon(mgr, stdin_path, - data->content_context, true) < 0) + data->content_context, false) < 0)
You're removing this whole condition in patch 3/3, so I suggest, you drop this patch completely. -- Erik Skultety

This API allows drivers to separate out handling of @stdin_path of virSecurityManagerSetAllLabel(). The thing is, the QEMU driver uses transactions for virSecurityManagerSetAllLabel() which relabels devices from inside of domain's namespace. This is what we usually want. Except when resuming domain from a file. The file is opened before any namespace is set up and the FD is passed to QEMU to read the migration stream from. Because of this, the file lives outside of the namespace and if it so happens that the file is a block device (i.e. it lives under /dev) its copy will be created in the namespace. But the FD that is passed to QEMU points to the original living in the host and not in the namespace. So relabeling the file inside the namespace helps nothing. But if we have a separate API for relabeling the restore file then the QEMU driver can continue calling virSecurityManagerSetAllLabel() with transactions enabled and call this new API without transactions. We already have an API for relabeling a single file (virSecurityManagerDomainSetPathLabel()) but in case of SELinux it uses @imagelabel (which allows RW access) and we want to use @content_context (which allows RO access). Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/libvirt_private.syms | 1 + src/security/security_driver.h | 4 ++++ src/security/security_manager.c | 29 +++++++++++++++++++++++++++++ src/security/security_manager.h | 4 ++++ src/security/security_stack.c | 21 +++++++++++++++++++++ 5 files changed, 59 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e276f55bb1..2c63f37fc2 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1523,6 +1523,7 @@ virSecurityDriverLookup; # security/security_manager.h virSecurityManagerCheckAllLabel; virSecurityManagerClearSocketLabel; +virSecurityManagerDomainSetIncomingPathLabel; virSecurityManagerDomainSetPathLabel; virSecurityManagerGenLabel; virSecurityManagerGetBaseLabel; diff --git a/src/security/security_driver.h b/src/security/security_driver.h index 3353955813..6cbe8613c9 100644 --- a/src/security/security_driver.h +++ b/src/security/security_driver.h @@ -140,6 +140,9 @@ typedef int (*virSecurityDomainSetPathLabel) (virSecurityManagerPtr mgr, virDomainDefPtr def, const char *path, bool allowSubtree); +typedef int (*virSecurityDomainSetIncomingPathLabel) (virSecurityManagerPtr mgr, + virDomainDefPtr def, + const char *path); typedef int (*virSecurityDomainSetChardevLabel) (virSecurityManagerPtr mgr, virDomainDefPtr def, virDomainChrSourceDefPtr dev_source, @@ -211,6 +214,7 @@ struct _virSecurityDriver { virSecurityDriverGetBaseLabel getBaseLabel; virSecurityDomainSetPathLabel domainSetPathLabel; + virSecurityDomainSetIncomingPathLabel domainSetIncomingPathLabel; virSecurityDomainSetChardevLabel domainSetSecurityChardevLabel; virSecurityDomainRestoreChardevLabel domainRestoreSecurityChardevLabel; diff --git a/src/security/security_manager.c b/src/security/security_manager.c index fe032746ff..a76b953ee4 100644 --- a/src/security/security_manager.c +++ b/src/security/security_manager.c @@ -1077,6 +1077,35 @@ virSecurityManagerDomainSetPathLabel(virSecurityManagerPtr mgr, } +/** + * virSecurityManagerDomainSetIncomingPathLabel: + * @mgr: security manager object + * @vm: domain definition object + * @path: path to label + * + * This function relabels given @path so that @vm can restore for + * it. This allows the driver backend to use different label than + * virSecurityManagerDomainSetPathLabel(). + * + * Returns: 0 on success, -1 on error. + */ +int +virSecurityManagerDomainSetIncomingPathLabel(virSecurityManagerPtr mgr, + virDomainDefPtr vm, + const char *path) +{ + if (mgr->drv->domainSetIncomingPathLabel) { + int ret; + virObjectLock(mgr); + ret = mgr->drv->domainSetIncomingPathLabel(mgr, vm, path); + virObjectUnlock(mgr); + return ret; + } + + return 0; +} + + /** * virSecurityManagerSetMemoryLabel: * @mgr: security manager object diff --git a/src/security/security_manager.h b/src/security/security_manager.h index 7699bcbc6f..465d71558f 100644 --- a/src/security/security_manager.h +++ b/src/security/security_manager.h @@ -189,6 +189,10 @@ int virSecurityManagerDomainSetPathLabel(virSecurityManagerPtr mgr, const char *path, bool allowSubtree); +int virSecurityManagerDomainSetIncomingPathLabel(virSecurityManagerPtr mgr, + virDomainDefPtr vm, + const char *path); + int virSecurityManagerSetChardevLabel(virSecurityManagerPtr mgr, virDomainDefPtr def, virDomainChrSourceDefPtr dev_source, diff --git a/src/security/security_stack.c b/src/security/security_stack.c index 073876daff..7782abaf9d 100644 --- a/src/security/security_stack.c +++ b/src/security/security_stack.c @@ -825,6 +825,26 @@ virSecurityStackDomainSetPathLabel(virSecurityManagerPtr mgr, return rc; } + +static int +virSecurityStackDomainSetIncomingPathLabel(virSecurityManagerPtr mgr, + virDomainDefPtr vm, + const char *path) +{ + virSecurityStackDataPtr priv = virSecurityManagerGetPrivateData(mgr); + virSecurityStackItemPtr item = priv->itemsHead; + int rc = 0; + + for (; item; item = item->next) { + if (virSecurityManagerDomainSetIncomingPathLabel(item->securityManager, + vm, path) < 0) + rc = -1; + } + + return rc; +} + + static int virSecurityStackDomainSetChardevLabel(virSecurityManagerPtr mgr, virDomainDefPtr def, @@ -985,6 +1005,7 @@ virSecurityDriver virSecurityDriverStack = { .getBaseLabel = virSecurityStackGetBaseLabel, .domainSetPathLabel = virSecurityStackDomainSetPathLabel, + .domainSetIncomingPathLabel = virSecurityStackDomainSetIncomingPathLabel, .domainSetSecurityChardevLabel = virSecurityStackDomainSetChardevLabel, .domainRestoreSecurityChardevLabel = virSecurityStackDomainRestoreChardevLabel, -- 2.24.1

On Fri, Apr 03, 2020 at 05:58:02PM +0200, Michal Privoznik wrote:
This API allows drivers to separate out handling of @stdin_path of virSecurityManagerSetAllLabel(). The thing is, the QEMU driver uses transactions for virSecurityManagerSetAllLabel() which relabels devices from inside of domain's namespace. This is what we usually want. Except when resuming domain from a file. The file is opened before any namespace is set up and the FD is passed to QEMU to read the migration stream from. Because of this, the file lives outside of the namespace and if it so happens that the file is a block device (i.e. it lives under /dev) its copy will be created in the namespace. But the FD that is passed to QEMU points to the original living in the host and not in the namespace. So relabeling the file inside the namespace helps nothing.
But if we have a separate API for relabeling the restore file then the QEMU driver can continue calling virSecurityManagerSetAllLabel() with transactions enabled and call this new API without transactions.
We already have an API for relabeling a single file (virSecurityManagerDomainSetPathLabel()) but in case of SELinux it uses @imagelabel (which allows RW access) and we want to use @content_context (which allows RO access).
Since this patch is only adding a generic driver API rather than a specific security backend API, I'd move ^this last paragraph to the next patch, I actually appreciated the info there much more than here during the review.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/libvirt_private.syms | 1 + src/security/security_driver.h | 4 ++++ src/security/security_manager.c | 29 +++++++++++++++++++++++++++++ src/security/security_manager.h | 4 ++++ src/security/security_stack.c | 21 +++++++++++++++++++++ 5 files changed, 59 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e276f55bb1..2c63f37fc2 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1523,6 +1523,7 @@ virSecurityDriverLookup; # security/security_manager.h virSecurityManagerCheckAllLabel; virSecurityManagerClearSocketLabel; +virSecurityManagerDomainSetIncomingPathLabel;
IncomingPath sounds a bit confusing to me, I'd go with something like HostPath instead, in patch 3/3 you also have an explicit commentary why we're using the respective backend API for this rather than simply using SetPathLabel.
virSecurityManagerDomainSetPathLabel; virSecurityManagerGenLabel; virSecurityManagerGetBaseLabel; diff --git a/src/security/security_driver.h b/src/security/security_driver.h index 3353955813..6cbe8613c9 100644 --- a/src/security/security_driver.h +++ b/src/security/security_driver.h @@ -140,6 +140,9 @@ typedef int (*virSecurityDomainSetPathLabel) (virSecurityManagerPtr mgr, virDomainDefPtr def, const char *path, bool allowSubtree); +typedef int (*virSecurityDomainSetIncomingPathLabel) (virSecurityManagerPtr mgr, + virDomainDefPtr def, + const char *path); typedef int (*virSecurityDomainSetChardevLabel) (virSecurityManagerPtr mgr, virDomainDefPtr def, virDomainChrSourceDefPtr dev_source, @@ -211,6 +214,7 @@ struct _virSecurityDriver { virSecurityDriverGetBaseLabel getBaseLabel;
virSecurityDomainSetPathLabel domainSetPathLabel; + virSecurityDomainSetIncomingPathLabel domainSetIncomingPathLabel;
virSecurityDomainSetChardevLabel domainSetSecurityChardevLabel; virSecurityDomainRestoreChardevLabel domainRestoreSecurityChardevLabel; diff --git a/src/security/security_manager.c b/src/security/security_manager.c index fe032746ff..a76b953ee4 100644 --- a/src/security/security_manager.c +++ b/src/security/security_manager.c @@ -1077,6 +1077,35 @@ virSecurityManagerDomainSetPathLabel(virSecurityManagerPtr mgr, }
+/** + * virSecurityManagerDomainSetIncomingPathLabel: + * @mgr: security manager object + * @vm: domain definition object + * @path: path to label + * + * This function relabels given @path so that @vm can restore for
maybe add "host" @path here to make it even clearer.
+ * it. This allows the driver backend to use different label than + * virSecurityManagerDomainSetPathLabel(). + * + * Returns: 0 on success, -1 on error. + */ +int +virSecurityManagerDomainSetIncomingPathLabel(virSecurityManagerPtr mgr, + virDomainDefPtr vm, + const char *path) +{ + if (mgr->drv->domainSetIncomingPathLabel) { + int ret; + virObjectLock(mgr); + ret = mgr->drv->domainSetIncomingPathLabel(mgr, vm, path); + virObjectUnlock(mgr); + return ret; + } + + return 0; +}
Looks good and works: Reviewed-by: Erik Skultety <eskultet@redhat.com>

On 4/17/20 12:57 PM, Erik Skultety wrote:
On Fri, Apr 03, 2020 at 05:58:02PM +0200, Michal Privoznik wrote:
This API allows drivers to separate out handling of @stdin_path of virSecurityManagerSetAllLabel(). The thing is, the QEMU driver uses transactions for virSecurityManagerSetAllLabel() which relabels devices from inside of domain's namespace. This is what we usually want. Except when resuming domain from a file. The file is opened before any namespace is set up and the FD is passed to QEMU to read the migration stream from. Because of this, the file lives outside of the namespace and if it so happens that the file is a block device (i.e. it lives under /dev) its copy will be created in the namespace. But the FD that is passed to QEMU points to the original living in the host and not in the namespace. So relabeling the file inside the namespace helps nothing.
But if we have a separate API for relabeling the restore file then the QEMU driver can continue calling virSecurityManagerSetAllLabel() with transactions enabled and call this new API without transactions.
We already have an API for relabeling a single file (virSecurityManagerDomainSetPathLabel()) but in case of SELinux it uses @imagelabel (which allows RW access) and we want to use @content_context (which allows RO access).
Since this patch is only adding a generic driver API rather than a specific security backend API, I'd move ^this last paragraph to the next patch, I actually appreciated the info there much more than here during the review.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/libvirt_private.syms | 1 + src/security/security_driver.h | 4 ++++ src/security/security_manager.c | 29 +++++++++++++++++++++++++++++ src/security/security_manager.h | 4 ++++ src/security/security_stack.c | 21 +++++++++++++++++++++ 5 files changed, 59 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e276f55bb1..2c63f37fc2 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1523,6 +1523,7 @@ virSecurityDriverLookup; # security/security_manager.h virSecurityManagerCheckAllLabel; virSecurityManagerClearSocketLabel; +virSecurityManagerDomainSetIncomingPathLabel;
IncomingPath sounds a bit confusing to me, I'd go with something like HostPath instead, in patch 3/3 you also have an explicit commentary why we're using the respective backend API for this rather than simply using SetPathLabel.
Yeah, naming is hard. Part of my reasoning was that if the name is not obvious people will not use it and default to virSecurityDomainSetPathLabel() which should be used as it guarantees RW access. But on the secdriver API level we don't distinguish transactions and thus host/namespaces. But if my reasoning was flawed and we might actually encourage people to use it? I mean, how about virSecurityDomainSetPathLabelRO() for the name, how does that sound? We can leave the transactions aside and at hypervisor driver discretion because in fact, it's only QEMU who uses transactions.
virSecurityManagerDomainSetPathLabel; virSecurityManagerGenLabel; virSecurityManagerGetBaseLabel; diff --git a/src/security/security_driver.h b/src/security/security_driver.h index 3353955813..6cbe8613c9 100644 --- a/src/security/security_driver.h +++ b/src/security/security_driver.h @@ -140,6 +140,9 @@ typedef int (*virSecurityDomainSetPathLabel) (virSecurityManagerPtr mgr, virDomainDefPtr def, const char *path, bool allowSubtree); +typedef int (*virSecurityDomainSetIncomingPathLabel) (virSecurityManagerPtr mgr, + virDomainDefPtr def, + const char *path); typedef int (*virSecurityDomainSetChardevLabel) (virSecurityManagerPtr mgr, virDomainDefPtr def, virDomainChrSourceDefPtr dev_source, @@ -211,6 +214,7 @@ struct _virSecurityDriver { virSecurityDriverGetBaseLabel getBaseLabel;
virSecurityDomainSetPathLabel domainSetPathLabel; + virSecurityDomainSetIncomingPathLabel domainSetIncomingPathLabel;
virSecurityDomainSetChardevLabel domainSetSecurityChardevLabel; virSecurityDomainRestoreChardevLabel domainRestoreSecurityChardevLabel; diff --git a/src/security/security_manager.c b/src/security/security_manager.c index fe032746ff..a76b953ee4 100644 --- a/src/security/security_manager.c +++ b/src/security/security_manager.c @@ -1077,6 +1077,35 @@ virSecurityManagerDomainSetPathLabel(virSecurityManagerPtr mgr, }
+/** + * virSecurityManagerDomainSetIncomingPathLabel: + * @mgr: security manager object + * @vm: domain definition object + * @path: path to label + * + * This function relabels given @path so that @vm can restore for
maybe add "host" @path here to make it even clearer.
+ * it. This allows the driver backend to use different label than + * virSecurityManagerDomainSetPathLabel().
This would then be: This function relabels given @path for read only access, which is in contrast with virSecurityManagerDomainSetPathLabel() which gives read write access. Michal

...
+/** + * virSecurityManagerDomainSetIncomingPathLabel: + * @mgr: security manager object + * @vm: domain definition object + * @path: path to label + * + * This function relabels given @path so that @vm can restore for
maybe add "host" @path here to make it even clearer.
+ * it. This allows the driver backend to use different label than + * virSecurityManagerDomainSetPathLabel().
This would then be:
This function relabels given @path for read only access, which is in contrast with virSecurityManagerDomainSetPathLabel() which gives read write access.
Yep, I'm okay with *SetPathLabelRO, don't forget to include ^this commentary bit :). -- Erik Skultety

As explained in the previous commit, we need to relabel the file we are restoring the domain from. That is the FD that is passed to QEMU. If the file is not under /dev then the file inside the namespace is the very same as the one in the host. And regardless of using transactions, the file will be relabeled. But, if the file is under /dev then when using transactions only the copy inside the namespace is relabeled and the one in the host is not. But QEMU is reading from the one in the host, actually. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1772838 Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/qemu/qemu_security.c | 7 +++++++ src/security/security_selinux.c | 23 +++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/qemu/qemu_security.c b/src/qemu/qemu_security.c index 484fc34552..594a700ea3 100644 --- a/src/qemu/qemu_security.c +++ b/src/qemu/qemu_security.c @@ -39,6 +39,13 @@ qemuSecuritySetAllLabel(virQEMUDriverPtr driver, qemuDomainObjPrivatePtr priv = vm->privateData; pid_t pid = -1; + /* Explicitly run this outside of transaction. We really want to relabel + * the file in the host and not in the domain's namespace. */ + if (virSecurityManagerDomainSetIncomingPathLabel(driver->securityManager, + vm->def, + stdin_path) < 0) + goto cleanup; + if (qemuDomainNamespaceEnabled(vm, QEMU_DOMAIN_NS_MOUNT)) pid = vm->pid; diff --git a/src/security/security_selinux.c b/src/security/security_selinux.c index f47bfbdba9..4d8f755c10 100644 --- a/src/security/security_selinux.c +++ b/src/security/security_selinux.c @@ -3135,7 +3135,7 @@ virSecuritySELinuxSetSecuritySmartcardCallback(virDomainDefPtr def, static int virSecuritySELinuxSetAllLabel(virSecurityManagerPtr mgr, virDomainDefPtr def, - const char *stdin_path, + const char *stdin_path G_GNUC_UNUSED, bool chardevStdioLogd, bool migrated G_GNUC_UNUSED) { @@ -3231,11 +3231,6 @@ virSecuritySELinuxSetAllLabel(virSecurityManagerPtr mgr, data->content_context, true) < 0) return -1; - if (stdin_path && - virSecuritySELinuxSetFilecon(mgr, stdin_path, - data->content_context, false) < 0) - return -1; - return 0; } @@ -3393,6 +3388,21 @@ virSecuritySELinuxDomainSetPathLabel(virSecurityManagerPtr mgr, return virSecuritySELinuxSetFilecon(mgr, path, seclabel->imagelabel, true); } +static int +virSecuritySELinuxDomainSetIncomingPathLabel(virSecurityManagerPtr mgr, + virDomainDefPtr def, + const char *path) +{ + virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr); + virSecurityLabelDefPtr secdef; + + secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME); + + if (!path || !secdef || !secdef->relabel || data->skipAllLabel) + return 0; + + return virSecuritySELinuxSetFilecon(mgr, path, data->content_context, false); +} /* * virSecuritySELinuxSetFileLabels: @@ -3596,6 +3606,7 @@ virSecurityDriver virSecurityDriverSELinux = { .getBaseLabel = virSecuritySELinuxGetBaseLabel, .domainSetPathLabel = virSecuritySELinuxDomainSetPathLabel, + .domainSetIncomingPathLabel = virSecuritySELinuxDomainSetIncomingPathLabel, .domainSetSecurityChardevLabel = virSecuritySELinuxSetChardevLabel, .domainRestoreSecurityChardevLabel = virSecuritySELinuxRestoreChardevLabel, -- 2.24.1

On Fri, Apr 03, 2020 at 05:58:03PM +0200, Michal Privoznik wrote:
As explained in the previous commit, we need to relabel the file we are restoring the domain from. That is the FD that is passed to QEMU. If the file is not under /dev then the file inside the namespace is the very same as the one in the host. And regardless of using transactions, the file will be relabeled. But, if the file is under /dev then when using transactions only the copy inside the namespace is relabeled and the one in the host is not. But QEMU is reading from the one in the host, actually.
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1772838
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- ...
/* * virSecuritySELinuxSetFileLabels: @@ -3596,6 +3606,7 @@ virSecurityDriver virSecurityDriverSELinux = { .getBaseLabel = virSecuritySELinuxGetBaseLabel,
.domainSetPathLabel = virSecuritySELinuxDomainSetPathLabel, + .domainSetIncomingPathLabel = virSecuritySELinuxDomainSetIncomingPathLabel,
"HostPath" would IMO feel better than "IncomingPath" in this patch as well. Reviewed-by: Erik Skultety <eskultet@redhat.com>
participants (2)
-
Erik Skultety
-
Michal Privoznik