[libvirt] [PATCH V1 0/6] Add support for guests with TPM passthrough device

Hello! The following set of patches adds support to libvirt for adding a TPM passthrough device to a QEMU guest. Support for this was recently accepted into QEMU. This set of patches borrows a lot from the recently added support for rng's. This patch series builds on top of the patches for adding support for file descriptor sets. Regards, Stefan

Probe for QEMU's QMP TPM support by querying the lists of supported TPM models (query-tpm-models) and backend types (query-tpm-types). The setting of the capability flags following the strings returned from the commands above is only provided in the patch where domain_conf.c gets TPM support due to dependencies on functions only introduced there. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- src/libvirt_private.syms | 1 src/qemu/qemu_capabilities.c | 61 ++++++++++++++++++++++++++++ src/qemu/qemu_capabilities.h | 3 + src/qemu/qemu_monitor.c | 44 ++++++++++++++++++++ src/qemu/qemu_monitor.h | 6 ++ src/qemu/qemu_monitor_json.c | 91 +++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 8 +++ src/util/virutil.c | 14 ++++++ src/util/virutil.h | 3 + 9 files changed, 231 insertions(+) Index: libvirt/src/qemu/qemu_capabilities.c =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.c +++ libvirt/src/qemu/qemu_capabilities.c @@ -210,6 +210,8 @@ VIR_ENUM_IMPL(virQEMUCaps, QEMU_CAPS_LAS "rng-random", /* 130 */ "rng-egd", + "tpm-passthrough", + "tpm-tis", ); struct _virQEMUCaps { Index: libvirt/src/qemu/qemu_capabilities.h =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.h +++ libvirt/src/qemu/qemu_capabilities.h @@ -171,6 +171,8 @@ enum virQEMUCapsFlags { QEMU_CAPS_OBJECT_RNG_RANDOM = 130, /* the rng-random backend for virtio rng */ QEMU_CAPS_OBJECT_RNG_EGD = 131, /* EGD protocol daemon for rng */ + QEMU_CAPS_DEVICE_TPM_PASSTHROUGH = 132, /* -tpmdev passthrough */ + QEMU_CAPS_DEVICE_TPM_TIS = 133, /* -device tpm_tis */ QEMU_CAPS_LAST, /* this must always be the last item */ }; @@ -252,4 +254,5 @@ int virQEMUCapsParseDeviceStr(virQEMUCap VIR_ENUM_DECL(virQEMUCaps); bool virQEMUCapsUsedQMP(virQEMUCapsPtr qemuCaps); + #endif /* __QEMU_CAPABILITIES_H__*/ Index: libvirt/src/qemu/qemu_monitor.c =================================================================== --- libvirt.orig/src/qemu/qemu_monitor.c +++ libvirt/src/qemu/qemu_monitor.c @@ -3522,3 +3522,47 @@ int qemuMonitorNBDServerStop(qemuMonitor return qemuMonitorJSONNBDServerStop(mon); } + + +int qemuMonitorGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels) +{ + VIR_DEBUG("mon=%p tpmmodels=%p", + mon, tpmmodels); + + if (!mon) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("monitor must not be NULL")); + return -1; + } + + if (!mon->json) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("JSON monitor is required")); + return -1; + } + + return qemuMonitorJSONGetTPMModels(mon, tpmmodels); +} + + +int qemuMonitorGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes) +{ + VIR_DEBUG("mon=%p tpmtypes=%p", + mon, tpmtypes); + + if (!mon) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("monitor must not be NULL")); + return -1; + } + + if (!mon->json) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("JSON monitor is required")); + return -1; + } + + return qemuMonitorJSONGetTPMTypes(mon, tpmtypes); +} Index: libvirt/src/qemu/qemu_monitor.h =================================================================== --- libvirt.orig/src/qemu/qemu_monitor.h +++ libvirt/src/qemu/qemu_monitor.h @@ -683,6 +683,12 @@ int qemuMonitorNBDServerAdd(qemuMonitorP const char *deviceID, bool writable); int qemuMonitorNBDServerStop(qemuMonitorPtr); +int qemuMonitorGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels); + +int qemuMonitorGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes); + /** * When running two dd process and using <> redirection, we need a * shell that will not truncate files. These two strings serve that Index: libvirt/src/qemu/qemu_monitor_json.c =================================================================== --- libvirt.orig/src/qemu/qemu_monitor_json.c +++ libvirt/src/qemu/qemu_monitor_json.c @@ -4707,3 +4707,94 @@ qemuMonitorJSONNBDServerStop(qemuMonitor virJSONValueFree(reply); return ret; } + + +static int +qemuMonitorJSONGetStringArray(qemuMonitorPtr mon, const char *qmpCmd, + char ***array) +{ + int ret; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + virJSONValuePtr data; + char **list = NULL; + int n = 0; + size_t i; + + *array = NULL; + + if (!(cmd = qemuMonitorJSONMakeCommand(qmpCmd, NULL))) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + if (ret < 0) + goto cleanup; + + ret = -1; + + if (!(data = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s reply was missing return data"), + qmpCmd); + goto cleanup; + } + + if ((n = virJSONValueArraySize(data)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s reply data was not an array"), + qmpCmd); + goto cleanup; + } + + if (VIR_ALLOC_N(list, n) < 0) { + virReportOOMError(); + goto cleanup; + } + + for (i = 0 ; i < n ; i++) { + virJSONValuePtr child = virJSONValueArrayGet(data, i); + const char *tmp; + + if (!(tmp = virJSONValueGetString(child))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s array element does not contain data"), + qmpCmd); + goto cleanup; + } + + if (!(list[i] = strdup(tmp))) { + virReportOOMError(); + goto cleanup; + } + } + + ret = n; + *array = list; + +cleanup: + if (ret < 0 && list) { + for (i = 0 ; i < n ; i++) + VIR_FREE(list[i]); + VIR_FREE(list); + } + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +int qemuMonitorJSONGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-models", tpmmodels); +} + + +int qemuMonitorJSONGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-types", tpmtypes); +} Index: libvirt/src/qemu/qemu_monitor_json.h =================================================================== --- libvirt.orig/src/qemu/qemu_monitor_json.h +++ libvirt/src/qemu/qemu_monitor_json.h @@ -341,4 +341,12 @@ int qemuMonitorJSONNBDServerAdd(qemuMoni const char *deviceID, bool writable); int qemuMonitorJSONNBDServerStop(qemuMonitorPtr mon); +int qemuMonitorJSONGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels) + ATTRIBUTE_NONNULL(2); + +int qemuMonitorJSONGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes) + ATTRIBUTE_NONNULL(2); + #endif /* QEMU_MONITOR_JSON_H */ Index: libvirt/src/libvirt_private.syms =================================================================== --- libvirt.orig/src/libvirt_private.syms +++ libvirt/src/libvirt_private.syms @@ -1886,6 +1886,7 @@ virSetUIDGIDWithCaps; virSkipSpaces; virSkipSpacesAndBackslash; virSkipSpacesBackwards; +virStrArrayHasString; virStrcpy; virStrIsPrint; virStrncpy; Index: libvirt/src/util/virutil.c =================================================================== --- libvirt.orig/src/util/virutil.c +++ libvirt/src/util/virutil.c @@ -3379,3 +3379,17 @@ cleanup: VIR_FREE(buf); return ret; } + + +bool +virStrArrayHasString(char **strings, size_t n_strings, const char *needle) +{ + size_t i; + + for (i = 0; i < n_strings; i++) { + if (STREQ(strings[i], needle)) + return true; + } + + return false; +} Index: libvirt/src/util/virutil.h =================================================================== --- libvirt.orig/src/util/virutil.h +++ libvirt/src/util/virutil.h @@ -297,4 +297,7 @@ int virGetDeviceUnprivSGIO(const char *p char * virGetUnprivSGIOSysfsPath(const char *path, const char *sysfs_dir); +bool virStrArrayHasString(char **strings, size_t n_strings, + const char *needle); + #endif /* __VIR_UTIL_H__ */

