From 83edaa14dafc6fc409ad4c9e2b89351c3f519602 Mon Sep 17 00:00:00 2001 Message-Id: <83edaa14dafc6fc409ad4c9e2b89351c3f519602.1378475168.git.jdenemar@redhat.com> From: Eric Blake Date: Tue, 20 Aug 2013 11:08:54 -0600 Subject: [PATCH] selinux: distinguish failure to label from request to avoid label https://bugzilla.redhat.com/show_bug.cgi?id=924153 Commit 904e05a2 (v0.9.9) added a per- seclabel element with an attribute relabel='no' in order to try and minimize the impact of shutdown delays when an NFS server disappears. The idea was that if a disk is on NFS and can't be labeled in the first place, there is no need to attempt the (no-op) relabel on domain shutdown. Unfortunately, the way this was implemented was by modifying the domain XML so that the optimization would survive libvirtd restart, but in a way that is indistinguishable from an explicit user setting. Furthermore, once the setting is turned on, libvirt avoids attempts at labeling, even for operations like snapshot or blockcopy where the chain is being extended or pivoted onto non-NFS, where SELinux labeling is once again possible. As a result, it was impossible to do a blockcopy to pivot from an NFS image file onto a local file. The solution is to separate the semantics of a chain that must not be labeled (which the user can set even on persistent domains) vs. the optimization of not attempting a relabel on cleanup (a live-only annotation), and using only the user's explicit notation rather than the optimization as the decision on whether to skip a label attempt in the first place. When upgrading an older libvirtd to a newer, an NFS volume will still attempt the relabel; but as the avoidance of a relabel was only an optimization, this shouldn't cause any problems. In the ideal future, libvirt will eventually have XML describing EVERY file in the backing chain, with each file having a separate element. At that point, libvirt will be able to track more closely which files need a relabel attempt at shutdown. But until we reach that point, the single for the entire chain is treated as a hint - when a chain has only one file, then we know it is accurate; but if the chain has more than one file, we have to attempt relabel in spite of the attribute, in case part of the chain is local and SELinux mattered for that portion of the chain. * src/conf/domain_conf.h (_virSecurityDeviceLabelDef): Add new member. * src/conf/domain_conf.c (virSecurityDeviceLabelDefParseXML): Parse it, for live images only. (virSecurityDeviceLabelDefFormat): Output it. (virDomainDiskDefParseXML, virDomainChrSourceDefParseXML) (virDomainDiskSourceDefFormat, virDomainChrDefFormat) (virDomainDiskDefFormat): Pass flags on through. * src/security/security_selinux.c (virSecuritySELinuxRestoreSecurityImageLabelInt): Honor labelskip when possible. (virSecuritySELinuxSetSecurityFileLabel): Set labelskip, not norelabel, if labeling fails. (virSecuritySELinuxSetFileconHelper): Fix indentation. * docs/formatdomain.html.in (seclabel): Document new xml. * docs/schemas/domaincommon.rng (devSeclabel): Allow it in RNG. * tests/qemuxml2argvdata/qemuxml2argv-seclabel-*-labelskip.xml: * tests/qemuxml2argvdata/qemuxml2argv-seclabel-*-labelskip.args: * tests/qemuxml2xmloutdata/qemuxml2xmlout-seclabel-*-labelskip.xml: New test files. * tests/qemuxml2argvtest.c (mymain): Run the new tests. * tests/qemuxml2xmltest.c (mymain): Likewise. Signed-off-by: Eric Blake (cherry picked from commit 0f082e699eda0ad14965c0bc75789c4bfac2bda7) --- docs/formatdomain.html.in | 6 ++- docs/schemas/domaincommon.rng | 27 +++++++------ src/conf/domain_conf.c | 47 ++++++++++++++++------ src/conf/domain_conf.h | 3 +- src/security/security_selinux.c | 18 ++++++--- .../qemuxml2argv-seclabel-dynamic-labelskip.args | 5 +++ .../qemuxml2argv-seclabel-dynamic-labelskip.xml | 32 +++++++++++++++ .../qemuxml2argv-seclabel-static-labelskip.args | 5 +++ .../qemuxml2argv-seclabel-static-labelskip.xml | 33 +++++++++++++++ tests/qemuxml2argvtest.c | 2 + .../qemuxml2xmlout-seclabel-dynamic-labelskip.xml | 31 ++++++++++++++ tests/qemuxml2xmltest.c | 8 ++-- 12 files changed, 182 insertions(+), 35 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-seclabel-dynamic-labelskip.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-seclabel-dynamic-labelskip.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-seclabel-static-labelskip.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-seclabel-static-labelskip.xml create mode 100644 tests/qemuxml2xmloutdata/qemuxml2xmlout-seclabel-dynamic-labelskip.xml diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in index 3fd83c9..4308dbe 100644 --- a/docs/formatdomain.html.in +++ b/docs/formatdomain.html.in @@ -5129,7 +5129,11 @@ qemu-kvm -net nic,model=? /dev/null a seclabel element is attached to a specific path rather than the top-level domain assignment, only the attribute relabel or the - sub-element label are supported. + sub-element label are supported. Additionally, + since 1.1.2, an output-only + element labelskip will be present for active + domains on disks where labeling was skipped due to the image + being on a file system that lacks security labeling.

