[libvirt] [RFC PATCH V1 0/5] Add TPM support to Qemu

The following series of patches add support for TPM to the libvirt's parser and Qemu driver. Since the underlying support in Qemu and SeaBIOS are not in repositories yet, I am posting them as RFCs for now. If you have feedback, please let me know. Regards, Stefan

This patch adds the capability to parse and generated the XML of the TPM device. The XML can look like this: <tpm type='built-in'> <storage/> </tpm> without an explicit pointer to a file for persistent storage or like this: <tpm type='built-in'> <storage file='/tmp/tpmstate.bin'/> </tpm> with an explicit file mentioned in the XML. The file must be of type QCoW2 and be of a size that qemu tells the user using: qemu-system-x86_64 -tpm ? Supported TPM types (choose only one): builtin Qemu's built-in TPM; requires 63kb of block storage This patch also provides a function that generates the filename for the storage file if the user did not provide one. Also, the schema extensions for the domain XML is included. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- docs/schemas/domain.rng | 25 +++++ src/conf/domain_conf.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 28 +++++ src/libvirt_private.syms | 5 + 4 files changed, 285 insertions(+) Index: libvirt-acl/src/conf/domain_conf.c =================================================================== --- libvirt-acl.orig/src/conf/domain_conf.c +++ libvirt-acl/src/conf/domain_conf.c @@ -384,6 +384,9 @@ VIR_ENUM_IMPL(virDomainTimerMode, VIR_DO "paravirt", "smpsafe"); +VIR_ENUM_IMPL(virDomainTPM, VIR_DOMAIN_TPM_TYPE_LAST, + "built-in") + #define virDomainReportError(code, ...) \ virReportErrorHelper(NULL, VIR_FROM_DOMAIN, code, __FILE__, \ __FUNCTION__, __LINE__, __VA_ARGS__) @@ -779,6 +782,22 @@ void virDomainVideoDefFree(virDomainVide VIR_FREE(def); } +void virDomainTPMDefFree(virDomainTPMDefPtr def) +{ + if (!def) + return; + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + VIR_FREE(def->data.builtin.storage); + break; + default: + break; + } + + VIR_FREE(def); +} + void virDomainHostdevDefFree(virDomainHostdevDefPtr def) { if (!def) @@ -911,6 +930,7 @@ void virDomainDefFree(virDomainDefPtr de virDomainChrDefFree(def->channels[i]); VIR_FREE(def->channels); + virDomainTPMDefFree(def->tpm); virDomainChrDefFree(def->console); for (i = 0 ; i < def->nsounds ; i++) @@ -3453,6 +3473,130 @@ error: goto cleanup; } + +static char * +virDomainTPMDefaultStorageFile(const unsigned char *vmuuid) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char uuid[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(vmuuid, uuid); + + virBufferVSprintf(&buf, + "%s/lib/libvirt/tpm/%s.bin", + LOCALSTATEDIR, uuid); + + if (virBufferError(&buf)) { + virBufferFreeAndReset(&buf); + virReportOOMError(); + return NULL; + } + + return virBufferContentAndReset(&buf); +} + + +char * +virDomainTPMGetStorageFilename(virDomainTPMDefPtr def, + const unsigned char *vmuuid) +{ + + if (def->data.builtin.storage) + return strdup(def->data.builtin.storage); + else + return virDomainTPMDefaultStorageFile(vmuuid); +} + + +/* Parse the XML definition for a TPM device + * + * The XML we're dealing with looks like + * + * <tpm type="built-in"> + * <storage file='path/to/QCoW2/state/file' /> + * </tpm> + * The 'storage' node is optional. If none is provided, + * libvirt is going to create the necessary storage using + * the VM's UUID as the name of the file. + * + */ +static virDomainTPMDefPtr +virDomainTPMDefParseXML(xmlNodePtr node) { + xmlNodePtr cur; + char *type = NULL; + char *path = NULL; + virDomainTPMDefPtr def; + char *tpmStateDir = NULL; + int err; + + if (VIR_ALLOC(def) < 0) { + virReportOOMError(); + return NULL; + } + + def->type = VIR_DOMAIN_TPM_TYPE_BUILTIN; + type = virXMLPropString(node, "type"); + if (type != NULL) { + if (STREQ(type, "builtin") || STREQ(type, "built-in")) + def->type = VIR_DOMAIN_TPM_TYPE_BUILTIN; + } + + cur = node->children; + while (cur != NULL) { + if (cur->type == XML_ELEMENT_NODE) { + if (xmlStrEqual(cur->name, BAD_CAST "storage")) { + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + if (path == NULL) + path = virXMLPropString(cur, "file"); + break; + default: + break; + } + } + } + cur = cur->next; + } + + if (!path) { + if (virAsprintf(&tpmStateDir, + "%s/lib/libvirt/tpm", LOCALSTATEDIR) < 0) { + virReportOOMError(); + goto error; + } + + if ((err = virFileMakePath(tpmStateDir))) { + virReportSystemError(errno, + _("cannot create TPM state directory '%s'"), + tpmStateDir); + goto error; + } + } + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + def->data.builtin.storage = path; + path = NULL; + break; + + default: + break; + } + +cleanup: + VIR_FREE(type); + VIR_FREE(path); + VIR_FREE(tpmStateDir); + + return def; + +error: + virDomainTPMDefFree(def); + def = NULL; + goto cleanup; +} + + /* Parse the XML definition for a network interface */ static virDomainInputDefPtr virDomainInputDefParseXML(const char *ostype, @@ -5587,6 +5731,14 @@ static virDomainDefPtr virDomainDefParse } VIR_FREE(nodes); + if ((node = virXPathNode("./devices/tpm[1]", ctxt)) != NULL) { + virDomainTPMDefPtr tpm = virDomainTPMDefParseXML(node); + if (!tpm) + goto error; + + def->tpm = tpm; + } + /* analysis of the controller devices */ if ((n = virXPathNodeSet("./devices/controller", ctxt, &nodes)) < 0) { virDomainReportError(VIR_ERR_INTERNAL_ERROR, @@ -7390,6 +7542,39 @@ virDomainSmartcardDefFormat(virBufferPtr } static int +virDomainTPMDefFormat(virBufferPtr buf, + virDomainTPMDefPtr def, + const char *name) +{ + const char *type = virDomainTPMTypeToString(def->type); + + if (!type) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected TPM type " + " %d"), def->type); + return -1; + } + + virBufferVSprintf(buf, " <%s type='%s'>\n", + name, type); + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + if (def->data.builtin.storage) + virBufferEscapeString(buf, " <storage file='%s'/>\n", + def->data.builtin.storage); + break; + + default: + break; + } + + virBufferVSprintf(buf, " </%s>\n", + name); + + return 0; +} + + +static int virDomainSoundDefFormat(virBufferPtr buf, virDomainSoundDefPtr def, int flags) @@ -8205,6 +8390,11 @@ char *virDomainDefFormat(virDomainDefPtr virDomainInputDefFormat(&buf, def->inputs[n], flags) < 0) goto cleanup; + if (def->tpm) { + if (virDomainTPMDefFormat(&buf, def->tpm, "tpm") < 0) + goto cleanup; + } + if (def->ngraphics > 0) { /* If graphics is enabled, add the implicit mouse */ virDomainInputDef autoInput = { @@ -8598,6 +8788,43 @@ cleanup: return ret; } +int virDomainTPMDelete(virDomainObjPtr dom, + bool afterMigration) +{ + int ret = -1; + char *tpmStateFile = NULL; + + if (dom->def->tpm) + if ((tpmStateFile = + virDomainTPMDefaultStorageFile(dom->def->uuid)) == NULL) + goto cleanup; + + /* + * remove tpm state file + * - if libvirt created it (in that case virDomainTPMDefaultStorageFile + * returned the name of a file libvirt may have created) + * - if we were not called due to an finished migration + */ + + if (tpmStateFile && + (!afterMigration || + (afterMigration && !virStorageFileIsSharedFS(tpmStateFile))) && + unlink(tpmStateFile) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("cannot remove TPM state file %s"), + tpmStateFile); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(tpmStateFile); + return ret; +} + + char *virDomainConfigFile(const char *dir, const char *name) { Index: libvirt-acl/src/conf/domain_conf.h =================================================================== --- libvirt-acl.orig/src/conf/domain_conf.h +++ libvirt-acl/src/conf/domain_conf.h @@ -40,6 +40,7 @@ # include "nwfilter_conf.h" # include "macvtap.h" # include "sysinfo.h" +# include "configmake.h" /* Private component of virDomainXMLFlags */ typedef enum { @@ -1009,6 +1010,26 @@ struct _virDomainSnapshotObjList { virHashTable *objs; }; + +enum virDomainTPMModel { + VIR_DOMAIN_TPM_TYPE_BUILTIN, + + VIR_DOMAIN_TPM_TYPE_LAST +}; + +typedef struct _virDomainTPMDef virDomainTPMDef; +typedef virDomainTPMDef *virDomainTPMDefPtr; +struct _virDomainTPMDef { + enum virDomainTPMModel type; + union { + struct { + char *storage; + } builtin; + } data; +}; + + + virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, int newSnapshot); void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def); @@ -1133,6 +1154,7 @@ struct _virDomainDef { virSecurityLabelDef seclabel; virDomainWatchdogDefPtr watchdog; virDomainMemballoonDefPtr memballoon; + virDomainTPMDefPtr tpm; virCPUDefPtr cpu; virSysinfoDefPtr sysinfo; @@ -1215,6 +1237,11 @@ int virDomainDeviceInfoIsSet(virDomainDe void virDomainDeviceInfoClear(virDomainDeviceInfoPtr info); void virDomainDefClearPCIAddresses(virDomainDefPtr def); void virDomainDefClearDeviceAliases(virDomainDefPtr def); +void virDomainTPMDefFree(virDomainTPMDefPtr def); +int virDomainTPMDelete(virDomainObjPtr dom, + bool afterMigration); +char *virDomainTPMGetStorageFilename(virDomainTPMDefPtr def, + const unsigned char *vmuuid); typedef int (*virDomainDeviceInfoCallback)(virDomainDefPtr def, virDomainDeviceInfoPtr dev, @@ -1423,6 +1450,7 @@ VIR_ENUM_DECL(virDomainInputBus) VIR_ENUM_DECL(virDomainGraphics) VIR_ENUM_DECL(virDomainGraphicsSpiceChannelName) VIR_ENUM_DECL(virDomainGraphicsSpiceChannelMode) +VIR_ENUM_DECL(virDomainTPM) /* from libvirt.h */ VIR_ENUM_DECL(virDomainState) VIR_ENUM_DECL(virDomainSeclabel) Index: libvirt-acl/docs/schemas/domain.rng =================================================================== --- libvirt-acl.orig/docs/schemas/domain.rng +++ libvirt-acl/docs/schemas/domain.rng @@ -1724,6 +1724,27 @@ <text/> </element> </define> + <define name="tpm-storage"> + <element name="storage"> + <optional> + <attribute name="file"> + <ref name="filePath"/> + </attribute> + </optional> + </element> + </define> + <define name="tpm"> + <element name="tpm"> + <attribute name="type"> + <choice> + <value>built-in</value> + </choice> + </attribute> + <optional> + <ref name="tpm-storage"/> + </optional> + </element> + </define> <define name="input"> <element name="input"> <attribute name="type"> @@ -1900,6 +1921,7 @@ <ref name="serial"/> <ref name="channel"/> <ref name="smartcard"/> + <ref name="tpm"/> </choice> </zeroOrMore> <optional> Index: libvirt-acl/src/libvirt_private.syms =================================================================== --- libvirt-acl.orig/src/libvirt_private.syms +++ libvirt-acl/src/libvirt_private.syms @@ -320,6 +320,11 @@ virDomainTimerTickpolicyTypeFromString; virDomainTimerTickpolicyTypeToString; virDomainTimerTrackTypeFromString; virDomainTimerTrackTypeToString; +virDomainTPMDefFree; +virDomainTPMDelete; +virDomainTPMGetStorageFilename; +virDomainTPMTypeFromString; +virDomainTPMTypeToString; virDomainVcpupinAdd; virDomainVcpupinFindByVcpu; virDomainVcpupinIsDuplicate;

