
On 05/08/2018 04:30 PM, John Ferlan wrote:
On 05/04/2018 04:21 PM, Stefan Berger wrote:
Add functions for managing the storage of the external swtpm as well as starting and stopping it. Also implement functions to use swtpm_setup, which simulates the manufacturing of a TPM which includes creation of certificates for the device.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> --- src/libvirt_private.syms | 5 + src/util/virtpm.c | 536 ++++++++++++++++++++++++++++++++++++++++++++++- src/util/virtpm.h | 33 ++- 3 files changed, 572 insertions(+), 2 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 33fe75b..eebfc72 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2984,6 +2984,11 @@ virTimeStringThenRaw;
# util/virtpm.h virTPMCreateCancelPath; +virTPMDeleteEmulatorStorage; +virTPMEmulatorBuildCommand; +virTPMEmulatorInitPaths; +virTPMEmulatorPrepareHost; +virTPMEmulatorStop;
# util/virtypedparam.h diff --git a/src/util/virtpm.c b/src/util/virtpm.c index d5c10da..76bbb21 100644 --- a/src/util/virtpm.c +++ b/src/util/virtpm.c @@ -1,7 +1,7 @@ /* * virtpm.c: TPM support * - * Copyright (C) 2013 IBM Corporation + * Copyright (C) 2013,2018 IBM Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,16 +22,36 @@
#include <config.h>
+#include <sys/types.h> #include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <cap-ng.h>
+#include "conf/domain_conf.h" syntax-check would have told you unsafe cross-directory include - IOW including conf/* files into util/* files is not allowed.
So I think you need to rethink where some of these functions will go. I think they are mostly all used by the qemu_extdevice.c changes in patch 9, so perhaps they need to get folded into them. There at least you can grab the conf/domain_conf.h file.
Probably best to do that... rather than passing the fields of virDomainTPMDef into the functions instead. Currently the functions have the prefix virTPM. That will have to change - to qemuTPM? So I'll merge these functions into qemu_extdevice.c? or another new file qemu_tpm.c ?
+#include "viralloc.h" syntax-check would have told you not to include "viralloc.h" twice...
obviously 'forgot' to run it.
+#include "vircommand.h" #include "virstring.h" #include "virerror.h" #include "viralloc.h" #include "virfile.h" +#include "virkmod.h" +#include "virlog.h" #include "virtpm.h" +#include "virutil.h" #include "viruuid.h" to get virUUIDGenerate
+#include "configmake.h"
#define VIR_FROM_THIS VIR_FROM_NONE
+VIR_LOG_INIT("util.tpm") + +/* + * executables for the swtpm; to be found on the host + */ +static char *swtpm_path; +static char *swtpm_setup; +static char *swtpm_ioctl; + There's a love/hate relationship with static/globals...
/** * virTPMCreateCancelPath: * @devpath: Path to the TPM device @@ -74,3 +94,517 @@ virTPMCreateCancelPath(const char *devpath) cleanup: return path; } Two empty lines - pervasive comment here...
+ +/* + * virTPMEmulatorInit + * + * Initialize the Emulator functions by searching for necessary + * executables that we will use to start and setup the swtpm + */ +static int +virTPMEmulatorInit(void) +{ + if (!swtpm_path) { + swtpm_path = virFindFileInPath("swtpm"); + if (!swtpm_path) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find swtpm 'swtpm' in PATH")); The message feels redundant.
You mean the repetition of 'swtpm' is redundant? Should I adapt the reporting to use this type of command ? if (!(qemunbd = virFindFileInPath("qemu-nbd"))) { virReportSystemError(ENOENT, "%s", _("Unable to find 'qemu-nbd' binary in $PATH")); goto cleanup; } +
+/* + * virTPMEmulatorPrepareHost: + * + * @tpm: tpm definition + * @logDir: directory where swtpm writes its logs into + * @vmname: name of the VM + * @swtpm_user: uid to run the swtpm with + * @swtpm_group: gid to run the swtpm with + * @swtpmStateDir: directory for swtpm's persistent state + * @qemu_user: uid that qemu will run with; we share the socket file with it + * @shortName: short and unique name of the domain + * + * Prepare the log directory for the swtpm and adjust ownership of it and the + * log file we will be using. Prepare the state directory where we will share + * the socket between tss and qemu users. + */ +int virTPMEmulatorPrepareHost(virDomainTPMDefPtr tpm, + const char *logDir, const char *vmname, + uid_t swtpm_user, gid_t swtpm_group, + const char *swtpmStateDir, + uid_t qemu_user, const char *shortName) one line each argument
+{ + int ret = -1; + + if (virTPMEmulatorInit() < 0) + return -1; + + /* create log dir ... */ + if (virFileMakePathWithMode(logDir, 0730) < 0) + goto cleanup; I think we could just return -1; here.
+ + /* ... and adjust ownership */ + if (virDirCreate(logDir, 0730, swtpm_user, swtpm_group, + VIR_DIR_CREATE_ALLOW_EXIST) < 0) + goto cleanup; + + /* create logfile name ... */ + if (virAsprintf(&tpm->data.emulator.logfile, "%s/%s-swtpm.log", + logDir, vmname) < 0) + goto cleanup; + + /* ... and make sure it can be accessed by swtpm_user */ + if (virFileExists(tpm->data.emulator.logfile) && + chown(tpm->data.emulator.logfile, swtpm_user, swtpm_group) < 0) { + virReportSystemError(errno, + _("Could not chown on swtpm logfile %s"), + tpm->data.emulator.logfile); + goto cleanup; + } + + /* + create our swtpm state dir ... + - QEMU user needs to be able to access the socket there + - swtpm group needs to be able to create files there + - in privileged mode 0570 would be enough, for non-privileged mode + we need 0770 + */ + if (virDirCreate(swtpmStateDir, 0770, qemu_user, swtpm_group, + VIR_DIR_CREATE_ALLOW_EXIST) < 0) + goto cleanup; + + /* create the socket filename */ + if (!(tpm->data.emulator.source.data.nix.path = + virTPMCreateEmulatorSocket(swtpmStateDir, shortName))) + goto cleanup; + tpm->data.emulator.source.type = VIR_DOMAIN_CHR_TYPE_UNIX; + + ret = 0; + + cleanup: + if (ret) + VIR_FREE(tpm->data.emulator.logfile); Does this matter? Wouldn't the logfile be free'd in a failure path by virDomainTPMDefFree? If it does matter, use "if (ret < 0)".
I'll remove that but add a check for tpm->data.emulator.logfile before 'virAsprintf(&tpm->data.emulator.logfile,', so we don't have a memory leak here.
+ + return ret; +} + +/* + * virTPMEmulatorRunSetup + * + * @storagepath: path to the directory for TPM state + * @vmname: the name of the VM + * @vmuuid: the UUID of the VM + * @privileged: whether we are running in privileged mode + * @swtpm_user: The userid to switch to when setting up the TPM; + * typically this should be the uid of 'tss' or 'root' + * @swtpm_group: The group id to switch to + * @logfile: The file to write the log into; it must be writable + * for the user given by userid or 'tss' + * + * Setup the external swtpm by creating endorsement key and + * certificates for it. + */ +static int +virTPMEmulatorRunSetup(const char *storagepath, const char *vmname, + const unsigned char *vmuuid, bool privileged, + uid_t swtpm_user, gid_t swtpm_group, + const char *logfile) One arg per line
+{ + virCommandPtr cmd = NULL; + int exitstatus; + int rc = 0; Use ret = -1;
+ char uuid[VIR_UUID_STRING_BUFLEN]; + char *vmid = NULL; + off_t logstart; + + if (!privileged) { + return virFileWriteStr(logfile, + _("Did not create EK and certificates since " + "this requires privileged mode\n"), + 0600); + } + + cmd = virCommandNew(swtpm_setup); + if (!cmd) { + rc = -1; + goto cleanup; + } Just goto cleanup; w/ @ret initialized to -1
+ + virUUIDFormat(vmuuid, uuid); + if (virAsprintf(&vmid, "%s:%s", vmname, uuid) < 0) + goto cleanup; Because here you would have returned 0 and I don't think that's what you'd expect at this point...
+ + virCommandSetUID(cmd, swtpm_user); + virCommandSetGID(cmd, swtpm_group); + + virCommandAddArgList(cmd, + "--tpm-state", storagepath, + "--vmid", vmid, + "--logfile", logfile, + "--createek", + "--create-ek-cert", + "--create-platform-cert", + "--lock-nvram", + "--not-overwrite", + NULL); + + virCommandClearCaps(cmd); + + /* get size of logfile */ + logstart = virFileLength(logfile, -1); + if (logstart < 0) + logstart = 0; + + if (virCommandRun(cmd, &exitstatus) < 0 || exitstatus != 0) { + char *buffer = NULL, *errors; + off_t loglength = virFileLength(logfile, -1); + + if (loglength > logstart) { + ignore_value(virFileReadOffsetQuiet(logfile, logstart, + loglength - logstart, + &buffer)); On error @buffer could be NULL
+ errors = virStringFilterLines(buffer, "Error:", 160); If @buffer == NULL, then the above is not going to work.
Also should it be _("Error:") or are we sure that the program is run with a specific language (e.g. i18n concerns)? Since we don't control the language of that output - it's a bit dicey to assume/use it.
+ virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not run '%s'. exitstatus: %d;\n" + "%s"), + swtpm_setup, exitstatus, errors); Given the "concerns" raised, why not just direct the consumer to the @logfile rather than trying to report the error into the output.
I had that before and thought it was more convenient to show to the user what the problem was.
IOW:
if (virCommandRun(cmd, &exitstatus) < 0 || exitstatus != 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not run '%s'. exitstatus: %d;\n" "Check error log '%s' for details."), swtpm_setup, exitstatus, logfile); goto cleanup; }
If you really want to keep this logic, then handling buffer == NULL before calling virStringFilterLines will need to be done and of course handling that @errors wouldn't be filled in.
I am not insisting ... let me drop this along with patches 1 & 2.
+ VIR_FREE(buffer); + VIR_FREE(errors); + } + rc = -1; goto cleanup;
+ } + ret = 0;
+ cleanup: + VIR_FREE(vmid); + virCommandFree(cmd); + + return rc; return ret;
+} + +/* + * virTPMEmulatorBuildCommand: + * + * @tpm: TPM definition + * @vmname: The name of the VM + * @vmuuid: The UUID of the VM + * @privileged: whether we are running in privileged mode + * @swtpm_user: The uid for the swtpm to run as (drop privileges to from root) + * @swtpm_group: The gid for the swtpm to run as + * + * Create the virCommand use for starting the emulator + * Do some initializations on the way, such as creation of storage + * and emulator setup. + */ +virCommandPtr +virTPMEmulatorBuildCommand(virDomainTPMDefPtr tpm, const char *vmname, + const unsigned char *vmuuid, bool privileged, + uid_t swtpm_user, gid_t swtpm_group) One arg per line
+{ + virCommandPtr cmd = NULL; + bool created = false; + + if (virTPMCreateEmulatorStorage(tpm->data.emulator.storagepath, + &created, swtpm_user, swtpm_group) < 0) + return NULL; + + if (created && + virTPMEmulatorRunSetup(tpm->data.emulator.storagepath, vmname, vmuuid, + privileged, swtpm_user, swtpm_group, + tpm->data.emulator.logfile) < 0) + goto error; + + unlink(tpm->data.emulator.source.data.nix.path); + + cmd = virCommandNew(swtpm_path); + if (!cmd) + goto error; + + virCommandClearCaps(cmd); + + virCommandAddArgList(cmd, "socket", "--daemon", "--ctrl", NULL); + virCommandAddArgFormat(cmd, "type=unixio,path=%s,mode=0600", + tpm->data.emulator.source.data.nix.path); + + virCommandAddArg(cmd, "--tpmstate"); + virCommandAddArgFormat(cmd, "dir=%s,mode=0600", + tpm->data.emulator.storagepath); + + virCommandAddArg(cmd, "--log"); + virCommandAddArgFormat(cmd, "file=%s", tpm->data.emulator.logfile); + + virCommandSetUID(cmd, swtpm_user); + virCommandSetGID(cmd, swtpm_group); + + return cmd; + + error: + if (created) + virTPMDeleteEmulatorStorage(tpm); + + VIR_FREE(tpm->data.emulator.source.data.nix.path); + VIR_FREE(tpm->data.emulator.storagepath); Similar question here about the VIR_FREE's - why not wait for virDomainTPMDefFree?
Dropped these as well.
+ + virCommandFree(cmd); + + return NULL; +} + +/* + * virTPMEmulatorStop + * @swtpmStateDir: A directory where the socket is located + * @shortName: short and unique name of the domain + * + * Gracefully stop the swptm + */ +void +virTPMEmulatorStop(const char *swtpmStateDir, const char *shortName) +{ + virCommandPtr cmd; + char *pathname; + char *errbuf = NULL; + + if (virTPMEmulatorInit() < 0) + return; + + if (!(pathname = virTPMCreateEmulatorSocket(swtpmStateDir, shortName))) + return; + + if (!virFileExists(pathname)) + goto cleanup; + + cmd = virCommandNew(swtpm_ioctl); + if (!cmd) { + VIR_FREE(pathname); ^^^^ Done in cleanup anyway.
+ goto cleanup; + } + + virCommandAddArgList(cmd, "--unix", pathname, "-s", NULL); + + virCommandSetErrorBuffer(cmd, &errbuf); + + ignore_value(virCommandRun(cmd, NULL)); + + virCommandFree(cmd); + + /* clean up the socket */ + unlink(pathname); + + cleanup: + VIR_FREE(pathname); + VIR_FREE(errbuf); +} diff --git a/src/util/virtpm.h b/src/util/virtpm.h index b21fc05..63f75b8 100644 --- a/src/util/virtpm.h +++ b/src/util/virtpm.h @@ -1,7 +1,7 @@ /* * virtpm.h: TPM support * - * Copyright (C) 2013 IBM Corporation + * Copyright (C) 2013,2018 IBM Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,6 +22,37 @@ #ifndef __VIR_TPM_H__ # define __VIR_TPM_H__
+# include "vircommand.h" + +typedef struct _virDomainTPMDef virDomainTPMDef; +typedef virDomainTPMDef *virDomainTPMDefPtr; + Duplicated from domain_conf.h to avoid errors, crafty...
I have a feeling most of this is going to be merged into patch 9...
Thanks for reviewing. Stefan