On Wed, Mar 13, 2013 at 12:03:49PM -0400, Stefan Berger wrote:
Probe for QEMU's QMP TPM support by querying the lists of supported TPM models (query-tpm-models) and backend types (query-tpm-types).
The setting of the capability flags following the strings returned from the commands above is only provided in the patch where domain_conf.c gets TPM support due to dependencies on functions only introduced there.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- src/libvirt_private.syms | 1 src/qemu/qemu_capabilities.c | 61 ++++++++++++++++++++++++++++ src/qemu/qemu_capabilities.h | 3 + src/qemu/qemu_monitor.c | 44 ++++++++++++++++++++ src/qemu/qemu_monitor.h | 6 ++ src/qemu/qemu_monitor_json.c | 91 +++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 8 +++ src/util/virutil.c | 14 ++++++ src/util/virutil.h | 3 + 9 files changed, 231 insertions(+)
Index: libvirt/src/qemu/qemu_capabilities.c =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.c +++ libvirt/src/qemu/qemu_capabilities.c @@ -210,6 +210,8 @@ VIR_ENUM_IMPL(virQEMUCaps, QEMU_CAPS_LAS
"rng-random", /* 130 */ "rng-egd", + "tpm-passthrough", + "tpm-tis", );
struct _virQEMUCaps { Index: libvirt/src/qemu/qemu_capabilities.h =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.h +++ libvirt/src/qemu/qemu_capabilities.h @@ -171,6 +171,8 @@ enum virQEMUCapsFlags { QEMU_CAPS_OBJECT_RNG_RANDOM = 130, /* the rng-random backend for virtio rng */ QEMU_CAPS_OBJECT_RNG_EGD = 131, /* EGD protocol daemon for rng */ + QEMU_CAPS_DEVICE_TPM_PASSTHROUGH = 132, /* -tpmdev passthrough */ + QEMU_CAPS_DEVICE_TPM_TIS = 133, /* -device tpm_tis */
I'm wondering whether we really need to spend 2 capability bits on this. Is it possible to build QEMU such that we have one, but not the other ? If not then one capability would suffice
Index: libvirt/src/qemu/qemu_monitor.c =================================================================== --- libvirt.orig/src/qemu/qemu_monitor.c +++ libvirt/src/qemu/qemu_monitor.c @@ -3522,3 +3522,47 @@ int qemuMonitorNBDServerStop(qemuMonitor
return qemuMonitorJSONNBDServerStop(mon); } + + +int qemuMonitorGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels) +{ + VIR_DEBUG("mon=%p tpmmodels=%p", + mon, tpmmodels); + + if (!mon) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("monitor must not be NULL")); + return -1; + } + + if (!mon->json) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("JSON monitor is required")); + return -1; + } + + return qemuMonitorJSONGetTPMModels(mon, tpmmodels); +} + + +int qemuMonitorGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes) +{ + VIR_DEBUG("mon=%p tpmtypes=%p", + mon, tpmtypes); + + if (!mon) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("monitor must not be NULL")); + return -1; + } + + if (!mon->json) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("JSON monitor is required")); + return -1; + } + + return qemuMonitorJSONGetTPMTypes(mon, tpmtypes); +} Index: libvirt/src/qemu/qemu_monitor.h =================================================================== --- libvirt.orig/src/qemu/qemu_monitor.h +++ libvirt/src/qemu/qemu_monitor.h @@ -683,6 +683,12 @@ int qemuMonitorNBDServerAdd(qemuMonitorP const char *deviceID, bool writable); int qemuMonitorNBDServerStop(qemuMonitorPtr); +int qemuMonitorGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels); + +int qemuMonitorGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes); + /** * When running two dd process and using <> redirection, we need a * shell that will not truncate files. These two strings serve that Index: libvirt/src/qemu/qemu_monitor_json.c =================================================================== --- libvirt.orig/src/qemu/qemu_monitor_json.c +++ libvirt/src/qemu/qemu_monitor_json.c @@ -4707,3 +4707,94 @@ qemuMonitorJSONNBDServerStop(qemuMonitor virJSONValueFree(reply); return ret; } + + +static int +qemuMonitorJSONGetStringArray(qemuMonitorPtr mon, const char *qmpCmd, + char ***array) +{ + int ret; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + virJSONValuePtr data; + char **list = NULL; + int n = 0; + size_t i; + + *array = NULL; + + if (!(cmd = qemuMonitorJSONMakeCommand(qmpCmd, NULL))) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + if (ret < 0) + goto cleanup; + + ret = -1; + + if (!(data = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s reply was missing return data"), + qmpCmd); + goto cleanup; + }
I think everything above this point should be in the main monitor APIs, and this method concern itself solely with converting the virJSONValuePtr into a string array, not actually executing command. This makes the code more portable for use in commands which need control over the command execution to handle errors / pass arguments.
+ + if ((n = virJSONValueArraySize(data)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s reply data was not an array"), + qmpCmd); + goto cleanup; + } + + if (VIR_ALLOC_N(list, n) < 0) { + virReportOOMError(); + goto cleanup; + } + + for (i = 0 ; i < n ; i++) { + virJSONValuePtr child = virJSONValueArrayGet(data, i); + const char *tmp; + + if (!(tmp = virJSONValueGetString(child))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s array element does not contain data"), + qmpCmd); + goto cleanup; + } + + if (!(list[i] = strdup(tmp))) { + virReportOOMError(); + goto cleanup; + } + } + + ret = n; + *array = list; + +cleanup: + if (ret < 0 && list) { + for (i = 0 ; i < n ; i++) + VIR_FREE(list[i]); + VIR_FREE(list); + } + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +int qemuMonitorJSONGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-models", tpmmodels); +} + + +int qemuMonitorJSONGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-types", tpmtypes); +}
Index: libvirt/src/libvirt_private.syms =================================================================== --- libvirt.orig/src/libvirt_private.syms +++ libvirt/src/libvirt_private.syms @@ -1886,6 +1886,7 @@ virSetUIDGIDWithCaps; virSkipSpaces; virSkipSpacesAndBackslash; virSkipSpacesBackwards; +virStrArrayHasString; virStrcpy; virStrIsPrint; virStrncpy; Index: libvirt/src/util/virutil.c =================================================================== --- libvirt.orig/src/util/virutil.c +++ libvirt/src/util/virutil.c @@ -3379,3 +3379,17 @@ cleanup: VIR_FREE(buf); return ret; } + + +bool +virStrArrayHasString(char **strings, size_t n_strings, const char *needle) +{ + size_t i; + + for (i = 0; i < n_strings; i++) { + if (STREQ(strings[i], needle)) + return true; + } + + return false; +} Index: libvirt/src/util/virutil.h =================================================================== --- libvirt.orig/src/util/virutil.h +++ libvirt/src/util/virutil.h @@ -297,4 +297,7 @@ int virGetDeviceUnprivSGIO(const char *p char * virGetUnprivSGIOSysfsPath(const char *path, const char *sysfs_dir);
+bool virStrArrayHasString(char **strings, size_t n_strings, + const char *needle); + #endif /* __VIR_UTIL_H__ */
The virStrArrayHasString method doesn't appear to be used in this patch. If some other patch needs this still, then it should be a separate patch from the monitor code. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 03/14/2013 10:20 AM, Daniel P. Berrange wrote:
Probe for QEMU's QMP TPM support by querying the lists of supported TPM models (query-tpm-models) and backend types (query-tpm-types).
The setting of the capability flags following the strings returned from the commands above is only provided in the patch where domain_conf.c gets TPM support due to dependencies on functions only introduced there.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- src/libvirt_private.syms | 1 src/qemu/qemu_capabilities.c | 61 ++++++++++++++++++++++++++++ src/qemu/qemu_capabilities.h | 3 + src/qemu/qemu_monitor.c | 44 ++++++++++++++++++++ src/qemu/qemu_monitor.h | 6 ++ src/qemu/qemu_monitor_json.c | 91 +++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 8 +++ src/util/virutil.c | 14 ++++++ src/util/virutil.h | 3 + 9 files changed, 231 insertions(+)
Index: libvirt/src/qemu/qemu_capabilities.c =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.c +++ libvirt/src/qemu/qemu_capabilities.c @@ -210,6 +210,8 @@ VIR_ENUM_IMPL(virQEMUCaps, QEMU_CAPS_LAS
"rng-random", /* 130 */ "rng-egd", + "tpm-passthrough", + "tpm-tis", );
struct _virQEMUCaps { Index: libvirt/src/qemu/qemu_capabilities.h =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.h +++ libvirt/src/qemu/qemu_capabilities.h @@ -171,6 +171,8 @@ enum virQEMUCapsFlags { QEMU_CAPS_OBJECT_RNG_RANDOM = 130, /* the rng-random backend for virtio rng */ QEMU_CAPS_OBJECT_RNG_EGD = 131, /* EGD protocol daemon for rng */ + QEMU_CAPS_DEVICE_TPM_PASSTHROUGH = 132, /* -tpmdev passthrough */ + QEMU_CAPS_DEVICE_TPM_TIS = 133, /* -device tpm_tis */ I'm wondering whether we really need to spend 2 capability bits on
On Wed, Mar 13, 2013 at 12:03:49PM -0400, Stefan Berger wrote: this. Is it possible to build QEMU such that we have one, but not the other ? If not then one capability would suffice
I had it with a single capability bit QEMU_CAPS_DEVICE_TPM *and* was reading the strings about the supported models and backend types from QEMU via QMP and held them in qemuCaps. When building the command line I then compared the intended model and backend type to be used against the list held in qemuCaps. That went fine until I wrote the test. There we end up using a pseudo executable (/usr/bin/qemu or so) as well and the test failed because it wasn't probed for the models and types. So I ended up changing the code so that I can set those bits inside the tests rather than having to set the needed strings in the array. Presumably query-tpm-models and query-tpm-lists allows us to report an error from libvirt about non-availability of a model/backend type rather than having QEMU report an error which we reflect to the user. + +static int +qemuMonitorJSONGetStringArray(qemuMonitorPtr mon, const char *qmpCmd, + char ***array) +{ + int ret; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + virJSONValuePtr data; + char **list = NULL; + int n = 0; + size_t i; + + *array = NULL; + + if (!(cmd = qemuMonitorJSONMakeCommand(qmpCmd, NULL))) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + if (ret < 0) + goto cleanup; + + ret = -1; + + if (!(data = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s reply was missing return data"), + qmpCmd); + goto cleanup; + }
I think everything above this point should be in the main monitor APIs, and this method concern itself solely with converting the virJSONValuePtr into a string array, not actually executing command. This makes the code more portable for use in commands which need control over the command execution to handle errors / pass arguments.
Hm, this surprises me since this would put JSON specific code into the monitor APIs (qemu_monitor.c) while qemu_monitor.c typically only serves as a dispatcher for either text monitor or json monitor.
Index: libvirt/src/libvirt_private.syms=================================================================== --- libvirt.orig/src/libvirt_private.syms +++ libvirt/src/libvirt_private.syms @@ -1886,6 +1886,7 @@ virSetUIDGIDWithCaps; virSkipSpaces; virSkipSpacesAndBackslash; virSkipSpacesBackwards; +virStrArrayHasString; virStrcpy; virStrIsPrint; virStrncpy; Index: libvirt/src/util/virutil.c =================================================================== --- libvirt.orig/src/util/virutil.c +++ libvirt/src/util/virutil.c @@ -3379,3 +3379,17 @@ cleanup: VIR_FREE(buf); return ret; } + + +bool +virStrArrayHasString(char **strings, size_t n_strings, const char *needle) +{ + size_t i; + + for (i = 0; i < n_strings; i++) { + if (STREQ(strings[i], needle)) + return true; + } + + return false; +} Index: libvirt/src/util/virutil.h =================================================================== --- libvirt.orig/src/util/virutil.h +++ libvirt/src/util/virutil.h @@ -297,4 +297,7 @@ int virGetDeviceUnprivSGIO(const char *p char * virGetUnprivSGIOSysfsPath(const char *path, const char *sysfs_dir);
+bool virStrArrayHasString(char **strings, size_t n_strings, + const char *needle); + #endif /* __VIR_UTIL_H__ */ The virStrArrayHasString method doesn't appear to be used in this patch. If some other patch needs this still, then it should be a separate patch from the monitor code. Ok, I'll split this off into a patch of its own.
Stefan

On Thu, Mar 14, 2013 at 10:44:32AM -0400, Stefan Berger wrote:
On 03/14/2013 10:20 AM, Daniel P. Berrange wrote:
Probe for QEMU's QMP TPM support by querying the lists of supported TPM models (query-tpm-models) and backend types (query-tpm-types).
The setting of the capability flags following the strings returned from the commands above is only provided in the patch where domain_conf.c gets TPM support due to dependencies on functions only introduced there.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- src/libvirt_private.syms | 1 src/qemu/qemu_capabilities.c | 61 ++++++++++++++++++++++++++++ src/qemu/qemu_capabilities.h | 3 + src/qemu/qemu_monitor.c | 44 ++++++++++++++++++++ src/qemu/qemu_monitor.h | 6 ++ src/qemu/qemu_monitor_json.c | 91 +++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 8 +++ src/util/virutil.c | 14 ++++++ src/util/virutil.h | 3 + 9 files changed, 231 insertions(+)
Index: libvirt/src/qemu/qemu_capabilities.c =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.c +++ libvirt/src/qemu/qemu_capabilities.c @@ -210,6 +210,8 @@ VIR_ENUM_IMPL(virQEMUCaps, QEMU_CAPS_LAS "rng-random", /* 130 */ "rng-egd", + "tpm-passthrough", + "tpm-tis", ); struct _virQEMUCaps { Index: libvirt/src/qemu/qemu_capabilities.h =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.h +++ libvirt/src/qemu/qemu_capabilities.h @@ -171,6 +171,8 @@ enum virQEMUCapsFlags { QEMU_CAPS_OBJECT_RNG_RANDOM = 130, /* the rng-random backend for virtio rng */ QEMU_CAPS_OBJECT_RNG_EGD = 131, /* EGD protocol daemon for rng */ + QEMU_CAPS_DEVICE_TPM_PASSTHROUGH = 132, /* -tpmdev passthrough */ + QEMU_CAPS_DEVICE_TPM_TIS = 133, /* -device tpm_tis */ I'm wondering whether we really need to spend 2 capability bits on
On Wed, Mar 13, 2013 at 12:03:49PM -0400, Stefan Berger wrote: this. Is it possible to build QEMU such that we have one, but not the other ? If not then one capability would suffice
I had it with a single capability bit QEMU_CAPS_DEVICE_TPM *and* was reading the strings about the supported models and backend types from QEMU via QMP and held them in qemuCaps. When building the command line I then compared the intended model and backend type to be used against the list held in qemuCaps. That went fine until I wrote the test. There we end up using a pseudo executable (/usr/bin/qemu or so) as well and the test failed because it wasn't probed for the models and types. So I ended up changing the code so that I can set those bits inside the tests rather than having to set the needed strings in the array.
Presumably query-tpm-models and query-tpm-lists allows us to report an error from libvirt about non-availability of a model/backend type rather than having QEMU report an error which we reflect to the user.
+ +static int +qemuMonitorJSONGetStringArray(qemuMonitorPtr mon, const char *qmpCmd, + char ***array) +{ + int ret; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + virJSONValuePtr data; + char **list = NULL; + int n = 0; + size_t i; + + *array = NULL; + + if (!(cmd = qemuMonitorJSONMakeCommand(qmpCmd, NULL))) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + if (ret < 0) + goto cleanup; + + ret = -1; + + if (!(data = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s reply was missing return data"), + qmpCmd); + goto cleanup; + }
I think everything above this point should be in the main monitor APIs, and this method concern itself solely with converting the virJSONValuePtr into a string array, not actually executing command. This makes the code more portable for use in commands which need control over the command execution to handle errors / pass arguments.
Hm, this surprises me since this would put JSON specific code into the monitor APIs (qemu_monitor.c) while qemu_monitor.c typically only serves as a dispatcher for either text monitor or json monitor.
No, you mis-understand me - I mean move the code into these 2 functions: +int qemuMonitorJSONGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-models", tpmmodels); +} + + +int qemuMonitorJSONGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-types", tpmtypes); +} Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 03/14/2013 10:47 AM, Daniel P. Berrange wrote:
On Thu, Mar 14, 2013 at 10:44:32AM -0400, Stefan Berger wrote:
On 03/14/2013 10:20 AM, Daniel P. Berrange wrote: Hm, this surprises me since this would put JSON specific code into the monitor APIs (qemu_monitor.c) while qemu_monitor.c typically only serves as a dispatcher for either text monitor or json monitor. No, you mis-understand me - I mean move the code into these 2 functions:
That's also what I had at the beginning. It was obviously much duplicated code that looked like as if it could be separated out. Stefan
+int qemuMonitorJSONGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-models", tpmmodels); +} + + +int qemuMonitorJSONGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-types", tpmtypes); +}
Daniel

On 03/14/2013 10:47 AM, Daniel P. Berrange wrote:
On Thu, Mar 14, 2013 at 10:44:32AM -0400, Stefan Berger wrote:
Hm, this surprises me since this would put JSON specific code into the monitor APIs (qemu_monitor.c) while qemu_monitor.c typically only serves as a dispatcher for either text monitor or json monitor.
No, you mis-understand me - I mean move the code into these 2 functions:
Sorry for bothering you about this one. If I was to move the code back into these functions, we'd end up with duplicate code in both functions. It's like having 100 lines of code while 50 + 10 do the same thing... You really want me to do this? Stefan
+int qemuMonitorJSONGetTPMModels(qemuMonitorPtr mon, + char ***tpmmodels) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-models", tpmmodels); +} + + +int qemuMonitorJSONGetTPMTypes(qemuMonitorPtr mon, + char ***tpmtypes) +{ + return qemuMonitorJSONGetStringArray(mon, "query-tpm-types", tpmtypes); +}
Daniel

On Fri, Mar 15, 2013 at 10:28:38AM -0400, Stefan Berger wrote:
On 03/14/2013 10:47 AM, Daniel P. Berrange wrote:
On Thu, Mar 14, 2013 at 10:44:32AM -0400, Stefan Berger wrote:
Hm, this surprises me since this would put JSON specific code into the monitor APIs (qemu_monitor.c) while qemu_monitor.c typically only serves as a dispatcher for either text monitor or json monitor.
No, you mis-understand me - I mean move the code into these 2 functions:
Sorry for bothering you about this one. If I was to move the code back into these functions, we'd end up with duplicate code in both functions. It's like having 100 lines of code while 50 + 10 do the same thing... You really want me to do this?
Yep, this makes the code for converting a JSON response into a string array reusable from other functions that need it. The duplication of the boilerplate for invoking commands is not a new issue - same applies to all functions in this file, so I'm not concerned about that. If we want to figure out a way to minimise it, we should do it across all the APIs. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Wed, Mar 13, 2013 at 12:03:49PM -0400, Stefan Berger wrote:
Index: libvirt/src/util/virutil.c =================================================================== --- libvirt.orig/src/util/virutil.c +++ libvirt/src/util/virutil.c @@ -3379,3 +3379,17 @@ cleanup: VIR_FREE(buf); return ret; } + + +bool +virStrArrayHasString(char **strings, size_t n_strings, const char *needle) +{ + size_t i; + + for (i = 0; i < n_strings; i++) { + if (STREQ(strings[i], needle)) + return true; + } + + return false; +} Index: libvirt/src/util/virutil.h =================================================================== --- libvirt.orig/src/util/virutil.h +++ libvirt/src/util/virutil.h @@ -297,4 +297,7 @@ int virGetDeviceUnprivSGIO(const char *p char * virGetUnprivSGIOSysfsPath(const char *path, const char *sysfs_dir);
+bool virStrArrayHasString(char **strings, size_t n_strings, + const char *needle); +
These shouldn't be in virutil.h - we have a virstring.h. Instead of passing in 'n_strings', we should declare that 'strings' is NULL terminated as we do for other string list APIs. Finally the name should be 'virStringArrayHasString' or 'virStringArrayContains' Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Parse the domain XML with TPM support. Convert the strings from QEMU's QMP TPM commands into capability flags. Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- docs/schemas/domaincommon.rng | 43 ++++++ src/conf/domain_conf.c | 268 ++++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 34 +++++ src/libvirt_private.syms | 5 src/qemu/qemu_capabilities.c | 59 +++++++++ 5 files changed, 409 insertions(+) Index: libvirt/docs/schemas/domaincommon.rng =================================================================== --- libvirt.orig/docs/schemas/domaincommon.rng +++ libvirt/docs/schemas/domaincommon.rng @@ -2814,6 +2814,48 @@ <text/> </element> </define> + + <define name="tpm"> + <element name="tpm"> + <optional> + <attribute name="model"> + <choice> + <value>tpm-tis</value> + </choice> + </attribute> + </optional> + <ref name="tpm-backend"/> + <optional> + <ref name="alias"/> + </optional> + </element> + </define> + + <define name="tpm-backend"> + <element name="backend"> + <choice> + <group> + <attribute name="type"> + <value>passthrough</value> + </attribute> + <ref name="tpm-passthrough-device"/> + </group> + </choice> + </element> + </define> + + <define name="tpm-passthrough-device"> + <optional> + <element name="device"> + <optional> + <attribute name="path"> + <ref name="filePath"/> + </attribute> + </optional> + </element> + </optional> + </define> + <define name="input"> <element name="input"> <attribute name="type"> @@ -3111,6 +3153,7 @@ <ref name="redirdev"/> <ref name="redirfilter"/> <ref name="rng"/> + <ref name="tpm"/> </choice> </zeroOrMore> <optional> Index: libvirt/src/conf/domain_conf.c =================================================================== --- libvirt.orig/src/conf/domain_conf.c +++ libvirt/src/conf/domain_conf.c @@ -709,6 +709,13 @@ VIR_ENUM_IMPL(virDomainRNGBackend, "random", "egd"); +VIR_ENUM_IMPL(virDomainTPMModel, VIR_DOMAIN_TPM_MODEL_LAST, + "tpm-tis") + +VIR_ENUM_IMPL(virDomainTPMBackend, VIR_DOMAIN_TPM_TYPE_LAST, + "passthrough") + + #define VIR_DOMAIN_XML_WRITE_FLAGS VIR_DOMAIN_XML_SECURE #define VIR_DOMAIN_XML_READ_FLAGS VIR_DOMAIN_XML_INACTIVE @@ -1515,6 +1522,24 @@ void virDomainHostdevDefClear(virDomainH } } +void virDomainTPMDefFree(virDomainTPMDefPtr def) +{ + if (!def) + return; + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + VIR_FREE(def->data.passthrough.path); + VIR_FREE(def->data.passthrough.cancel_path); + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + virDomainDeviceInfoClear(&def->info); + VIR_FREE(def); +} + void virDomainHostdevDefFree(virDomainHostdevDefPtr def) { if (!def) @@ -1776,6 +1801,8 @@ void virDomainDefFree(virDomainDefPtr de virDomainRNGDefFree(def->rng); + virDomainTPMDefFree(def->tpm); + VIR_FREE(def->os.type); VIR_FREE(def->os.machine); VIR_FREE(def->os.init); @@ -6312,6 +6339,192 @@ error: goto cleanup; } +/* + * Check whether the given base path, e.g., /sys/class/misc/tpm0/device, + * is the sysfs entry of a TPM. A TPM sysfs entry should be uniquely + * recognizable by the file entries 'pcrs' and 'cancel'. + * Upon success 'true' is returned and the basebath buffer has '/cancel' + * appended. + */ +static bool +virDomainTPMCheckSysfsCancel(char *basepath, size_t bufsz) +{ + char *path = NULL; + struct stat statbuf; + + if (virAsprintf(&path, "%s/pcrs", basepath) < 0) { + virReportOOMError(); + goto error; + } + if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) + goto error; + + VIR_FREE(path); + + if (virAsprintf(&path, "%s/cancel", basepath) < 0) { + virReportOOMError(); + goto error; + } + + if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) + goto error; + + if (!virStrncpy(basepath, path, strlen(path) + 1, bufsz)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Basepath buffer is too small")); + goto error; + } + + VIR_FREE(path); + + return true; + +error: + VIR_FREE(path); + return false; +} + + +static char * +virDomainTPMFindCancelPath(void) +{ + unsigned int idx; + int len; + DIR *pnp_dir; + char path[100], *p; + struct dirent entry, *result; + bool found = false; + + snprintf(path, sizeof(path), "/sys/class/misc"); + pnp_dir = opendir(path); + if (pnp_dir != NULL) { + while (readdir_r(pnp_dir, &entry, &result) == 0 && + result != NULL) { + if (sscanf(entry.d_name, "tpm%u%n", &idx, &len) < 1 || + len <= strlen("tpm") || + len != strlen(entry.d_name)) { + continue; + } + snprintf(path, sizeof(path), "/sys/class/misc/%s/device", + entry.d_name); + if (!virDomainTPMCheckSysfsCancel(path, sizeof(path))) { + continue; + } + + found = true; + break; + } + closedir(pnp_dir); + } + + if (found) { + if (!(p = strdup(path))) + virReportOOMError(); + return p; + } + + return NULL; +} + + +/* Parse the XML definition for a TPM device + * + * The XML looks like this: + * + * <tpm model='tpm-tis'> + * <backend type='passthrough'> + * <device path='/dev/tpm0'/> + * </backend> + * </tpm> + * + */ +static virDomainTPMDefPtr +virDomainTPMDefParseXML(const xmlNodePtr node, + xmlXPathContextPtr ctxt, + unsigned int flags) +{ + char *type = NULL; + char *path = NULL; + char *model = NULL; + char *backend = NULL; + virDomainTPMDefPtr def; + xmlNodePtr save = ctxt->node; + xmlNodePtr *backends = NULL; + int nbackends; + + if (VIR_ALLOC(def) < 0) { + virReportOOMError(); + return NULL; + } + + model = virXMLPropString(node, "model"); + if (model != NULL && + (int)(def->model = virDomainTPMModelTypeFromString(model)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("Unknown TPM frontend model '%s'"), model); + goto error; + } else { + def->model = VIR_DOMAIN_TPM_MODEL_TIS; + } + + ctxt->node = node; + + if ((nbackends = virXPathNodeSet("./backend", ctxt, &backends)) < 0) + goto error; + + if (nbackends > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("only one TPM backend is supported")); + goto error; + } + + if (!(backend = virXMLPropString(backends[0], "type"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing TPM device backend type")); + goto error; + } + + if ((int)(def->type = virDomainTPMBackendTypeFromString(backend)) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Unknown TPM backend type '%s'"), + backend); + goto error; + } + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + path = virXPathString("string(./backend/device/@path)", ctxt); + if (!path && !(path = strdup(VIR_DOMAIN_TPM_DEFAULT_DEVICE))) { + virReportOOMError(); + goto error; + } + def->data.passthrough.path = path; + path = NULL; + /* cancel_path is read-only */ + def->data.passthrough.cancel_path = virDomainTPMFindCancelPath(); + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + goto error; + } + + if (virDomainDeviceInfoParseXML(node, NULL, &def->info, flags) < 0) + goto error; + +cleanup: + VIR_FREE(type); + VIR_FREE(path); + VIR_FREE(model); + VIR_FREE(backend); + VIR_FREE(backends); + ctxt->node = save; + return def; + +error: + virDomainTPMDefFree(def); + def = NULL; + goto cleanup; +} + /* Parse the XML definition for an input device */ static virDomainInputDefPtr virDomainInputDefParseXML(const char *ostype, @@ -10659,6 +10872,23 @@ virDomainDefParseXML(virCapsPtr caps, goto error; VIR_FREE(nodes); } + VIR_FREE(nodes); + + /* Parse the TPM devices */ + if ((n = virXPathNodeSet("./devices/tpm", ctxt, &nodes)) < 0) + goto error; + + if (n > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("only a single TPM device is supported")); + goto error; + } + + if (n > 0) { + if (!(def->tpm = virDomainTPMDefParseXML(nodes[0], ctxt, flags))) + goto error; + } + VIR_FREE(nodes); /* analysis of the hub devices */ if ((n = virXPathNodeSet("./devices/hub", ctxt, &nodes)) < 0) { @@ -13589,6 +13819,39 @@ virDomainSoundCodecDefFormat(virBufferPt } static int +virDomainTPMDefFormat(virBufferPtr buf, + virDomainTPMDefPtr def, + unsigned int flags) +{ + virBufferAsprintf(buf, " <tpm model='%s'>\n", + virDomainTPMModelTypeToString(def->model)); + + virBufferAsprintf(buf, " <backend type='%s'>\n", + virDomainTPMBackendTypeToString(def->type)); + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + virBufferEscapeString(buf, " <device path='%s'/>\n", + def->data.passthrough.path); + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + virBufferAddLit(buf, " </backend>\n"); + + if (virDomainDeviceInfoIsSet(&def->info, flags)) { + if (virDomainDeviceInfoFormat(buf, &def->info, flags) < 0) + return -1; + } + + virBufferAddLit(buf, " </tpm>\n"); + + return 0; +} + + +static int virDomainSoundDefFormat(virBufferPtr buf, virDomainSoundDefPtr def, unsigned int flags) @@ -14905,6 +15168,11 @@ virDomainDefFormatInternal(virDomainDefP virDomainInputDefFormat(buf, def->inputs[n], flags) < 0) goto error; + if (def->tpm) { + if (virDomainTPMDefFormat(buf, def->tpm, flags) < 0) + goto error; + } + if (def->ngraphics > 0) { /* If graphics is enabled, add the implicit mouse */ virDomainInputDef autoInput = { Index: libvirt/src/conf/domain_conf.h =================================================================== --- libvirt.orig/src/conf/domain_conf.h +++ libvirt/src/conf/domain_conf.h @@ -1063,6 +1063,34 @@ struct _virDomainHubDef { virDomainDeviceInfo info; }; +enum virDomainTPMModel { + VIR_DOMAIN_TPM_MODEL_TIS, + + VIR_DOMAIN_TPM_MODEL_LAST +}; + +enum virDomainTPMBackendType { + VIR_DOMAIN_TPM_TYPE_PASSTHROUGH, + + VIR_DOMAIN_TPM_TYPE_LAST +}; + +# define VIR_DOMAIN_TPM_DEFAULT_DEVICE "/dev/tpm0" + +typedef struct _virDomainTPMDef virDomainTPMDef; +typedef virDomainTPMDef *virDomainTPMDefPtr; +struct _virDomainTPMDef { + enum virDomainTPMBackendType type; + virDomainDeviceInfo info; + enum virDomainTPMModel model; + union { + struct { + char *path; + char *cancel_path; + } passthrough; + } data; +}; + enum virDomainInputType { VIR_DOMAIN_INPUT_TYPE_MOUSE, VIR_DOMAIN_INPUT_TYPE_TABLET, @@ -1866,6 +1894,7 @@ struct _virDomainDef { /* Only 1 */ virDomainWatchdogDefPtr watchdog; virDomainMemballoonDefPtr memballoon; + virDomainTPMDefPtr tpm; virCPUDefPtr cpu; virSysinfoDefPtr sysinfo; virDomainRedirFilterDefPtr redirfilter; @@ -1988,6 +2017,9 @@ int virDomainDeviceInfoCopy(virDomainDev void virDomainDeviceInfoClear(virDomainDeviceInfoPtr info); void virDomainDefClearPCIAddresses(virDomainDefPtr def); void virDomainDefClearDeviceAliases(virDomainDefPtr def); +void virDomainTPMDefFree(virDomainTPMDefPtr def); +char *virDomainTPMGetPathCopy(virDomainTPMDefPtr def); +char *virDomainTPMGetCancelPath(virDomainTPMDefPtr def); typedef int (*virDomainDeviceInfoCallback)(virDomainDefPtr def, virDomainDeviceDefPtr dev, @@ -2346,6 +2378,8 @@ VIR_ENUM_DECL(virDomainNumatuneMemPlacem VIR_ENUM_DECL(virDomainHyperv) VIR_ENUM_DECL(virDomainRNGModel) VIR_ENUM_DECL(virDomainRNGBackend) +VIR_ENUM_DECL(virDomainTPMModel) +VIR_ENUM_DECL(virDomainTPMBackend) /* from libvirt.h */ VIR_ENUM_DECL(virDomainState) VIR_ENUM_DECL(virDomainNostateReason) Index: libvirt/src/libvirt_private.syms =================================================================== --- libvirt.orig/src/libvirt_private.syms +++ libvirt/src/libvirt_private.syms @@ -323,6 +323,11 @@ virDomainTimerTickpolicyTypeFromString; virDomainTimerTickpolicyTypeToString; virDomainTimerTrackTypeFromString; virDomainTimerTrackTypeToString; +virDomainTPMBackendTypeFromString; +virDomainTPMBackendTypeToString; +virDomainTPMDefFree; +virDomainTPMModelTypeFromString; +virDomainTPMModelTypeToString; virDomainVcpuPinAdd; virDomainVcpuPinDefArrayFree; virDomainVcpuPinDefCopy; Index: libvirt/src/qemu/qemu_capabilities.c =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.c +++ libvirt/src/qemu/qemu_capabilities.c @@ -2110,6 +2110,63 @@ virQEMUCapsProbeQMPCPUDefinitions(virQEM static int +virQEMUCapsProbeQMPTPM(virQEMUCapsPtr qemuCaps, + qemuMonitorPtr mon) +{ + int nentries, i; + char **entries = NULL; + struct typeToCaps { + int type; + enum virQEMUCapsFlags caps; + }; + const struct typeToCaps tpmTypesToCaps[] = { + { + .type = VIR_DOMAIN_TPM_TYPE_PASSTHROUGH, + .caps = QEMU_CAPS_DEVICE_TPM_PASSTHROUGH, + }, + }; + const struct typeToCaps tpmModelsToCaps[] = { + { + .type = VIR_DOMAIN_TPM_MODEL_TIS, + .caps = QEMU_CAPS_DEVICE_TPM_TIS, + }, + }; + + if ((nentries = qemuMonitorGetTPMModels(mon, &entries)) < 0) + return -1; + + if (nentries > 0) { + for (i = 0; i < ARRAY_CARDINALITY(tpmModelsToCaps); i++) { + const char *needle = virDomainTPMModelTypeToString( + tpmModelsToCaps[i].type); + if (virStrArrayHasString(entries, nentries, needle)) + virQEMUCapsSet(qemuCaps, tpmModelsToCaps[i].caps); + } + for (i = 0; i < nentries; i++) + VIR_FREE(entries[i]); + } + VIR_FREE(entries); + + if ((nentries = qemuMonitorGetTPMTypes(mon, &entries)) < 0) + return -1; + + if (nentries > 0) { + for (i = 0; i < ARRAY_CARDINALITY(tpmTypesToCaps); i++) { + const char *needle = virDomainTPMBackendTypeToString( + tpmTypesToCaps[i].type); + if (virStrArrayHasString(entries, nentries, needle)) + virQEMUCapsSet(qemuCaps, tpmTypesToCaps[i].caps); + } + for (i = 0; i < nentries; i++) + VIR_FREE(entries[i]); + } + VIR_FREE(entries); + + return 0; +} + + +static int virQEMUCapsProbeQMPKVMState(virQEMUCapsPtr qemuCaps, qemuMonitorPtr mon) { @@ -2458,6 +2515,8 @@ virQEMUCapsInitQMP(virQEMUCapsPtr qemuCa goto cleanup; if (virQEMUCapsProbeQMPKVMState(qemuCaps, mon) < 0) goto cleanup; + if (virQEMUCapsProbeQMPTPM(qemuCaps, mon) < 0) + goto cleanup; ret = 0;

On Wed, Mar 13, 2013 at 12:03:50PM -0400, Stefan Berger wrote:
Parse the domain XML with TPM support.
Convert the strings from QEMU's QMP TPM commands into capability flags.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- docs/schemas/domaincommon.rng | 43 ++++++ src/conf/domain_conf.c | 268 ++++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 34 +++++ src/libvirt_private.syms | 5 src/qemu/qemu_capabilities.c | 59 +++++++++ 5 files changed, 409 insertions(+)
The QEMU changes should be a separate patch from any XML schema changes.
@@ -1515,6 +1522,24 @@ void virDomainHostdevDefClear(virDomainH } }
+void virDomainTPMDefFree(virDomainTPMDefPtr def) +{ + if (!def) + return; + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + VIR_FREE(def->data.passthrough.path); + VIR_FREE(def->data.passthrough.cancel_path); + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + virDomainDeviceInfoClear(&def->info); + VIR_FREE(def); +} + void virDomainHostdevDefFree(virDomainHostdevDefPtr def) { if (!def) @@ -1776,6 +1801,8 @@ void virDomainDefFree(virDomainDefPtr de
virDomainRNGDefFree(def->rng);
+ virDomainTPMDefFree(def->tpm); + VIR_FREE(def->os.type); VIR_FREE(def->os.machine); VIR_FREE(def->os.init); @@ -6312,6 +6339,192 @@ error: goto cleanup; }
+/* + * Check whether the given base path, e.g., /sys/class/misc/tpm0/device, + * is the sysfs entry of a TPM. A TPM sysfs entry should be uniquely + * recognizable by the file entries 'pcrs' and 'cancel'. + * Upon success 'true' is returned and the basebath buffer has '/cancel' + * appended. + */ +static bool +virDomainTPMCheckSysfsCancel(char *basepath, size_t bufsz) +{ + char *path = NULL; + struct stat statbuf; + + if (virAsprintf(&path, "%s/pcrs", basepath) < 0) { + virReportOOMError(); + goto error; + } + if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) + goto error; + + VIR_FREE(path); + + if (virAsprintf(&path, "%s/cancel", basepath) < 0) { + virReportOOMError(); + goto error; + } + + if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) + goto error; + + if (!virStrncpy(basepath, path, strlen(path) + 1, bufsz)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Basepath buffer is too small")); + goto error; + } + + VIR_FREE(path); + + return true; + +error: + VIR_FREE(path); + return false; +} + + +static char * +virDomainTPMFindCancelPath(void) +{ + unsigned int idx; + int len; + DIR *pnp_dir; + char path[100], *p; + struct dirent entry, *result; + bool found = false; + + snprintf(path, sizeof(path), "/sys/class/misc"); + pnp_dir = opendir(path); + if (pnp_dir != NULL) { + while (readdir_r(pnp_dir, &entry, &result) == 0 && + result != NULL) { + if (sscanf(entry.d_name, "tpm%u%n", &idx, &len) < 1 || + len <= strlen("tpm") || + len != strlen(entry.d_name)) { + continue; + } + snprintf(path, sizeof(path), "/sys/class/misc/%s/device", + entry.d_name); + if (!virDomainTPMCheckSysfsCancel(path, sizeof(path))) { + continue; + } + + found = true; + break; + } + closedir(pnp_dir); + } + + if (found) { + if (!(p = strdup(path))) + virReportOOMError(); + return p; + } + + return NULL; +}
These two funtions have nothing todo with the XML schema so should not be in this file. Either some helper file in src/util or in the QEMU code.
+ switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + path = virXPathString("string(./backend/device/@path)", ctxt); + if (!path && !(path = strdup(VIR_DOMAIN_TPM_DEFAULT_DEVICE))) { + virReportOOMError(); + goto error; + } + def->data.passthrough.path = path; + path = NULL; + /* cancel_path is read-only */ + def->data.passthrough.cancel_path = virDomainTPMFindCancelPath();
This data is driver specific state so should not be in the XML schema structs - it should be in QEMU code where needed
+# define VIR_DOMAIN_TPM_DEFAULT_DEVICE "/dev/tpm0" + +typedef struct _virDomainTPMDef virDomainTPMDef; +typedef virDomainTPMDef *virDomainTPMDefPtr; +struct _virDomainTPMDef { + enum virDomainTPMBackendType type; + virDomainDeviceInfo info; + enum virDomainTPMModel model; + union { + struct { + char *path; + char *cancel_path;
Per previous comments 'cancel_path' should not be in this struct.
+ } passthrough; + } data; +}; +
Index: libvirt/src/qemu/qemu_capabilities.c =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.c +++ libvirt/src/qemu/qemu_capabilities.c @@ -2110,6 +2110,63 @@ virQEMUCapsProbeQMPCPUDefinitions(virQEM
static int +virQEMUCapsProbeQMPTPM(virQEMUCapsPtr qemuCaps, + qemuMonitorPtr mon) +{ + int nentries, i; + char **entries = NULL; + struct typeToCaps { + int type; + enum virQEMUCapsFlags caps; + }; + const struct typeToCaps tpmTypesToCaps[] = { + { + .type = VIR_DOMAIN_TPM_TYPE_PASSTHROUGH, + .caps = QEMU_CAPS_DEVICE_TPM_PASSTHROUGH, + }, + }; + const struct typeToCaps tpmModelsToCaps[] = { + { + .type = VIR_DOMAIN_TPM_MODEL_TIS, + .caps = QEMU_CAPS_DEVICE_TPM_TIS, + }, + }; + + if ((nentries = qemuMonitorGetTPMModels(mon, &entries)) < 0) + return -1; + + if (nentries > 0) { + for (i = 0; i < ARRAY_CARDINALITY(tpmModelsToCaps); i++) { + const char *needle = virDomainTPMModelTypeToString( + tpmModelsToCaps[i].type); + if (virStrArrayHasString(entries, nentries, needle)) + virQEMUCapsSet(qemuCaps, tpmModelsToCaps[i].caps); + } + for (i = 0; i < nentries; i++) + VIR_FREE(entries[i]);
If you make the monitor commands return a NULL terminated list, you can just use virStringFreeList. This also avoids the need to pass around 'nentries'; to virStrArrayHasString. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- docs/formatdomain.html.in | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) Index: libvirt/docs/formatdomain.html.in =================================================================== --- libvirt.orig/docs/formatdomain.html.in +++ libvirt/docs/formatdomain.html.in @@ -4336,6 +4336,63 @@ qemu-kvm -net nic,model=? /dev/null </dl> + <h4><a name="elementsTpm">TPM device</a></h4> + + <p> + The TPM device enables a QEMU guest to have access to TPM + functionality. + </p> + <p> + The TPM passthrough device type provides access to the host's TPM + for one QEMU guest. No other software may be is using the TPM device, + typically /dev/tpm0, at the time the QEMU guest is started. + <span class="since">'passthrough' since 1.0.4</span> + </p> + + <p> + Example: usage of the TPM passthrough device + </p> +<pre> + ... + <device> + <tpm model='tpm-tis'> + <backend type='passthrough'> + <backend path='/dev/tpm0'/> + </backend> + </tpm> + </device> +</pre> + <dl> + <dt><code>model</code></dt> + <dd> + <p> + The <code>model</code> attribute specifies what device + model QEMU provides to the guest. If no model name is provided, + <code>tpm-tis</code> will automatically be chosen. + </p> + </dd> + <dt><code>backend</code></dt> + <dd> + <p> + The <code>backend</code> element specifies the type of + TPM device. The following types are supported: + </p> + <ul> + <li>'passthrough' — use the hosts's TPM device. + </ul> + </dd> + <dt><code>backend type='passthrough'</code></dt> + <dd> + <p> + This backend type requires exclusive access to a TPM device on + the host. + An example for such a device is /dev/tpm0. The filename is + specified as path attribute of the <code>source</code> element. + If no file name is specified then /dev/tpm0 is automatically used. + </p> + </dd> + </dl> + <h3><a name="seclabel">Security label</a></h3> <p>

On Wed, Mar 13, 2013 at 12:03:51PM -0400, Stefan Berger wrote:
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- docs/formatdomain.html.in | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+)
This ought to be part of the patch which introduces the RNG schema and parser.
Index: libvirt/docs/formatdomain.html.in =================================================================== --- libvirt.orig/docs/formatdomain.html.in +++ libvirt/docs/formatdomain.html.in @@ -4336,6 +4336,63 @@ qemu-kvm -net nic,model=? /dev/null
</dl>
+ <h4><a name="elementsTpm">TPM device</a></h4> + + <p> + The TPM device enables a QEMU guest to have access to TPM + functionality. + </p> + <p> + The TPM passthrough device type provides access to the host's TPM + for one QEMU guest. No other software may be is using the TPM device, + typically /dev/tpm0, at the time the QEMU guest is started. + <span class="since">'passthrough' since 1.0.4</span> + </p> + + <p> + Example: usage of the TPM passthrough device + </p> +<pre> + ... + <device> + <tpm model='tpm-tis'> + <backend type='passthrough'> + <backend path='/dev/tpm0'/> + </backend> + </tpm> + </device> +</pre> + <dl> + <dt><code>model</code></dt> + <dd> + <p> + The <code>model</code> attribute specifies what device + model QEMU provides to the guest. If no model name is provided, + <code>tpm-tis</code> will automatically be chosen. + </p> + </dd> + <dt><code>backend</code></dt> + <dd> + <p> + The <code>backend</code> element specifies the type of + TPM device. The following types are supported: + </p> + <ul> + <li>'passthrough' — use the hosts's TPM device. + </ul> + </dd> + <dt><code>backend type='passthrough'</code></dt> + <dd> + <p> + This backend type requires exclusive access to a TPM device on + the host. + An example for such a device is /dev/tpm0. The filename is + specified as path attribute of the <code>source</code> element. + If no file name is specified then /dev/tpm0 is automatically used. + </p> + </dd> + </dl> + <h3><a name="seclabel">Security label</a></h3>
<p>
Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- src/security/security_selinux.c | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) Index: libvirt/src/security/security_selinux.c =================================================================== --- libvirt.orig/src/security/security_selinux.c +++ libvirt/src/security/security_selinux.c @@ -76,6 +76,12 @@ struct _virSecuritySELinuxCallbackData { #define SECURITY_SELINUX_VOID_DOI "0" #define SECURITY_SELINUX_NAME "selinux" +static int +virSecuritySELinuxRestoreSecurityTPMFileLabelInt(virSecurityManagerPtr mgr, + virDomainDefPtr def, + virDomainTPMDefPtr tpm); + + /* * Returns 0 on success, 1 if already reserved, or -1 on fatal error */ @@ -1017,6 +1023,80 @@ err: return rc; } + +static int +virSecuritySELinuxSetSecurityTPMFileLabel(virSecurityManagerPtr mgr, + virDomainDefPtr def, + virDomainTPMDefPtr tpm) +{ + int rc; + virSecurityLabelDefPtr seclabel; + + seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME); + if (seclabel == NULL) + return -1; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + rc = virSecuritySELinuxSetFilecon(tpm->data.passthrough.path, + seclabel->imagelabel); + if (rc < 0) + return -1; + + if (tpm->data.passthrough.cancel_path) { + rc = virSecuritySELinuxSetFilecon( + tpm->data.passthrough.cancel_path, + seclabel->imagelabel); + if (rc < 0) { + virSecuritySELinuxRestoreSecurityTPMFileLabelInt(mgr, def, + tpm); + return -1; + } + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot start guest without TPM " + "cancel path")); + return -1; + } + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + return 0; +} + + +static int +virSecuritySELinuxRestoreSecurityTPMFileLabelInt(virSecurityManagerPtr mgr, + virDomainDefPtr def, + virDomainTPMDefPtr tpm) +{ + int rc = 0; + virSecurityLabelDefPtr seclabel; + + seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME); + if (seclabel == NULL) + return -1; + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + rc = virSecuritySELinuxRestoreSecurityFileLabel( + mgr, tpm->data.passthrough.path); + + if (tpm->data.passthrough.cancel_path) { + rc = virSecuritySELinuxRestoreSecurityFileLabel(mgr, + tpm->data.passthrough.cancel_path); + } + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + return rc; +} + + static int virSecuritySELinuxRestoreSecurityImageLabelInt(virSecurityManagerPtr mgr, virDomainDefPtr def, @@ -1685,6 +1765,12 @@ virSecuritySELinuxRestoreSecurityAllLabe if (secdef->norelabel || data->skipAllLabel) return 0; + if (def->tpm) { + if (virSecuritySELinuxRestoreSecurityTPMFileLabelInt(mgr, def, + def->tpm) < 0) + rc = -1; + } + for (i = 0 ; i < def->nhostdevs ; i++) { if (virSecuritySELinuxRestoreSecurityHostdevLabel(mgr, def, @@ -2095,6 +2181,11 @@ virSecuritySELinuxSetSecurityAllLabel(vi NULL) < 0) return -1; } + if (def->tpm) { + if (virSecuritySELinuxSetSecurityTPMFileLabel(mgr, def, + def->tpm) < 0) + return -1; + } if (virDomainChrDefForeach(def, true,

On Wed, Mar 13, 2013 at 12:03:52PM -0400, Stefan Berger wrote:
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- src/security/security_selinux.c | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+)
I imagine we also need to update security_apparmour.c and security_dac.c. Also src/conf/domain_audit.c will need to emit an audit event when the TPM is configured to use a host device. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 03/14/2013 10:29 AM, Daniel P. Berrange wrote:
On Wed, Mar 13, 2013 at 12:03:52PM -0400, Stefan Berger wrote:
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- src/security/security_selinux.c | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) I imagine we also need to update security_apparmour.c and security_dac.c.
DAC: this seems to only be necessary if the the owner of the device is not root. Typically it is owned by root. I added support for it anyway now. AppArmour: it looks like no other character devices are being labeled so I may not have to do this for the TPM, either (?)
Also src/conf/domain_audit.c will need to emit an audit event when the TPM is configured to use a host device.
type=VIRT_RESOURCE msg=audit(1363292411.635:499): pid=23365 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:virtd_t:s0-s0:c0.c1023 msg='virt=kvm resrc=cgroup reason=allow vm="TPM-PT" uuid=a4d7cd22-da89-3094-6212-079a48a309a1 cgroup="/sys/fs/cgroup/devices/libvirt/qemu/TPM-PT/" class=path path=/dev/tpm0 rdev=0A:E0 acl=rw exe="/usr/sbin/libvirtd" hostname=? addr=? terminal=? res=success' Is this message type sufficient for a host device? Stefan
Daniel