This patch extends the SELinux and DAC security handlers with support for the TPM's storage file. If the user did not provide an explicit file for TPM data storage, then a dummy-file is created in the drivers here and labeled. Athis is necessary since the creation of the TPM's QCoW2 storage file happens later. There is no code for AppArmor. I am not sure whether it needs explicit support. Obviously I haven't tested it with AppArmor. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- src/security/security_dac.c | 102 ++++++++++++++++++++++++++++++++++++++++ src/security/security_selinux.c | 100 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) Index: libvirt-acl/src/security/security_selinux.c =================================================================== --- libvirt-acl.orig/src/security/security_selinux.c +++ libvirt-acl/src/security/security_selinux.c @@ -522,6 +522,94 @@ SELinuxRestoreSecurityImageLabel(virSecu static int +SELinuxSetSecurityTPMFileLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, + virDomainObjPtr vm, + virDomainTPMDefPtr tpm) +{ + int rc; + char *path; + struct stat statbuf; + FILE *f; + const virSecurityLabelDefPtr secdef = &vm->def->seclabel; + + if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC) + return 0; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + path = virDomainTPMGetStorageFilename(tpm, + vm->def->uuid); + if (stat(path, &statbuf) == -1 && errno == ENOENT) { + f = fopen(path, "w"); + VIR_FORCE_FCLOSE(f); + } + rc = SELinuxSetFilecon(path, vm->def->seclabel.imagelabel); + VIR_FREE(path); + if (rc < 0) + return -1; + break; + + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + return 0; +} + + +static int +SELinuxRestoreSecurityTPMFileLabelInt( + virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, + virDomainObjPtr vm, + virDomainTPMDefPtr tpm, + int migrated) +{ + int rc = 0; + char *path; + struct stat statbuf; + const virSecurityLabelDefPtr secdef = &vm->def->seclabel; + + if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC) + return 0; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + path = virDomainTPMGetStorageFilename(tpm, + vm->def->uuid); + if (stat(path, &statbuf) == 0 && statbuf.st_size == 0) + unlink(path); + + /* If we have a shared FS & doing migrated, we must not + * change ownership, because that kills access on the + * destination host which is sub-optimal for the guest + * VM's I/O attempts :-) + */ + if (migrated) { + rc = virStorageFileIsSharedFS(path); + if (rc < 0) { + VIR_FREE(path); + return -1; + } + if (rc == 1) { + VIR_DEBUG("Skipping image label restore on %s because FS is shared", + path); + VIR_FREE(path); + return 0; + } + } + rc = SELinuxRestoreSecurityFileLabel(path); + VIR_FREE(path); + break; + + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + return rc; +} + + +static int SELinuxSetSecurityFileLabel(virDomainDiskDefPtr disk, const char *path, size_t depth, @@ -854,6 +942,14 @@ SELinuxRestoreSecurityAllLabel(virSecuri if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC) return 0; + if (vm->def->tpm) { + if (SELinuxRestoreSecurityTPMFileLabelInt(mgr, + vm, + vm->def->tpm, + migrated) < 0) + rc = -1; + } + for (i = 0 ; i < vm->def->nhostdevs ; i++) { if (SELinuxRestoreSecurityHostdevLabel(mgr, vm, @@ -1171,6 +1267,10 @@ SELinuxSetSecurityAllLabel(virSecurityMa vm->def->hostdevs[i]) < 0) return -1; } + if (vm->def->tpm) + if (SELinuxSetSecurityTPMFileLabel(mgr, + vm, vm->def->tpm) < 0) + return -1; if (virDomainChrDefForeach(vm->def, true, Index: libvirt-acl/src/security/security_dac.c =================================================================== --- libvirt-acl.orig/src/security/security_dac.c +++ libvirt-acl/src/security/security_dac.c @@ -31,6 +31,7 @@ #include "pci.h" #include "hostusb.h" #include "storage_file.h" +#include "files.h" #define VIR_FROM_THIS VIR_FROM_SECURITY @@ -243,6 +244,94 @@ virSecurityDACRestoreSecurityImageLabel( static int +virSecurityDACSetSecurityTPMFileLabel(virSecurityManagerPtr mgr, + virDomainObjPtr vm, + virDomainTPMDefPtr tpm) +{ + int rc; + char *path; + struct stat statbuf; + FILE *f; + virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr); + + if (!priv->dynamicOwnership) + return 0; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + path = virDomainTPMGetStorageFilename(tpm, + vm->def->uuid); + if (stat(path, &statbuf) == -1 && errno == ENOENT) { + f = fopen(path, "w"); + VIR_FORCE_FCLOSE(f); + } + rc = virSecurityDACSetOwnership(path, priv->user, priv->group); + VIR_FREE(path); + if (rc < 0) + return -1; + break; + + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + return 0; +} + + +static int +virSecurityDACRestoreSecurityTPMFileLabelInt( + virSecurityManagerPtr mgr, + virDomainObjPtr vm, + virDomainTPMDefPtr tpm, + int migrated) +{ + int rc = 0; + char *path; + struct stat statbuf; + virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr); + + if (!priv->dynamicOwnership) + return 0; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + path = virDomainTPMGetStorageFilename(tpm, + vm->def->uuid); + if (stat(path, &statbuf) == 0 && statbuf.st_size == 0) + unlink(path); + + /* If we have a shared FS & doing migrated, we must not + * change ownership, because that kills access on the + * destination host which is sub-optimal for the guest + * VM's I/O attempts :-) + */ + if (migrated) { + rc = virStorageFileIsSharedFS(path); + if (rc < 0) { + VIR_FREE(path); + return -1; + } + if (rc == 1) { + VIR_DEBUG("Skipping image label restore on %s because FS is shared", + path); + VIR_FREE(path); + return 0; + } + } + rc = virSecurityDACRestoreSecurityFileLabel(path); + VIR_FREE(path); + break; + + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + return rc; +} + + +static int virSecurityDACSetSecurityPCILabel(pciDevice *dev ATTRIBUTE_UNUSED, const char *file, void *opaque) @@ -492,6 +581,14 @@ virSecurityDACRestoreSecurityAllLabel(vi VIR_DEBUG("Restoring security label on %s migrated=%d", vm->def->name, migrated); + if (vm->def->tpm) { + if (virSecurityDACRestoreSecurityTPMFileLabelInt(mgr, + vm, + vm->def->tpm, + migrated) < 0) + rc = -1; + } + for (i = 0 ; i < vm->def->nhostdevs ; i++) { if (virSecurityDACRestoreSecurityHostdevLabel(mgr, vm, @@ -561,6 +658,11 @@ virSecurityDACSetSecurityAllLabel(virSec vm->def->hostdevs[i]) < 0) return -1; } + if (vm->def->tpm) + if (virSecurityDACSetSecurityTPMFileLabel(mgr, + vm, + vm->def->tpm) < 0) + return -1; if (virDomainChrDefForeach(vm->def, true,

This patch adds support for TPM for the Qemu driver to - detect whether Qemu supports TPM and then sets the TPM capability flag appropriately - create the Qemu command line to add a TPM to the VM - create a QCoW2 file that holds the TPM persistent state - parse a Qemu command line This patch also moves the function qemuFindQemuImgBinary(void) from qemu_driver to qemu_command and makes it a non-static function. qemu_command seemed a better place for 'public' functions that qemu_driver. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- src/qemu/qemu_capabilities.c | 54 ++++++++++ src/qemu/qemu_capabilities.h | 3 src/qemu/qemu_command.c | 232 +++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_command.h | 1 src/qemu/qemu_driver.c | 37 ++++-- src/qemu/qemu_migration.c | 1 6 files changed, 314 insertions(+), 14 deletions(-) Index: libvirt-acl/src/qemu/qemu_command.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_command.c +++ libvirt-acl/src/qemu/qemu_command.c @@ -910,6 +910,109 @@ int qemuDomainPCIAddressSetNextAddr(qemu return -1; } + +static int qemudTPMCreateBlockStore(const char *tpmstorefile, + const char *qemu_cmd) +{ + int rc = 0; + struct stat statbuf; + char *qemuimg = NULL; + int status; + char filesize[10]; + unsigned int bssize; + const char *argv[] = { qemuimg, "create", "-f", "qcow2", tpmstorefile, + filesize, NULL}; + + if (stat(tpmstorefile, &statbuf) == -1 || statbuf.st_size == 0) { + if (errno == ENOENT || statbuf.st_size == 0) { + /* determine size of qcow2 */ + if (qemuCapsGetTPMBuiltinBSSize(qemu_cmd, + &bssize) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", + _("Could not determine TPM store file size")); + rc = 1; + goto err_exit; + } + + /* create the file */ + qemuimg = qemuFindQemuImgBinary(); + if (!qemuimg) { + rc = 1; + goto err_exit; + } + argv[0] = qemuimg; + snprintf(filesize, sizeof(filesize),"%dk", bssize); + + VIR_DEBUG("Creating BS file %s of size %dkb\n", + tpmstorefile, + bssize); + + if (virRun(argv, &status) != 0 || status != 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("Could not create TPM store file")); + rc = 1; + goto err_exit; + } + } else { + // FIXME: Not sure what to do in all the other error cases + virReportSystemError(errno, "%s", + _("Error while stating TPM blockstore file")); + rc = 1; + } + } else { + VIR_DEBUG("TPM Blockstorage file %s exists\n", tpmstorefile); + } + + err_exit: + VIR_FREE(qemuimg); + return rc; +} + + +static char * qemudBuildCommandLineTPMDevStr(const virDomainDefPtr def, + const char *qemu_cmd) +{ + int rc = 0; + char *tmp = NULL; + const virDomainTPMDefPtr tpm = def->tpm; + virBuffer buf = VIR_BUFFER_INITIALIZER; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + tmp = virDomainTPMGetStorageFilename(tpm, def->uuid); + if (!tmp) { + rc = -1; + goto err_exit; + } + if (qemudTPMCreateBlockStore(tmp, qemu_cmd)) { + rc = -1; + goto err_exit; + } + virBufferVSprintf(&buf, "type=builtin,path=%s", tmp); + if (virBufferError(&buf)) { + virReportOOMError(); + rc = -1; + goto err_exit; + } + break; + + default: + rc = -1; + goto err_exit; + } + + VIR_FREE(tmp); + + return virBufferContentAndReset(&buf); + + err_exit: + virBufferFreeAndReset(&buf); + VIR_FREE(tmp); + return NULL; +} + + /* * This assigns static PCI slots to all configured devices. * The ordering here is chosen to match the ordering used @@ -3831,6 +3934,21 @@ qemuBuildCommandLine(virConnectPtr conn, } } + if (qemuCapsGet(qemuCaps, QEMU_CAPS_TPM)) { + if (!def->tpm) { + virCommandAddArg(cmd, "-tpm"); + virCommandAddArg(cmd, "none"); + } else { + char *optstr; + if (!(optstr = qemudBuildCommandLineTPMDevStr(def, emulator))) + goto error; + + virCommandAddArg(cmd, "-tpm"); + virCommandAddArg(cmd, optstr); + VIR_FREE(optstr); + } + } + virCommandAddArg(cmd, "-usb"); for (i = 0 ; i < def->ninputs ; i++) { virDomainInputDefPtr input = def->inputs[i]; @@ -5508,6 +5626,99 @@ error: static int +qemuParseCommandLineTPM(virDomainDefPtr dom, + const char *val) +{ + int rc = 0; + virDomainTPMDefPtr tpm; + char **keywords; + char **values; + int nkeywords; + int i; + + if (dom->tpm) + goto error; + + nkeywords = qemuParseKeywords(val, &keywords, &values, 1); + if (nkeywords < 0) + goto error; + + if (VIR_ALLOC(tpm) < 0) + goto no_memory; + + tpm->type = VIR_DOMAIN_TPM_TYPE_BUILTIN; + + for (i = 0; i < nkeywords; i++) { + if (STREQ(keywords[i], "type")) { + if (values[i] && STREQ(values[i], "builtin")) + tpm->type = VIR_DOMAIN_TPM_TYPE_BUILTIN; + } else if (STREQ(keywords[i], "path")) { + if (values[i]) { + tpm->data.builtin.storage = values[i]; + values[i] = NULL; + } else + goto syntax; + } + } + + /* sanity checks */ + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + if (!tpm->data.builtin.storage) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", + _("missing required file=<filename> option")); + goto bad_definition; + } + break; + default: + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", + _("unknown TPM type")); + goto bad_definition; + } + + /* all ok */ + dom->tpm = tpm; + +cleanup: + for (i = 0 ; i < nkeywords ; i++) { + VIR_FREE(keywords[i]); + VIR_FREE(values[i]); + } + VIR_FREE(keywords); + VIR_FREE(values); + + + return rc; + +syntax: + virDomainTPMDefFree(tpm); + + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown TPM syntax '%s'"), val); + rc = -1; + goto cleanup; + +bad_definition: + virDomainTPMDefFree(tpm); + + rc = -1; + goto cleanup; + +no_memory: + virReportOOMError(); + + rc = -1; + goto cleanup; + + +error: + return -1; +} + + +static int qemuParseCommandLineSmp(virDomainDefPtr dom, const char *val) { @@ -6141,6 +6352,12 @@ virDomainDefPtr qemuParseCommandLine(vir /* ignore, used internally by libvirt */ } else if (STREQ(arg, "-S")) { /* ignore, always added by libvirt */ + } else if (STREQ(arg, "-tpm")) { + WANT_VALUE(); + if (STRNEQ(val, "none")) { + if (qemuParseCommandLineTPM(def, val) < 0) + goto error; + } } else { /* something we can't yet parse. Add it to the qemu namespace * cmdline/environment advanced options and hope for the best @@ -6330,3 +6547,18 @@ cleanup: return def; } + + +char *qemuFindQemuImgBinary(void) +{ + char *ret; + + ret = virFindFileInPath("kvm-img"); + if (ret == NULL) + ret = virFindFileInPath("qemu-img"); + if (ret == NULL) + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find kvm-img or qemu-img")); + + return ret; +} Index: libvirt-acl/src/qemu/qemu_command.h =================================================================== --- libvirt-acl.orig/src/qemu/qemu_command.h +++ libvirt-acl/src/qemu/qemu_command.h @@ -172,5 +172,6 @@ qemuParseKeywords(const char *str, char ***retvalues, int allowEmptyValue); +char *qemuFindQemuImgBinary(void); #endif /* __QEMU_COMMAND_H__*/ Index: libvirt-acl/src/qemu/qemu_driver.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_driver.c +++ libvirt-acl/src/qemu/qemu_driver.c @@ -1268,6 +1268,7 @@ static virDomainPtr qemudDomainCreate(vi (flags & VIR_DOMAIN_START_PAUSED) != 0, -1, NULL, VIR_VM_OP_CREATE) < 0) { qemuAuditDomainStart(vm, "booted", false); + virDomainTPMDelete(vm, false); if (qemuDomainObjEndJob(vm) > 0) virDomainRemoveInactive(&driver->domains, vm); @@ -1496,6 +1497,7 @@ static int qemudDomainDestroy(virDomainP qemuAuditDomainStop(vm, "destroyed"); if (!vm->persistent) { + virDomainTPMDelete(vm, false); if (qemuDomainObjEndJob(vm) > 0) virDomainRemoveInactive(&driver->domains, vm); @@ -3693,6 +3695,8 @@ static int qemudDomainUndefine(virDomain goto cleanup; } + virDomainTPMDelete(vm, false); + if (virDomainDeleteConfig(driver->configDir, driver->autostartDir, vm) < 0) goto cleanup; @@ -5856,20 +5860,6 @@ cleanup: return ret; } -static char *qemuFindQemuImgBinary(void) -{ - char *ret; - - ret = virFindFileInPath("kvm-img"); - if (ret == NULL) - ret = virFindFileInPath("qemu-img"); - if (ret == NULL) - qemuReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("unable to find kvm-img or qemu-img")); - - return ret; -} - static int qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm, virDomainSnapshotObjPtr snapshot, char *snapshotDir) @@ -6474,6 +6464,7 @@ static int qemuDomainSnapshotDiscard(str int i; qemuDomainObjPrivatePtr priv; virDomainSnapshotObjPtr parentsnap; + virDomainTPMDefPtr tpm; if (!virDomainObjIsActive(vm)) { qemuimgarg[0] = qemuFindQemuImgBinary(); @@ -6504,6 +6495,24 @@ static int qemuDomainSnapshotDiscard(str } } } + + tpm = vm->def->tpm; + if (tpm) { + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + qemuimgarg[4] = virDomainTPMGetStorageFilename(tpm, + vm->def->uuid); + if (virRun(qemuimgarg, NULL) < 0) { + /* we continue on even in the face of error + */ + } + VIR_FREE(qemuimgarg[4]); + break; + + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + } } else { priv = vm->privateData; Index: libvirt-acl/src/qemu/qemu_migration.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_migration.c +++ libvirt-acl/src/qemu/qemu_migration.c @@ -1090,6 +1090,7 @@ int qemuMigrationPerform(struct qemud_dr VIR_DOMAIN_EVENT_STOPPED_MIGRATED); if (!vm->persistent || (flags & VIR_MIGRATE_UNDEFINE_SOURCE)) { virDomainDeleteConfig(driver->configDir, driver->autostartDir, vm); + virDomainTPMDelete(vm, true); if (qemuDomainObjEndJob(vm) > 0) virDomainRemoveInactive(&driver->domains, vm); vm = NULL; Index: libvirt-acl/src/qemu/qemu_capabilities.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_capabilities.c +++ libvirt-acl/src/qemu/qemu_capabilities.c @@ -40,6 +40,7 @@ #include <sys/wait.h> #include <sys/utsname.h> #include <stdarg.h> +#include <c-ctype.h> #define VIR_FROM_THIS VIR_FROM_QEMU @@ -808,6 +809,8 @@ qemuCapsComputeCmdFlags(const char *help qemuCapsSet(flags, QEMU_CAPS_XEN_DOMID); else if (strstr(help, "-domid")) qemuCapsSet(flags, QEMU_CAPS_DOMID); + if (strstr(help, "-tpm")) + qemuCapsSet(flags, QEMU_CAPS_TPM); if (strstr(help, "-drive")) { qemuCapsSet(flags, QEMU_CAPS_DRIVE); if (strstr(help, "cache=") && @@ -1238,6 +1241,57 @@ int qemuCapsExtractVersion(virCapsPtr ca } +int qemuCapsGetTPMBuiltinBSSize(const char *qemu, + unsigned int *bssize) +{ + int ret = -1; + char *output = NULL, *pos; + virCommandPtr cmd; + + /* Make sure the binary we are about to try exec'ing exists. + * Technically we could catch the exec() failure, but that's + * in a sub-process so it's hard to feed back a useful error. + */ + if (virFileIsExecutable(qemu) < 0) { + virReportSystemError(errno, _("Cannot find QEMU binary %s"), qemu); + return -1; + } + + cmd = virCommandNewArgList(qemu, "-tpm", "?", NULL); + virCommandAddEnvPassCommon(cmd); + virCommandSetOutputBuffer(cmd, &output); + virCommandClearCaps(cmd); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + pos = strstr(output, "builtin"); + if (!pos) + goto cleanup; + + while (1) { + if (*pos == '\n' || *pos == 0) + goto cleanup; + + if (c_isdigit(*pos)) { + if (sscanf(pos, "%dkb", bssize) != 1) + goto cleanup; + else + break; + } + pos++; + } + + ret = 0; + +cleanup: + VIR_FREE(output); + virCommandFree(cmd); + + return ret; +} + + virBitmapPtr qemuCapsNew(void) { Index: libvirt-acl/src/qemu/qemu_capabilities.h =================================================================== --- libvirt-acl.orig/src/qemu/qemu_capabilities.h +++ libvirt-acl/src/qemu/qemu_capabilities.h @@ -95,6 +95,7 @@ enum qemuCapsFlags { QEMU_CAPS_DEVICE_SPICEVMC = 57, /* older -device spicevmc*/ QEMU_CAPS_VIRTIO_TX_ALG = 58, /* -device virtio-net-pci,tx=string */ QEMU_CAPS_DEVICE_QXL_VGA = 59, /* Is the primary and vga campatible qxl device named qxl-vga? */ + QEMU_CAPS_TPM = 60, /* if TPM (-tpm) support is available*/ QEMU_CAPS_LAST, /* this must always be the last item */ }; @@ -141,5 +142,7 @@ int qemuCapsParseHelpStr(const char *qem int qemuCapsParseDeviceStr(const char *str, virBitmapPtr qemuCaps); +int qemuCapsGetTPMBuiltinBSSize(const char *qemu, + unsigned int *size); #endif /* __QEMU_CAPABILITIES_H__*/

This patch adds support for encrypted block storage for the TPM's block storage. Specifically it - extends the XML parser and generator to handle the extended TPM XML referring to a 'secret'. It can handle the following XML <tpm type='built-in'> <storage> <encryption format='qcow'> <secret type='passphrase' uuid='13ea49f7-2553-7308-5168-e337ade36232'/> </encryption> </storage> </tpm> For this it relies on existing functions originally written for storage encryption. - extends the code creating the block storage file to create an encrypted QCoW2 - extends the montior to pass the encryption key to Qemu so it can access the encrypted data; - handles the secret associated with the TPM storage; much of the code around the handling of the secret has been recycled from existing storage handling code Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- docs/schemas/domain.rng | 3 src/conf/domain_conf.c | 53 ++++++++++++ src/conf/domain_conf.h | 1 src/qemu/qemu_command.c | 24 ++++- src/qemu/qemu_driver.c | 2 src/qemu/qemu_monitor.c | 16 +++ src/qemu/qemu_monitor.h | 13 +++ src/qemu/qemu_monitor_text.c | 6 + src/qemu/qemu_process.c | 176 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 285 insertions(+), 9 deletions(-) Index: libvirt-acl/src/qemu/qemu_monitor.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_monitor.c +++ libvirt-acl/src/qemu/qemu_monitor.c @@ -797,6 +797,22 @@ int qemuMonitorGetDiskSecret(qemuMonitor } +int qemuMonitorGetTPMSecret(qemuMonitorPtr mon, + virConnectPtr conn, + const char *path, + char **secret, + size_t *secretLen) +{ + int ret = -1; + *secret = NULL; + *secretLen = 0; + + QEMU_MONITOR_CALLBACK(mon, ret, tpmSecretLookup, conn, mon->vm, + path, secret, secretLen); + return ret; +} + + int qemuMonitorEmitShutdown(qemuMonitorPtr mon) { int ret = -1; Index: libvirt-acl/src/qemu/qemu_monitor.h =================================================================== --- libvirt-acl.orig/src/qemu/qemu_monitor.h +++ libvirt-acl/src/qemu/qemu_monitor.h @@ -81,6 +81,13 @@ struct _qemuMonitorCallbacks { char **secret, size_t *secretLen); + int (*tpmSecretLookup)(qemuMonitorPtr mon, + virConnectPtr conn, + virDomainObjPtr vm, + const char *path, + char **secret, + size_t *secretLen); + int (*domainShutdown)(qemuMonitorPtr mon, virDomainObjPtr vm); int (*domainReset)(qemuMonitorPtr mon, @@ -152,6 +159,12 @@ int qemuMonitorGetDiskSecret(qemuMonitor char **secret, size_t *secretLen); +int qemuMonitorGetTPMSecret(qemuMonitorPtr mon, + virConnectPtr conn, + const char *path, + char **secret, + size_t *secretLen); + int qemuMonitorEmitShutdown(qemuMonitorPtr mon); int qemuMonitorEmitReset(qemuMonitorPtr mon); int qemuMonitorEmitPowerdown(qemuMonitorPtr mon); Index: libvirt-acl/src/qemu/qemu_monitor_text.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_monitor_text.c +++ libvirt-acl/src/qemu/qemu_monitor_text.c @@ -315,6 +315,12 @@ qemuMonitorSendDiskPassphrase(qemuMonito path, &passphrase, &passphrase_len); + if (res < 0) + res = qemuMonitorGetTPMSecret(mon, + conn, + path, + &passphrase, + &passphrase_len); VIR_FREE(path); if (res < 0) return -1; Index: libvirt-acl/src/qemu/qemu_driver.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_driver.c +++ libvirt-acl/src/qemu/qemu_driver.c @@ -187,6 +187,8 @@ qemuAutostartDomains(struct qemud_driver virConnectClose(conn); } + + static int qemuSecurityInit(struct qemud_driver *driver) { Index: libvirt-acl/src/conf/domain_conf.h =================================================================== --- libvirt-acl.orig/src/conf/domain_conf.h +++ libvirt-acl/src/conf/domain_conf.h @@ -1024,6 +1024,7 @@ struct _virDomainTPMDef { union { struct { char *storage; + virStorageEncryptionPtr encryption; } builtin; } data; }; Index: libvirt-acl/src/conf/domain_conf.c =================================================================== --- libvirt-acl.orig/src/conf/domain_conf.c +++ libvirt-acl/src/conf/domain_conf.c @@ -790,6 +790,7 @@ void virDomainTPMDefFree(virDomainTPMDef switch (def->type) { case VIR_DOMAIN_TPM_TYPE_BUILTIN: VIR_FREE(def->data.builtin.storage); + virStorageEncryptionFree(def->data.builtin.encryption); break; default: break; @@ -3513,7 +3514,12 @@ virDomainTPMGetStorageFilename(virDomain * The XML we're dealing with looks like * * <tpm type="built-in"> - * <storage file='path/to/QCoW2/state/file' /> + * <storage file='path_to_state_file' > + * <encryption format='qcow'> + * <secret type='passphrase' + * uuid='13ea49f7-2553-7308-5168-e337ade36232'/> + * </encryption> + * </storage> * </tpm> * The 'storage' node is optional. If none is provided, * libvirt is going to create the necessary storage using @@ -3522,12 +3528,13 @@ virDomainTPMGetStorageFilename(virDomain */ static virDomainTPMDefPtr virDomainTPMDefParseXML(xmlNodePtr node) { - xmlNodePtr cur; + xmlNodePtr cur, stor; char *type = NULL; char *path = NULL; virDomainTPMDefPtr def; char *tpmStateDir = NULL; int err; + virStorageEncryptionPtr encryption = NULL; if (VIR_ALLOC(def) < 0) { virReportOOMError(); @@ -3553,6 +3560,30 @@ virDomainTPMDefParseXML(xmlNodePtr node) default: break; } + stor = cur->children; + while (stor != NULL) { + if (stor->type == XML_ELEMENT_NODE) { + if (encryption == NULL && + xmlStrEqual(stor->name, BAD_CAST "encryption")) { + encryption = virStorageEncryptionParseNode( + cur->doc, + stor); + if (encryption == NULL) + goto error; + + if (encryption->format != + VIR_STORAGE_ENCRYPTION_FORMAT_QCOW) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + _("encryption format type must be " + "'qcow'")); + virStorageEncryptionFree(encryption); + encryption = NULL; + goto error; + } + } + } + stor = stor->next; + } } } cur = cur->next; @@ -3577,6 +3608,8 @@ virDomainTPMDefParseXML(xmlNodePtr node) case VIR_DOMAIN_TPM_TYPE_BUILTIN: def->data.builtin.storage = path; path = NULL; + def->data.builtin.encryption = encryption; + encryption = NULL; break; default: @@ -3587,6 +3620,7 @@ cleanup: VIR_FREE(type); VIR_FREE(path); VIR_FREE(tpmStateDir); + virStorageEncryptionFree(encryption); return def; @@ -7546,6 +7580,7 @@ virDomainTPMDefFormat(virBufferPtr buf, virDomainTPMDefPtr def, const char *name) { + virStorageEncryptionPtr enc; const char *type = virDomainTPMTypeToString(def->type); if (!type) { @@ -7558,9 +7593,21 @@ virDomainTPMDefFormat(virBufferPtr buf, name, type); switch (def->type) { case VIR_DOMAIN_TPM_TYPE_BUILTIN: + enc = def->data.builtin.encryption; + + virBufferVSprintf(buf, " <storage"); + if (def->data.builtin.storage) - virBufferEscapeString(buf, " <storage file='%s'/>\n", + virBufferEscapeString(buf, " file='%s'", def->data.builtin.storage); + + virBufferVSprintf(buf, "%s>\n", enc ? "" : "/"); + + if (enc) { + if (virStorageEncryptionFormat(buf, enc, 8) < 0) + return -1; + virBufferVSprintf( buf, " </storage>\n"); + } break; default: Index: libvirt-acl/src/qemu/qemu_command.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_command.c +++ libvirt-acl/src/qemu/qemu_command.c @@ -912,7 +912,8 @@ int qemuDomainPCIAddressSetNextAddr(qemu static int qemudTPMCreateBlockStore(const char *tpmstorefile, - const char *qemu_cmd) + const char *qemu_cmd, + bool encrypted) { int rc = 0; struct stat statbuf; @@ -920,12 +921,13 @@ static int qemudTPMCreateBlockStore(cons int status; char filesize[10]; unsigned int bssize; - const char *argv[] = { qemuimg, "create", "-f", "qcow2", tpmstorefile, + const char *argv[] = { qemuimg, "create", "-f", "qcow2", + "-o", "encryption", tpmstorefile, filesize, NULL}; if (stat(tpmstorefile, &statbuf) == -1 || statbuf.st_size == 0) { if (errno == ENOENT || statbuf.st_size == 0) { - /* determine size of qcow2 */ + /* determine necessary size; qemu -tpm ? tells us */ if (qemuCapsGetTPMBuiltinBSSize(qemu_cmd, &bssize) < 0) { qemuReportError(VIR_ERR_INTERNAL_ERROR, @@ -944,9 +946,16 @@ static int qemudTPMCreateBlockStore(cons argv[0] = qemuimg; snprintf(filesize, sizeof(filesize),"%dk", bssize); - VIR_DEBUG("Creating BS file %s of size %dkb\n", + VIR_DEBUG("Creating BS file %s of size %dkb; encrypted: %d\n", tpmstorefile, - bssize); + bssize, + encrypted); + + if (!encrypted) { + argv[4] = argv[6]; + argv[5] = argv[7]; + argv[6] = NULL; + } if (virRun(argv, &status) != 0 || status != 0) { qemuReportError(VIR_ERR_INTERNAL_ERROR, @@ -985,7 +994,10 @@ static char * qemudBuildCommandLineTPMDe rc = -1; goto err_exit; } - if (qemudTPMCreateBlockStore(tmp, qemu_cmd)) { + if (qemudTPMCreateBlockStore(tmp, qemu_cmd, + tpm->data.builtin.encryption + ? true + : false)) { rc = -1; goto err_exit; } Index: libvirt-acl/src/qemu/qemu_process.c =================================================================== --- libvirt-acl.orig/src/qemu/qemu_process.c +++ libvirt-acl/src/qemu/qemu_process.c @@ -297,6 +297,144 @@ cleanup: } +static virDomainTPMDefPtr +findDomainTPMByPath(virDomainObjPtr vm, + const char *path) +{ + virDomainTPMDefPtr res = vm->def->tpm; +#if 0 + char *tmp; + + if (res) { + tmp = virDomainTPMGetStorageFilename(res, vm->def->uuid); + if (!STREQ(tmp, path)) + res = NULL; + + VIR_FREE(tmp); + } +#else + (void)path; +#endif + + return res; +} + +static int +getTPMPassphrase(virConnectPtr conn, + virDomainTPMDefPtr tpm, + char **secretRet, + size_t *secretLen) +{ + virSecretPtr secret; + char *passphrase; + unsigned char *data; + size_t size; + int ret = -1; + virStorageEncryptionPtr enc; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + if (!tpm->data.builtin.encryption) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("tpm does not have any encryption information")); + return -1; + } + enc = tpm->data.builtin.encryption; + break; + + default: + return -1; + } + + if (!conn) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("cannot find secrets without a connection")); + goto cleanup; + } + + if (conn->secretDriver == NULL || + conn->secretDriver->lookupByUUID == NULL || + conn->secretDriver->getValue == NULL) { + qemuReportError(VIR_ERR_NO_SUPPORT, "%s", + _("secret storage not supported")); + goto cleanup; + } + + if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW || + enc->nsecrets != 1 || + enc->secrets[0]->type != + VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE) { + qemuReportError(VIR_ERR_XML_ERROR, + _("invalid <encryption>")); + goto cleanup; + } + + secret = conn->secretDriver->lookupByUUID(conn, + enc->secrets[0]->uuid); + if (secret == NULL) + goto cleanup; + data = conn->secretDriver->getValue(secret, &size, + VIR_SECRET_GET_VALUE_INTERNAL_CALL); + virUnrefSecret(secret); + if (data == NULL) + goto cleanup; + + if (memchr(data, '\0', size) != NULL) { + memset(data, 0, size); + VIR_FREE(data); + qemuReportError(VIR_ERR_XML_ERROR, + _("format='qcow' passphrase for %s must not contain a " + "'\\0'"), "TPM"); + goto cleanup; + } + + if (VIR_ALLOC_N(passphrase, size + 1) < 0) { + memset(data, 0, size); + VIR_FREE(data); + virReportOOMError(); + goto cleanup; + } + memcpy(passphrase, data, size); + passphrase[size] = '\0'; + + memset(data, 0, size); + VIR_FREE(data); + + *secretRet = passphrase; + *secretLen = size; + + ret = 0; + +cleanup: + return ret; +} + +static int +qemuProcessFindTPMPassphrase(qemuMonitorPtr mon ATTRIBUTE_UNUSED, + virConnectPtr conn, + virDomainObjPtr vm, + const char *path, + char **secretRet, + size_t *secretLen) +{ + virDomainTPMDefPtr tpm; + int ret = -1; + + virDomainObjLock(vm); + tpm = findDomainTPMByPath(vm, path); + + if (!tpm) + goto cleanup; + + ret = getTPMPassphrase(conn, tpm, secretRet, secretLen); + +cleanup: + virDomainObjUnlock(vm); + return ret; +} + + + static int qemuProcessHandleReset(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm) @@ -613,6 +751,7 @@ static qemuMonitorCallbacks monitorCallb .destroy = qemuProcessHandleMonitorDestroy, .eofNotify = qemuProcessHandleMonitorEOF, .diskSecretLookup = qemuProcessFindVolumeQcowPassphrase, + .tpmSecretLookup = qemuProcessFindTPMPassphrase, .domainShutdown = qemuProcessHandleShutdown, .domainStop = qemuProcessHandleStop, .domainReset = qemuProcessHandleReset, @@ -1268,6 +1407,43 @@ qemuProcessInitPasswords(virConnectPtr c } } + while (qemuCapsGet(qemuCaps, QEMU_CAPS_TPM)) { + virDomainTPMDefPtr tpm = vm->def->tpm; + virStorageEncryptionPtr enc = NULL; + char *secret; + size_t secretLen; + + if (!tpm) + break; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_BUILTIN: + enc = tpm->data.builtin.encryption; + break; + + default: + break; + } + + if (!enc) + break; + + if (getTPMPassphrase(conn, tpm, + &secret, &secretLen) < 0) + goto cleanup; + + qemuDomainObjEnterMonitorWithDriver(driver, vm); + ret = qemuMonitorSetDrivePassphrase(priv->mon, + "vtpm-nvram", + secret); + VIR_FREE(secret); + qemuDomainObjExitMonitorWithDriver(driver, vm); + if (ret < 0) + goto cleanup; + + break; + } + cleanup: return ret; } Index: libvirt-acl/docs/schemas/domain.rng =================================================================== --- libvirt-acl.orig/docs/schemas/domain.rng +++ libvirt-acl/docs/schemas/domain.rng @@ -1731,6 +1731,9 @@ <ref name="filePath"/> </attribute> </optional> + <optional> + <ref name="encryption"/> + </optional> </element> </define> <define name="tpm">

This patch adds several test cases for the parsing and Qemu command line generation for the previously added TPM support. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- tests/qemuxml2argvdata/qemuxml2argv-tpm-encrypted-bs.args | 6 ++ tests/qemuxml2argvdata/qemuxml2argv-tpm-encrypted-bs.xml | 30 ++++++++++++++ tests/qemuxml2argvdata/qemuxml2argv-tpm-file.args | 6 ++ tests/qemuxml2argvdata/qemuxml2argv-tpm-file.xml | 26 ++++++++++++ tests/qemuxml2argvdata/qemuxml2argv-tpm.args | 6 ++ tests/qemuxml2argvdata/qemuxml2argv-tpm.xml | 26 ++++++++++++ tests/qemuxml2argvtest.c | 3 + tests/qemuxml2xmltest.c | 4 + 8 files changed, 107 insertions(+) Index: libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm.xml =================================================================== --- /dev/null +++ libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm.xml @@ -0,0 +1,26 @@ +<domain type='qemu'> + <name>TPM-VM</name> + <uuid>a4d7cd22-da89-3094-6212-079a48a309a1</uuid> + <memory>2097152</memory> + <currentMemory>512288</currentMemory> + <vcpu>1</vcpu> + <os> + <type arch='x86_64' machine='pc-0.12'>hvm</type> + <boot dev='hd'/> + <bootmenu enable='yes'/> + </os> + <features> + <acpi/> + </features> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <tpm type='built-in'> + <storage/> + </tpm> + <memballoon model='virtio'/> + </devices> +</domain> Index: libvirt-acl/tests/qemuxml2argvtest.c =================================================================== --- libvirt-acl.orig/tests/qemuxml2argvtest.c +++ libvirt-acl/tests/qemuxml2argvtest.c @@ -496,6 +496,9 @@ mymain(int argc, char **argv) DO_TEST("blkiotune", false, QEMU_CAPS_NAME); DO_TEST("cputune", false, QEMU_CAPS_NAME); + DO_TEST("tpm", false, QEMU_CAPS_TPM); + DO_TEST("tpm-encrypted-bs", false, QEMU_CAPS_TPM); + free(driver.stateDir); virCapabilitiesFree(driver.caps); Index: libvirt-acl/tests/qemuxml2xmltest.c =================================================================== --- libvirt-acl.orig/tests/qemuxml2xmltest.c +++ libvirt-acl/tests/qemuxml2xmltest.c @@ -189,6 +189,10 @@ mymain(int argc, char **argv) DO_TEST("smp"); + DO_TEST("tpm"); + DO_TEST("tpm-file"); + DO_TEST("tpm-encrypted-bs"); + /* These tests generate different XML */ DO_TEST_DIFFERENT("balloon-device-auto"); DO_TEST_DIFFERENT("channel-virtio-auto"); Index: libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm.args =================================================================== --- /dev/null +++ libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm.args @@ -0,0 +1,6 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test \ +/usr/bin/qemu -S -M pc-0.12 -m 2048 -smp 1 -nographic \ +-monitor unix:/tmp/test-monitor,server,nowait -boot c -net none \ +-serial none -parallel none \ +-tpm type=builtin,path=/var/lib/libvirt/tpm/a4d7cd22-da89-3094-6212-079a48a309a1.bin \ +-usb Index: libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm-encrypted-bs.args =================================================================== --- /dev/null +++ libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm-encrypted-bs.args @@ -0,0 +1,6 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test \ +/usr/bin/qemu -S -M pc-0.12 -m 2048 -smp 1 -nographic \ +-monitor unix:/tmp/test-monitor,server,nowait -boot c -net none \ +-serial none -parallel none \ +-tpm type=builtin,path=/var/lib/libvirt/tpm/a4d7cd22-da89-3094-6212-079a48a309a1.bin \ +-usb Index: libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm-encrypted-bs.xml =================================================================== --- /dev/null +++ libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm-encrypted-bs.xml @@ -0,0 +1,30 @@ +<domain type='qemu'> + <name>TPM-VM</name> + <uuid>a4d7cd22-da89-3094-6212-079a48a309a1</uuid> + <memory>2097152</memory> + <currentMemory>512288</currentMemory> + <vcpu>1</vcpu> + <os> + <type arch='x86_64' machine='pc-0.12'>hvm</type> + <boot dev='hd'/> + <bootmenu enable='yes'/> + </os> + <features> + <acpi/> + </features> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <tpm type='built-in'> + <storage> + <encryption format='qcow'> + <secret type='passphrase' uuid='13ea49f7-2553-7308-5168-e337ade36232'/> + </encryption> + </storage> + </tpm> + <memballoon model='virtio'/> + </devices> +</domain> Index: libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm-file.args =================================================================== --- /dev/null +++ libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm-file.args @@ -0,0 +1,6 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test \ +/usr/bin/qemu -S -M pc-0.12 -m 2048 -smp 1 -nographic \ +-monitor unix:/tmp/test-monitor,server,nowait -boot c -net none \ +-serial none -parallel none \ +-tpm type=builtin,path=/tmp/tpmstate.bin \ +-usb Index: libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm-file.xml =================================================================== --- /dev/null +++ libvirt-acl/tests/qemuxml2argvdata/qemuxml2argv-tpm-file.xml @@ -0,0 +1,26 @@ +<domain type='qemu'> + <name>TPM-VM</name> + <uuid>a4d7cd22-da89-3094-6212-079a48a309a1</uuid> + <memory>2097152</memory> + <currentMemory>512288</currentMemory> + <vcpu>1</vcpu> + <os> + <type arch='x86_64' machine='pc-0.12'>hvm</type> + <boot dev='hd'/> + <bootmenu enable='yes'/> + </os> + <features> + <acpi/> + </features> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <tpm type='built-in'> + <storage file='/tmp/tpmstate.bin'/> + </tpm> + <memballoon model='virtio'/> + </devices> +</domain>
participants (1)
-
Stefan Berger