On 11/1/21 6:23 PM, Stefan Berger wrote: > Extend the TPM domain XML with an attribute active_pcr_banks that allows > a user to specify the PCR banks to activate before starting a VM. A comma- > separated list of PCR banks with the choices of sha1, sha256, sha384 and > sha512 is allowed. When the XML attribute is provided, the set of active > PCR banks is 'enforced' by running swtpm_setup before every start of the > VM. The activation requires that swtpm_setup v0.7 or later is installed > and may not have any effect otherwise. > > <tpm model='tpm-tis'> > <backend type='emulator' version='2.0' active_pcr_banks='sha256,sha384'/> > </tpm> > > Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2016599 > > Signed-off-by: Stefan Berger <stefanb@xxxxxxxxxxxxx> > --- > docs/formatdomain.rst | 12 ++- > docs/schemas/basictypes.rng | 6 ++ > docs/schemas/domaincommon.rng | 5 ++ > src/conf/domain_conf.c | 21 ++++- > src/conf/domain_conf.h | 1 + > src/qemu/qemu_tpm.c | 80 +++++++++++++++++++ > src/util/virtpm.c | 1 + > src/util/virtpm.h | 1 + > tests/qemuxml2argvdata/tpm-emulator-tpm2.xml | 2 +- > .../tpm-emulator-tpm2.x86_64-latest.xml | 2 +- > 10 files changed, 127 insertions(+), 4 deletions(-) > > diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst > index 0651975c88..8785a7a682 100644 > --- a/docs/formatdomain.rst > +++ b/docs/formatdomain.rst > @@ -7537,7 +7537,7 @@ Example: usage of the TPM Emulator > ... > <devices> > <tpm model='tpm-tis'> > - <backend type='emulator' version='2.0'> > + <backend type='emulator' version='2.0' active_pcr_banks='sha256'> > <encryption secret='6dd3e4a5-1d76-44ce-961f-f119f5aad935'/> > </backend> > </tpm> > @@ -7598,6 +7598,16 @@ Example: usage of the TPM Emulator > This attribute only works with the ``emulator`` backend. The accepted values > are ``yes`` and ``no``. :since:`Since 7.0.0` > > +``active_pcr_banks`` > + The ``active_pcr_banks`` attribute indicates the names of the PCR banks > + of a TPM 2.0 to activate. A comma separated list of PCR banks' names > + must be provided. Valid names are for example sha1, sha256, sha384, and > + sha512. If this attribute is provided, the set of PCR banks are activated > + before every start of a VM and this step is logged in the swtpm's log. > + This attribute requires that swtpm_setup v0.7 or later is installed > + and may not have any effect otherwise. This attribute only works with the > + ``emulator`` backend. since:`Since 7.10.0` > + > ``encryption`` > The ``encryption`` element allows the state of a TPM emulator to be > encrypted. The ``secret`` must reference a secret object that holds the > diff --git a/docs/schemas/basictypes.rng b/docs/schemas/basictypes.rng > index a221ff6295..3bd1eebdc4 100644 > --- a/docs/schemas/basictypes.rng > +++ b/docs/schemas/basictypes.rng > @@ -88,6 +88,12 @@ > </choice> > </define> > > + <define name="pcrBankList"> > + <data type="string"> > + <param name="pattern">(sha1|sha256|sha384|sha512){1}(,(sha1|sha256|sha384|sha512)){0,3}</param> > + </data> > + </define> > + Honestly, I'm not a big fan of comma separated lists. I think we could do with nested elements, repeated for each option. But I'll let others decide that. > <define name="numaDistanceValue"> > <data type="unsignedInt"> > <param name="minInclusive">10</param> > diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng > index 67df13d90d..6801673cf1 100644 > --- a/docs/schemas/domaincommon.rng > +++ b/docs/schemas/domaincommon.rng > @@ -5331,6 +5331,11 @@ > </choice> > </attribute> > </optional> > + <optional> > + <attribute name="active_pcr_banks"> > + <ref name="pcrBankList"/> > + </attribute> > + </optional> > </group> > </choice> > <optional> > diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c > index 4644d18120..bc8237fd0b 100644 > --- a/src/conf/domain_conf.c > +++ b/src/conf/domain_conf.c > @@ -3207,6 +3207,7 @@ void virDomainTPMDefFree(virDomainTPMDef *def) > break; > case VIR_DOMAIN_TPM_TYPE_EMULATOR: > virDomainChrSourceDefClear(&def->data.emulator.source); > + g_free(def->data.emulator.activePcrBanks); > g_free(def->data.emulator.storagepath); > g_free(def->data.emulator.logfile); > break; > @@ -11733,7 +11734,7 @@ virDomainSmartcardDefParseXML(virDomainXMLOption *xmlopt, > * Emulator state encryption is supported with the following: > * > * <tpm model='tpm-tis'> > - * <backend type='emulator' version='2.0'> > + * <backend type='emulator' version='2.0' active_pcr_banks='sha256,sha384'> > * <encryption secret='32ee7e76-2178-47a1-ab7b-269e6e348015'/> > * </backend> > * </tpm> > @@ -11759,6 +11760,7 @@ virDomainTPMDefParseXML(virDomainXMLOption *xmlopt, > g_autofree char *version = NULL; > g_autofree char *secretuuid = NULL; > g_autofree char *persistent_state = NULL; > + g_autofree char *activePcrBanks = NULL; > g_autofree xmlNodePtr *backends = NULL; > > def = g_new0(virDomainTPMDef, 1); > @@ -11841,6 +11843,18 @@ virDomainTPMDefParseXML(virDomainXMLOption *xmlopt, > goto error; > } > } > + if (def->version == VIR_DOMAIN_TPM_VERSION_2_0) { > + activePcrBanks = virXMLPropString(backends[0], "active_pcr_banks"); > + if (activePcrBanks) { > + if (!virStringMatch(activePcrBanks, > + "(sha1|sha256|sha384|sha512)(,(sha1|sha256|sha384|sha512)){0,3}")) { > + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", > + _("Malformatted list of PCR banks")); > + goto error; > + } > + def->data.emulator.activePcrBanks = g_steal_pointer(&activePcrBanks); > + } > + } > break; > case VIR_DOMAIN_TPM_TYPE_LAST: > goto error; > @@ -25433,6 +25447,11 @@ virDomainTPMDefFormat(virBuffer *buf, > virDomainTPMVersionTypeToString(def->version)); > if (def->data.emulator.persistent_state) > virBufferAddLit(buf, " persistent_state='yes'"); > + if (def->version == VIR_DOMAIN_TPM_VERSION_2_0 && > + def->data.emulator.activePcrBanks) { Technically, if version != 2.0 then parser didn't even bother parsing and activePCrBanks would be NULL. > + virBufferAsprintf(buf, " active_pcr_banks='%s'", > + def->data.emulator.activePcrBanks); > + } > if (def->data.emulator.hassecretuuid) { > char uuidstr[VIR_UUID_STRING_BUFLEN]; > virBufferAddLit(buf, ">\n"); > diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h > index cb6d8975b8..19597dba7e 100644 > --- a/src/conf/domain_conf.h > +++ b/src/conf/domain_conf.h > @@ -1381,6 +1381,7 @@ struct _virDomainTPMDef { > unsigned char secretuuid[VIR_UUID_BUFLEN]; > bool hassecretuuid; > bool persistent_state; > + char *activePcrBanks; > } emulator; > } data; > }; > diff --git a/src/qemu/qemu_tpm.c b/src/qemu/qemu_tpm.c > index 93cb04f49d..bb14228edc 100644 > --- a/src/qemu/qemu_tpm.c > +++ b/src/qemu/qemu_tpm.c > @@ -566,6 +566,78 @@ qemuTPMEmulatorRunSetup(const char *storagepath, > } > > > +/* > + * qemuTPMEmulatorReconfigure > + * > + * > + * @storagepath: path to the directory for TPM state > + * @swtpm_user: The userid to switch to when setting up the TPM; > + * typically this should be the uid of 'tss' or 'root' > + * @swtpm_group: The group id to switch to > + * @swtpmActivePcrBanks: The string describing the active PCR banks > + * @logfile: The file to write the log into; it must be writable > + * for the user given by userid or 'tss' > + * @tpmversion: The version of the TPM, either a TPM 1.2 or TPM 2 > + * @secretuuid: The secret's UUID needed for state encryption > + * > + * Reconfigure the active PCR banks of a TPM 2. > + */ > +static int > +qemuTPMEmulatorReconfigure(const char *storagepath, > + uid_t swtpm_user, > + gid_t swtpm_group, > + const char *swtpmActivePcrBanks, > + const char *logfile, > + const virDomainTPMVersion tpmversion, > + const unsigned char *secretuuid) > +{ > + g_autoptr(virCommand) cmd = NULL; > + int exitstatus; > + g_autofree char *swtpm_setup = virTPMGetSwtpmSetup(); > + VIR_AUTOCLOSE pwdfile_fd = -1; > + > + if (!swtpm_setup) > + return -1; > + > + if (tpmversion != VIR_DOMAIN_TPM_VERSION_2_0 || > + swtpmActivePcrBanks == NULL || > + !virTPMSwtpmSetupCapsGet( > + VIR_TPM_SWTPM_SETUP_FEATURE_CMDARG_RECONFIGURE_PCR_BANKS)) > + return 0; > + > + cmd = virCommandNew(swtpm_setup); > + if (!cmd) > + return -1; > + > + virCommandSetUID(cmd, swtpm_user); > + virCommandSetGID(cmd, swtpm_group); > + > + virCommandAddArgList(cmd, "--tpm2", NULL); > + > + if (qemuTPMVirCommandAddEncryption(cmd, swtpm_setup, secretuuid) < 0) > + return -1; > + > + virCommandAddArgList(cmd, > + "--tpm-state", storagepath, > + "--logfile", logfile, > + "--pcr-banks", swtpmActivePcrBanks, > + "--reconfigure", > + NULL); > + > + virCommandClearCaps(cmd); > + > + if (virCommandRun(cmd, &exitstatus) < 0 || exitstatus != 0) { > + virReportError(VIR_ERR_INTERNAL_ERROR, > + _("Could not run '%s --reconfigure'. exitstatus: %d; " > + "Check error log '%s' for details."), > + swtpm_setup, exitstatus, logfile); > + return -1; > + } > + > + return 0; > +} > + > + > /* > * qemuTPMEmulatorBuildCommand: > * > @@ -620,6 +692,14 @@ qemuTPMEmulatorBuildCommand(virDomainTPMDef *tpm, > secretuuid, incomingMigration) < 0) > goto error; > > + if (!incomingMigration && > + qemuTPMEmulatorReconfigure(tpm->data.emulator.storagepath, > + swtpm_user, swtpm_group, > + tpm->data.emulator.activePcrBanks, > + tpm->data.emulator.logfile, tpm->version, > + secretuuid) < 0) So this runs reconfigure on every cold boot of a guest. I wonder whether there's a way to run it just once, when activePcrBanks have changed. For instance, in qemuDomainDefineXMLFlags() the @oldDef is set to the old domain definition and maybe we can use that to compare activePcrBanks and run reconfigure at that time? That won't cover transient domains though, nor it would cover domains which are persistent but are started with a different XML (yes, as horrible as it sounds you can 'virsh define dom1.xml && virsh create dom2.xml' where dom1.xml and dom2.xml have nothing in common except domain <name/> and <uuid/>). Maybe I'm just complicating this needlessly, what do you think? Michal