On Thu, Mar 14, 2013 at 04:24:27PM -0400, Stefan Berger wrote:
On 03/14/2013 10:29 AM, Daniel P. Berrange wrote:
On Wed, Mar 13, 2013 at 12:03:52PM -0400, Stefan Berger wrote:
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- src/security/security_selinux.c | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) I imagine we also need to update security_apparmour.c and security_dac.c.
DAC: this seems to only be necessary if the the owner of the device is not root. Typically it is owned by root. I added support for it anyway now. AppArmour: it looks like no other character devices are being labeled so I may not have to do this for the TPM, either (?)
Also src/conf/domain_audit.c will need to emit an audit event when the TPM is configured to use a host device.
type=VIRT_RESOURCE msg=audit(1363292411.635:499): pid=23365 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:virtd_t:s0-s0:c0.c1023 msg='virt=kvm resrc=cgroup reason=allow vm="TPM-PT" uuid=a4d7cd22-da89-3094-6212-079a48a309a1 cgroup="/sys/fs/cgroup/devices/libvirt/qemu/TPM-PT/" class=path path=/dev/tpm0 rdev=0A:E0 acl=rw exe="/usr/sbin/libvirtd" hostname=? addr=? terminal=? res=success'
Is this message type sufficient for a host device?
No, this is just a generic message related to cgroups setup. We need to emit explicit audit log for each device in addition to this, since we cannot assume cgroups is enabled. We need to extend the virDomainAuditStart method to include this new device type. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 03/14/2013 06:34 PM, Daniel P. Berrange wrote:
On Thu, Mar 14, 2013 at 04:24:27PM -0400, Stefan Berger wrote:
On 03/14/2013 10:29 AM, Daniel P. Berrange wrote:
On Wed, Mar 13, 2013 at 12:03:52PM -0400, Stefan Berger wrote:
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- src/security/security_selinux.c | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) I imagine we also need to update security_apparmour.c and security_dac.c. DAC: this seems to only be necessary if the the owner of the device is not root. Typically it is owned by root. I added support for it anyway now. AppArmour: it looks like no other character devices are being labeled so I may not have to do this for the TPM, either (?)
Also src/conf/domain_audit.c will need to emit an audit event when the TPM is configured to use a host device. type=VIRT_RESOURCE msg=audit(1363292411.635:499): pid=23365 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:virtd_t:s0-s0:c0.c1023 msg='virt=kvm resrc=cgroup reason=allow vm="TPM-PT" uuid=a4d7cd22-da89-3094-6212-079a48a309a1 cgroup="/sys/fs/cgroup/devices/libvirt/qemu/TPM-PT/" class=path path=/dev/tpm0 rdev=0A:E0 acl=rw exe="/usr/sbin/libvirtd" hostname=? addr=? terminal=? res=success'
Is this message type sufficient for a host device? No, this is just a generic message related to cgroups setup. We need to emit explicit audit log for each device in addition to this, since we cannot assume cgroups is enabled.
We need to extend the virDomainAuditStart method to include this new device type.
Ok, so now libvirt is also printing this one here: type=VIRT_RESOURCE msg=audit(1363305463.181:1302): pid=17807 uid=0 auid=0 ses=1 subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 msg='virt=kvm resrc=dev reason=start vm="TPM-PT" uuid=a4d7cd22-da89-3094-6212-079a48a309a1 device="/dev/tpm0" exe="/usr/sbin/libvirtd" hostname=? addr=? terminal=pts/20 res=success' I suppose the msg='' part is somewhat device specific. So above really only shows /dev/tpm0 and I don't think there's much else worth mentioning. The last thing is now in which patch to put this :-) I would keep it in a separate patch after the one touching the SELinux and DAC labeling... Stefan