Example configs

diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index ac807e6..dfcd61c 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -152,34 +152,35 @@ + relabel=no or a diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 53daf73..d6b4ea7 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -4484,7 +4484,8 @@ static int virSecurityDeviceLabelDefParseXML(virSecurityDeviceLabelDefPtr **seclabels_rtn, size_t *nseclabels_rtn, virSecurityLabelDefPtr *vmSeclabels, - int nvmSeclabels, xmlXPathContextPtr ctxt) + int nvmSeclabels, xmlXPathContextPtr ctxt, + unsigned int flags) { virSecurityDeviceLabelDefPtr *seclabels; size_t nseclabels = 0; @@ -4492,7 +4493,7 @@ virSecurityDeviceLabelDefParseXML(virSecurityDeviceLabelDefPtr **seclabels_rtn, size_t i, j; xmlNodePtr *list = NULL; virSecurityLabelDefPtr vmDef = NULL; - char *model, *relabel, *label; + char *model, *relabel, *label, *labelskip; if ((n = virXPathNodeSet("./seclabel", ctxt, &list)) < 0) goto error; @@ -4547,6 +4548,13 @@ virSecurityDeviceLabelDefParseXML(virSecurityDeviceLabelDefPtr **seclabels_rtn, seclabels[i]->norelabel = false; } + /* labelskip is only parsed on live images */ + labelskip = virXMLPropString(list[i], "labelskip"); + seclabels[i]->labelskip = false; + if (labelskip && !(flags & VIR_DOMAIN_XML_INACTIVE)) + seclabels[i]->labelskip = STREQ(labelskip, "yes"); + VIR_FREE(labelskip); + ctxt->node = list[i]; label = virXPathStringLimit("string(./label)", VIR_SECURITY_LABEL_BUFLEN-1, ctxt); @@ -5208,7 +5216,8 @@ virDomainDiskDefParseXML(virDomainXMLOptionPtr xmlopt, &def->nseclabels, vmSeclabels, nvmSeclabels, - ctxt) < 0) + ctxt, + flags) < 0) goto error; ctxt->node = saved_node; } @@ -6884,7 +6893,8 @@ virDomainChrSourceDefParseXML(virDomainChrSourceDefPtr def, &chr_def->nseclabels, vmSeclabels, nvmSeclabels, - ctxt) < 0) { + ctxt, + flags) < 0) { ctxt->node = saved_node; goto error; } @@ -14028,14 +14038,23 @@ virSecurityLabelDefFormat(virBufferPtr buf, virSecurityLabelDefPtr def) static void virSecurityDeviceLabelDefFormat(virBufferPtr buf, - virSecurityDeviceLabelDefPtr def) + virSecurityDeviceLabelDefPtr def, + unsigned int flags) { + /* For offline output, skip elements that allow labels but have no + * label specified (possible if labelskip was ignored on input). */ + if ((flags & VIR_DOMAIN_XML_INACTIVE) && !def->label && !def->norelabel) + return; + virBufferAddLit(buf, "model) virBufferAsprintf(buf, " model='%s'", def->model); - virBufferAsprintf(buf, " relabel='%s'", def->norelabel ? "no" : "yes"); + if (def->labelskip) + virBufferAddLit(buf, " labelskip='yes'"); + else + virBufferAsprintf(buf, " relabel='%s'", def->norelabel ? "no" : "yes"); if (def->label) { virBufferAddLit(buf, ">\n"); @@ -14110,7 +14129,8 @@ virDomainDiskBlockIoDefFormat(virBufferPtr buf, static int virDomainDiskSourceDefFormat(virBufferPtr buf, - virDomainDiskDefPtr def) + virDomainDiskDefPtr def, + unsigned int flags) { int n; const char *startupPolicy = virDomainStartupPolicyTypeToString(def->startupPolicy); @@ -14129,7 +14149,8 @@ virDomainDiskSourceDefFormat(virBufferPtr buf, virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 8); for (n = 0; n < def->nseclabels; n++) - virSecurityDeviceLabelDefFormat(buf, def->seclabels[n]); + virSecurityDeviceLabelDefFormat(buf, def->seclabels[n], + flags); virBufferAdjustIndent(buf, -8); virBufferAddLit(buf, " \n"); } else { @@ -14146,7 +14167,8 @@ virDomainDiskSourceDefFormat(virBufferPtr buf, virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 8); for (n = 0; n < def->nseclabels; n++) - virSecurityDeviceLabelDefFormat(buf, def->seclabels[n]); + virSecurityDeviceLabelDefFormat(buf, def->seclabels[n], + flags); virBufferAdjustIndent(buf, -8); virBufferAddLit(buf, " \n"); } else { @@ -14211,7 +14233,8 @@ virDomainDiskSourceDefFormat(virBufferPtr buf, virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 8); for (n = 0; n < def->nseclabels; n++) - virSecurityDeviceLabelDefFormat(buf, def->seclabels[n]); + virSecurityDeviceLabelDefFormat(buf, def->seclabels[n], + flags); virBufferAdjustIndent(buf, -8); virBufferAddLit(buf, " \n"); } else { @@ -14347,7 +14370,7 @@ virDomainDiskDefFormat(virBufferPtr buf, virBufferAddLit(buf, " \n"); } - if (virDomainDiskSourceDefFormat(buf, def) < 0) + if (virDomainDiskSourceDefFormat(buf, def, flags) < 0) return -1; virDomainDiskGeometryDefFormat(buf, def); virDomainDiskBlockIoDefFormat(buf, def); @@ -15199,7 +15222,7 @@ virDomainChrDefFormat(virBufferPtr buf, if (def->seclabels && def->nseclabels > 0) { virBufferAdjustIndent(buf, 2); for (n = 0; n < def->nseclabels; n++) - virSecurityDeviceLabelDefFormat(buf, def->seclabels[n]); + virSecurityDeviceLabelDefFormat(buf, def->seclabels[n], flags); virBufferAdjustIndent(buf, -2); } diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 29ef0f8..b9e9600 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -343,7 +343,8 @@ typedef virSecurityDeviceLabelDef *virSecurityDeviceLabelDefPtr; struct _virSecurityDeviceLabelDef { char *model; char *label; /* image label string */ - bool norelabel; + bool norelabel; /* true to skip label attempts */ + bool labelskip; /* live-only; true if skipping failed label attempt */ }; diff --git a/src/security/security_selinux.c b/src/security/security_selinux.c index e3dce66a..38de060 100644 --- a/src/security/security_selinux.c +++ b/src/security/security_selinux.c @@ -917,10 +917,10 @@ virSecuritySELinuxSetFileconHelper(const char *path, char *tcon, bool optional) security_get_boolean_active("virt_use_nfs") != 1) { msg = _("Setting security context '%s' on '%s' not supported. " "Consider setting virt_use_nfs"); - if (security_getenforce() == 1) - VIR_WARN(msg, tcon, path); - else - VIR_INFO(msg, tcon, path); + if (security_getenforce() == 1) + VIR_WARN(msg, tcon, path); + else + VIR_INFO(msg, tcon, path); } else { VIR_INFO("Setting security context '%s' on '%s' not supported", tcon, path); @@ -1135,6 +1135,14 @@ virSecuritySELinuxRestoreSecurityImageLabelInt(virSecurityManagerPtr mgr, if (seclabel->norelabel || (disk_seclabel && disk_seclabel->norelabel)) return 0; + /* If labelskip is true and there are no backing files, then we + * know it is safe to skip the restore. FIXME - backing files should + * be tracked in domain XML, at which point labelskip should be a + * per-file attribute instead of a disk attribute. */ + if (disk_seclabel && disk_seclabel->labelskip && + !disk->backingChain) + return 0; + /* Don't restore labels on readoly/shared disks, because * other VMs may still be accessing these * Alternatively we could iterate over all running @@ -1219,7 +1227,7 @@ virSecuritySELinuxSetSecurityFileLabel(virDomainDiskDefPtr disk, disk_seclabel = virDomainDiskDefGenSecurityLabelDef(SECURITY_SELINUX_NAME); if (!disk_seclabel) return -1; - disk_seclabel->norelabel = true; + disk_seclabel->labelskip = true; if (VIR_APPEND_ELEMENT(disk->seclabels, disk->nseclabels, disk_seclabel) < 0) { virSecurityDeviceLabelDefFree(disk_seclabel); diff --git a/tests/qemuxml2argvdata/qemuxml2argv-seclabel-dynamic-labelskip.args b/tests/qemuxml2argvdata/qemuxml2argv-seclabel-dynamic-labelskip.args new file mode 100644 index 0000000..892c6b5 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-seclabel-dynamic-labelskip.args @@ -0,0 +1,5 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test /usr/bin/qemu \ +-name QEMUGuest1 -S -M pc -m 214 -smp 1 -nographic -monitor \ +unix:/tmp/test-monitor,server,nowait -no-acpi -boot c -usb \ +-hda /dev/HostVG/QEMUGuest1 \ +-net none -serial none -parallel none diff --git a/tests/qemuxml2argvdata/qemuxml2argv-seclabel-dynamic-labelskip.xml b/tests/qemuxml2argvdata/qemuxml2argv-seclabel-dynamic-labelskip.xml new file mode 100644 index 0000000..e3bc700 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-seclabel-dynamic-labelskip.xml @@ -0,0 +1,32 @@ + + QEMUGuest1 + c7a5fdbd-edaf-9455-926a-d65c16db1809 + 219100 + 219100 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu + + + + + +
+ + + + + + + + system_u:system_r:svirt_custom_t:s0 + + diff --git a/tests/qemuxml2argvdata/qemuxml2argv-seclabel-static-labelskip.args b/tests/qemuxml2argvdata/qemuxml2argv-seclabel-static-labelskip.args new file mode 100644 index 0000000..892c6b5 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-seclabel-static-labelskip.args @@ -0,0 +1,5 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test /usr/bin/qemu \ +-name QEMUGuest1 -S -M pc -m 214 -smp 1 -nographic -monitor \ +unix:/tmp/test-monitor,server,nowait -no-acpi -boot c -usb \ +-hda /dev/HostVG/QEMUGuest1 \ +-net none -serial none -parallel none diff --git a/tests/qemuxml2argvdata/qemuxml2argv-seclabel-static-labelskip.xml b/tests/qemuxml2argvdata/qemuxml2argv-seclabel-static-labelskip.xml new file mode 100644 index 0000000..a743448 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-seclabel-static-labelskip.xml @@ -0,0 +1,33 @@ + + QEMUGuest1 + c7a5fdbd-edaf-9455-926a-d65c16db1809 + 219100 + 219100 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu + + + + + +
+ + + + + + + + + system_u:system_r:svirt_custom_t:s0:c192,c392 + + diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 679124e..3a3c304 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -923,8 +923,10 @@ mymain(void) DO_TEST("seclabel-dynamic", QEMU_CAPS_NAME); DO_TEST("seclabel-dynamic-baselabel", QEMU_CAPS_NAME); DO_TEST("seclabel-dynamic-override", QEMU_CAPS_NAME); + DO_TEST("seclabel-dynamic-labelskip", QEMU_CAPS_NAME); DO_TEST("seclabel-static", QEMU_CAPS_NAME); DO_TEST("seclabel-static-relabel", QEMU_CAPS_NAME); + DO_TEST("seclabel-static-labelskip", QEMU_CAPS_NAME); DO_TEST("seclabel-none", QEMU_CAPS_NAME); DO_TEST("pseries-basic", diff --git a/tests/qemuxml2xmloutdata/qemuxml2xmlout-seclabel-dynamic-labelskip.xml b/tests/qemuxml2xmloutdata/qemuxml2xmlout-seclabel-dynamic-labelskip.xml new file mode 100644 index 0000000..0764691 --- /dev/null +++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-seclabel-dynamic-labelskip.xml @@ -0,0 +1,31 @@ + + QEMUGuest1 + c7a5fdbd-edaf-9455-926a-d65c16db1809 + 219100 + 219100 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu + + + + +
+ + + + + + + + system_u:system_r:svirt_custom_t:s0 + + diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index 5c6730d..6eebc68 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -30,6 +30,7 @@ testCompareXMLToXMLFiles(const char *inxml, const char *outxml, bool live) char *actual = NULL; int ret = -1; virDomainDefPtr def = NULL; + unsigned int flags = live ? 0 : VIR_DOMAIN_XML_INACTIVE; if (virtTestLoadFile(inxml, &inXmlData) < 0) goto fail; @@ -37,11 +38,10 @@ testCompareXMLToXMLFiles(const char *inxml, const char *outxml, bool live) goto fail; if (!(def = virDomainDefParseString(inXmlData, driver.caps, driver.xmlopt, - QEMU_EXPECTED_VIRT_TYPES, - live ? 0 : VIR_DOMAIN_XML_INACTIVE))) + QEMU_EXPECTED_VIRT_TYPES, flags))) goto fail; - if (!(actual = virDomainDefFormat(def, VIR_DOMAIN_XML_SECURE))) + if (!(actual = virDomainDefFormat(def, VIR_DOMAIN_XML_SECURE | flags))) goto fail; if (STRNEQ(outXmlData, actual)) { @@ -257,7 +257,9 @@ mymain(void) DO_TEST_FULL("seclabel-dynamic-baselabel", false, WHEN_INACTIVE); DO_TEST_FULL("seclabel-dynamic-override", false, WHEN_INACTIVE); + DO_TEST_FULL("seclabel-dynamic-labelskip", true, WHEN_INACTIVE); DO_TEST("seclabel-static"); + DO_TEST_FULL("seclabel-static-labelskip", false, WHEN_ACTIVE); DO_TEST("seclabel-none"); DO_TEST("numad-static-vcpu-no-numatune"); DO_TEST("disk-scsi-lun-passthrough-sgio"); -- 1.8.3.2