On Thu, Mar 14, 2013 at 08:00:21PM -0400, Stefan Berger wrote:
On 03/14/2013 06:34 PM, Daniel P. Berrange wrote:
On Thu, Mar 14, 2013 at 04:24:27PM -0400, Stefan Berger wrote:
On 03/14/2013 10:29 AM, Daniel P. Berrange wrote:
On Wed, Mar 13, 2013 at 12:03:52PM -0400, Stefan Berger wrote:
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- src/security/security_selinux.c | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) I imagine we also need to update security_apparmour.c and security_dac.c. DAC: this seems to only be necessary if the the owner of the device is not root. Typically it is owned by root. I added support for it anyway now. AppArmour: it looks like no other character devices are being labeled so I may not have to do this for the TPM, either (?)
Also src/conf/domain_audit.c will need to emit an audit event when the TPM is configured to use a host device. type=VIRT_RESOURCE msg=audit(1363292411.635:499): pid=23365 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:virtd_t:s0-s0:c0.c1023 msg='virt=kvm resrc=cgroup reason=allow vm="TPM-PT" uuid=a4d7cd22-da89-3094-6212-079a48a309a1 cgroup="/sys/fs/cgroup/devices/libvirt/qemu/TPM-PT/" class=path path=/dev/tpm0 rdev=0A:E0 acl=rw exe="/usr/sbin/libvirtd" hostname=? addr=? terminal=? res=success'
Is this message type sufficient for a host device? No, this is just a generic message related to cgroups setup. We need to emit explicit audit log for each device in addition to this, since we cannot assume cgroups is enabled.
We need to extend the virDomainAuditStart method to include this new device type.
Ok, so now libvirt is also printing this one here:
type=VIRT_RESOURCE msg=audit(1363305463.181:1302): pid=17807 uid=0 auid=0 ses=1 subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 msg='virt=kvm resrc=dev reason=start vm="TPM-PT" uuid=a4d7cd22-da89-3094-6212-079a48a309a1 device="/dev/tpm0" exe="/usr/sbin/libvirtd" hostname=? addr=? terminal=pts/20 res=success'
I suppose the msg='' part is somewhat device specific. So above really only shows /dev/tpm0 and I don't think there's much else worth mentioning.
Yep, that's fine - people using these are just interested in the name of the device / file on the host being used by the VM.
The last thing is now in which patch to put this :-) I would keep it in a separate patch after the one touching the SELinux and DAC labeling...
Sure, separate patch is fine. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- src/qemu/qemu_command.c | 249 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) Index: libvirt/src/qemu/qemu_command.c =================================================================== --- libvirt.orig/src/qemu/qemu_command.c +++ libvirt/src/qemu/qemu_command.c @@ -856,6 +856,10 @@ qemuAssignDeviceAliases(virDomainDefPtr if (virAsprintf(&def->rng->info.alias, "rng%d", 0) < 0) goto no_memory; } + if (def->tpm) { + if (virAsprintf(&def->tpm->info.alias, "tpm%d", 0) < 0) + goto no_memory; + } return 0; @@ -4437,6 +4441,111 @@ cleanup: } +static char *qemuBuildTPMBackendStr(virCommandPtr cmd, + virQEMUDriverPtr driver, + const virDomainDefPtr def, + virQEMUCapsPtr qemuCaps, + virFdSetPtr fdset, + const char *emulator) +{ + const char *path; + const virDomainTPMDefPtr tpm = def->tpm; + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *type = virDomainTPMBackendTypeToString(tpm->type); + virDomainDeviceInfo info = { 0, }; + + virBufferAsprintf(&buf, "%s,id=tpm-%s", type, tpm->info.alias); + + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_TPM_PASSTHROUGH)) + goto no_support; + + if (qemuCreatePathForFilePath(driver, &buf, + ",path=", tpm->data.passthrough.path, + (int[]){O_RDWR, -1}, + cmd, fdset, &tpm->info, + qemuCaps) < 0) + goto error; + + path = tpm->data.passthrough.cancel_path; + if (!path) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("TPM cancel path could not be determined")); + goto error; + } + + if (virAsprintf(&info.alias, "%s-cancel", tpm->info.alias) < 0) { + virReportOOMError(); + goto error; + } + + if (qemuCreatePathForFilePath(driver, &buf, + ",cancel-path=", path, + (int[]){O_WRONLY, -1}, + cmd, fdset, &info, + qemuCaps) < 0) + goto error; + + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + goto error; + } + + if (virBufferError(&buf)) { + virReportOOMError(); + goto error; + } + + VIR_FREE(info.alias); + + return virBufferContentAndReset(&buf); + + no_support: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("The QEMU executable %s does not support TPM " + "backend type %s"), + emulator, type); + + error: + virBufferFreeAndReset(&buf); + VIR_FREE(info.alias); + return NULL; +} + + +static char *qemuBuildTPMDevStr(const virDomainDefPtr def, + virQEMUCapsPtr qemuCaps, + const char *emulator) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + const virDomainTPMDefPtr tpm = def->tpm; + const char *model = virDomainTPMModelTypeToString(tpm->model); + + if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_TPM_TIS)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("The QEMU executable %s does not support TPM " + "model %s"), + emulator, model); + goto error; + } + + virBufferAsprintf(&buf, "%s,tpmdev=tpm-%s,id=%s", + model, tpm->info.alias, tpm->info.alias); + + if (virBufferError(&buf)) { + virReportOOMError(); + goto error; + } + + return virBufferContentAndReset(&buf); + + error: + virBufferFreeAndReset(&buf); + return NULL; +} + + static char *qemuBuildSmbiosBiosStr(virSysinfoDefPtr def) { virBuffer buf = VIR_BUFFER_INITIALIZER; @@ -6779,6 +6888,23 @@ qemuBuildCommandLine(virConnectPtr conn, } } + if (def->tpm) { + char *optstr; + + if (!(optstr = qemuBuildTPMBackendStr(cmd, driver, def, + qemuCaps, fdset, emulator))) + goto error; + + virCommandAddArgList(cmd, "-tpmdev", optstr, NULL); + VIR_FREE(optstr); + + if (!(optstr = qemuBuildTPMDevStr(def, qemuCaps, emulator))) + goto error; + + virCommandAddArgList(cmd, "-device", optstr, NULL); + VIR_FREE(optstr); + } + for (i = 0 ; i < def->ninputs ; i++) { virDomainInputDefPtr input = def->inputs[i]; @@ -8632,6 +8758,125 @@ 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->model = VIR_DOMAIN_TPM_MODEL_TIS; + + for (i = 0; i < nkeywords; i++) { + if (STREQ(keywords[i], "type")) { + if (values[i] && + STREQ(values[i], + virDomainTPMBackendTypeToString(VIR_DOMAIN_TPM_TYPE_PASSTHROUGH))) + tpm->type = VIR_DOMAIN_TPM_TYPE_PASSTHROUGH; + } else if (STREQ(keywords[i], + virDomainTPMBackendTypeToString(VIR_DOMAIN_TPM_TYPE_PASSTHROUGH))) { + tpm->type = VIR_DOMAIN_TPM_TYPE_PASSTHROUGH; + } else if (STREQ(keywords[i], "path")) { + if (values[i]) { + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + tpm->data.passthrough.path = values[i]; + values[i] = NULL; + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + } else { + goto syntax; + } + } else if (STREQ(keywords[i], "cancel-path")) { + if (values[i]) { + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + tpm->data.passthrough.cancel_path = values[i]; + values[i] = NULL; + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + } else { + goto syntax; + } + } + } + + /* sanity checks */ + switch (tpm->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + if (!tpm->data.passthrough.path) { + if (!(tpm->data.passthrough.path = + strdup(VIR_DOMAIN_TPM_DEFAULT_DEVICE))) { + virReportOOMError(); + goto bad_definition; + } + } + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + virReportError(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); + + virReportError(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) { @@ -9440,6 +9685,10 @@ virDomainDefPtr qemuParseCommandLine(vir } else if (STREQ(arg, "-S")) { /* ignore, always added by libvirt */ + } else if (STREQ(arg, "-tpmdev")) { + WANT_VALUE(); + 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

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- tests/qemuxml2argvdata/qemuxml2argv-tpm-passthrough.args | 6 +++ tests/qemuxml2argvdata/qemuxml2argv-tpm-passthrough.xml | 29 +++++++++++++++ tests/qemuxml2argvtest.c | 3 + tests/qemuxml2xmltest.c | 2 + 4 files changed, 40 insertions(+) Index: libvirt/tests/qemuxml2argvdata/qemuxml2argv-tpm-passthrough.args =================================================================== --- /dev/null +++ libvirt/tests/qemuxml2argvdata/qemuxml2argv-tpm-passthrough.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 -nodefaults \ +-monitor unix:/tmp/test-monitor,server,nowait -boot c -usb \ +-tpmdev passthrough,id=tpm-tpm0,path=/dev/tpm0,cancel-path=/sys/class/misc/tpm0/device/cancel \ +-device tpm-tis,tpmdev=tpm-tpm0,id=tpm0 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 Index: libvirt/tests/qemuxml2argvdata/qemuxml2argv-tpm-passthrough.xml =================================================================== --- /dev/null +++ libvirt/tests/qemuxml2argvdata/qemuxml2argv-tpm-passthrough.xml @@ -0,0 +1,29 @@ +<domain type='qemu'> + <name>TPM-VM</name> + <uuid>11d7cd22-da89-3094-6212-079a48a309a1</uuid> + <memory unit='KiB'>2097152</memory> + <currentMemory unit='KiB'>512288</currentMemory> + <vcpu placement='static'>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> + <controller type='usb' index='0'/> + <tpm model='tpm-tis'> + <backend type='passthrough'> + <device path='/dev/tpm0'/> + </backend> + </tpm> + <memballoon model='virtio'/> + </devices> +</domain> Index: libvirt/tests/qemuxml2argvtest.c =================================================================== --- libvirt.orig/tests/qemuxml2argvtest.c +++ libvirt/tests/qemuxml2argvtest.c @@ -898,6 +898,9 @@ mymain(void) DO_TEST("add-fd", QEMU_CAPS_ADD_FD, QEMU_CAPS_CHARDEV, QEMU_CAPS_DEVICE, QEMU_CAPS_DRIVE); + DO_TEST("tpm-passthrough", QEMU_CAPS_DEVICE, + QEMU_CAPS_DEVICE_TPM_PASSTHROUGH, QEMU_CAPS_DEVICE_TPM_TIS); + virObjectUnref(driver.config); virObjectUnref(driver.caps); VIR_FREE(map); Index: libvirt/tests/qemuxml2xmltest.c =================================================================== --- libvirt.orig/tests/qemuxml2xmltest.c +++ libvirt/tests/qemuxml2xmltest.c @@ -261,6 +261,8 @@ mymain(void) DO_TEST_DIFFERENT("metadata"); + DO_TEST("tpm-passthrough"); + virObjectUnref(driver.caps); return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE;

On Wed, Mar 13, 2013 at 12:03:54PM -0400, Stefan Berger wrote:
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
--- tests/qemuxml2argvdata/qemuxml2argv-tpm-passthrough.args | 6 +++ tests/qemuxml2argvdata/qemuxml2argv-tpm-passthrough.xml | 29 +++++++++++++++ tests/qemuxml2argvtest.c | 3 + tests/qemuxml2xmltest.c | 2 + 4 files changed, 40 insertions(+)
This can be squashed into the patch which adds the QEMU cli generation/ parsing. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (2)
-
Daniel P. Berrange
-
Stefan Berger