All of lore.kernel.org
 help / color / mirror / Atom feed
From: David Tolnay <dtolnay@gmail.com>
To: Peter Huewe <peterhuewe@gmx.de>,
	Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>,
	linux-integrity@vger.kernel.org,
	"Michael S. Tsirkin" <mst@redhat.com>,
	Jason Wang <jasowang@redhat.com>,
	virtualization@lists.linux-foundation.org, dgreid@chromium.org,
	apronin@chromium.org
Subject: [PATCH] tpm: Add driver for TPM over virtio
Date: Thu, 21 Feb 2019 18:14:02 -0800	[thread overview]
Message-ID: <388c5b80-21a7-1e91-a11f-3a1c1432368b@gmail.com> (raw)

Add a config TCG_VIRTIO_VTPM which enables a driver providing the guest
kernel side of TPM over virtio.

Use case: TPM support is needed for performing trusted work from within
a virtual machine launched by Chrome OS.

Tested inside crosvm, the Chrome OS virtual machine monitor. Crosvm's
implementation of the virtio TPM device can be found in these two source
files:

- https://chromium.googlesource.com/chromiumos/platform/crosvm/+/18ce5713e6cb99c40aafec52b67c28ba12a44f31/devices/src/virtio/tpm.rs
- https://chromium.googlesource.com/chromiumos/platform/crosvm/+/18ce5713e6cb99c40aafec52b67c28ba12a44f31/tpm2/src/lib.rs

and is currently backed by the libtpm2 TPM simulator:

- https://chromium.googlesource.com/chromiumos/third_party/tpm2/

Reviewed-on: https://chromium-review.googlesource.com/1387655
Reviewed-by: Andrey Pronin <apronin@chromium.org>
Tested-by: David Tolnay <dtolnay@gmail.com>
Signed-off-by: David Tolnay <dtolnay@gmail.com>
---
UNRESOLVED:
The driver assumes virtio device number VIRTIO_ID_TPM=31. If there is
interest in accepting a virtio TPM driver into the kernel, the Chrome OS
team will coordinate with the OASIS virtio technical committee to secure
an agreed upon device number and include it in a later patchset.

 drivers/char/tpm/Kconfig      |   9 +
 drivers/char/tpm/Makefile     |   1 +
 drivers/char/tpm/tpm_virtio.c | 460 ++++++++++++++++++++++++++++++++++
 3 files changed, 470 insertions(+)
 create mode 100644 drivers/char/tpm/tpm_virtio.c

diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index 536e55d3919f..8997060e248e 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -164,6 +164,15 @@ config TCG_VTPM_PROXY
 	  /dev/vtpmX and a server-side file descriptor on which the vTPM
 	  can receive commands.
 
+config TCG_VIRTIO_VTPM
+	tristate "Virtio vTPM"
+	depends on TCG_TPM
+	help
+	  This driver provides the guest kernel side of TPM over Virtio. If
+	  you are building Linux to run inside of a hypervisor that supports
+	  TPM over Virtio, say Yes and the virtualized TPM will be
+	  accessible from the guest.
+
 
 source "drivers/char/tpm/st33zp24/Kconfig"
 endif # TCG_TPM
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index a01c4cab902a..4f5d1071257a 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -33,3 +33,4 @@ obj-$(CONFIG_TCG_TIS_ST33ZP24) += st33zp24/
 obj-$(CONFIG_TCG_XEN) += xen-tpmfront.o
 obj-$(CONFIG_TCG_CRB) += tpm_crb.o
 obj-$(CONFIG_TCG_VTPM_PROXY) += tpm_vtpm_proxy.o
+obj-$(CONFIG_TCG_VIRTIO_VTPM) += tpm_virtio.o
diff --git a/drivers/char/tpm/tpm_virtio.c b/drivers/char/tpm/tpm_virtio.c
new file mode 100644
index 000000000000..f3239d983f18
--- /dev/null
+++ b/drivers/char/tpm/tpm_virtio.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Author: David Tolnay <dtolnay@gmail.com>
+ *
+ * ---
+ *
+ * Device driver for TPM over virtio.
+ *
+ * This driver employs a single virtio queue to handle both send and recv. TPM
+ * commands are sent over virtio to the hypervisor during a TPM send operation
+ * and responses are received over the same queue during a recv operation.
+ *
+ * The driver contains a single buffer that is the only buffer we ever place on
+ * the virtio queue. Commands are copied from the caller's command buffer into
+ * the driver's buffer before handing off to virtio, and responses are received
+ * into the driver's buffer then copied into the caller's response buffer. This
+ * allows us to be resilient to timeouts. When a send or recv operation times
+ * out, the caller is free to destroy their buffer; we don't want the hypervisor
+ * continuing to perform reads or writes against that destroyed buffer.
+ *
+ * This driver does not support concurrent send and recv operations. Mutual
+ * exclusion is upheld by the tpm_mutex lock held in tpm-interface.c around the
+ * calls to chip->ops->send and chip->ops->recv.
+ *
+ * The intended hypervisor-side implementation is as follows.
+ *
+ *     while true:
+ *         await next virtio buffer.
+ *         expect first descriptor in chain to be guest-to-host.
+ *         read tpm command from that buffer.
+ *         synchronously perform TPM work determined by the command.
+ *         expect second descriptor in chain to be host-to-guest.
+ *         write TPM response into that buffer.
+ *         place buffer on virtio used queue indicating how many bytes written.
+ */
+
+#include <linux/virtio_config.h>
+
+#include "tpm.h"
+
+/*
+ * Timeout duration when waiting on the hypervisor to complete its end of the
+ * TPM operation. This timeout is relatively high because certain TPM operations
+ * can take dozens of seconds.
+ */
+#define TPM_VIRTIO_TIMEOUT (120 * HZ)
+
+struct vtpm_device {
+	/*
+	 * Data structure for integration with the common code of the TPM driver
+	 * in tpm-chip.c.
+	 */
+	struct tpm_chip *chip;
+
+	/*
+	 * Virtio queue for sending TPM commands out of the virtual machine and
+	 * receiving TPM responses back from the hypervisor.
+	 */
+	struct virtqueue *vq;
+
+	/*
+	 * Completion that is notified when a virtio operation has been
+	 * fulfilled by the hypervisor.
+	 */
+	struct completion complete;
+
+	/*
+	 * Whether driver currently holds ownership of the virtqueue buffer.
+	 * When false, the hypervisor is in the process of reading or writing
+	 * the buffer and the driver must not touch it.
+	 */
+	bool driver_has_buffer;
+
+	/*
+	 * Whether during the most recent TPM operation, a virtqueue_kick failed
+	 * or a wait timed out.
+	 *
+	 * The next send or recv operation will attempt a kick upon seeing this
+	 * status. That should clear up the queue in the case that the
+	 * hypervisor became temporarily nonresponsive, such as by resource
+	 * exhaustion on the host. The extra kick enables recovery from kicks
+	 * going unnoticed by the hypervisor as well as recovery from virtio
+	 * callbacks going unnoticed by the guest kernel.
+	 */
+	bool needs_kick;
+
+	/* Number of bytes available to read from the virtqueue buffer. */
+	unsigned int readable;
+
+	/*
+	 * Buffer in which all virtio transfers take place. Buffer size is the
+	 * maximum legal TPM command or response message size.
+	 */
+	u8 virtqueue_buffer[TPM_BUFSIZE];
+};
+
+/*
+ * Wait for ownership of the virtqueue buffer.
+ *
+ * The why-string should begin with "waiting to..." or "waiting for..." with no
+ * trailing newline. It will appear in log output.
+ *
+ * Returns zero for success, otherwise negative error.
+ */
+static int vtpm_wait_for_buffer(struct vtpm_device *dev, const char *why)
+{
+	int ret;
+	bool did_kick;
+	struct tpm_chip *chip = dev->chip;
+	unsigned long deadline = jiffies + TPM_VIRTIO_TIMEOUT;
+
+	/* Kick queue if needed. */
+	if (dev->needs_kick) {
+		did_kick = virtqueue_kick(dev->vq);
+		if (!did_kick) {
+			dev_notice(&chip->dev, "kick failed; will retry\n");
+			return -EBUSY;
+		}
+		dev->needs_kick = false;
+	}
+
+	while (!dev->driver_has_buffer) {
+		unsigned long now = jiffies;
+
+		/* Check timeout, otherwise `deadline - now` may underflow. */
+		if time_after_eq(now, deadline) {
+			dev_warn(&chip->dev, "timed out %s\n", why);
+			dev->needs_kick = true;
+			return -ETIMEDOUT;
+		}
+
+		/*
+		 * Wait to be signaled by virtio callback.
+		 *
+		 * Positive ret is jiffies remaining until timeout when the
+		 * completion occurred, which means successful completion. Zero
+		 * ret is timeout. Negative ret is error.
+		 */
+		ret = wait_for_completion_killable_timeout(
+				&dev->complete, deadline - now);
+
+		/* Log if completion did not occur. */
+		if (ret == -ERESTARTSYS) {
+			/* Not a warning if it was simply interrupted. */
+			dev_dbg(&chip->dev, "interrupted %s\n", why);
+		} else if (ret == 0) {
+			dev_warn(&chip->dev, "timed out %s\n", why);
+			ret = -ETIMEDOUT;
+		} else if (ret < 0) {
+			dev_warn(&chip->dev, "failed while %s: error %d\n",
+					why, -ret);
+		}
+
+		/*
+		 * Return error if completion did not occur. Schedule kick to be
+		 * retried at the start of the next send/recv to help unblock
+		 * the queue.
+		 */
+		if (ret < 0) {
+			dev->needs_kick = true;
+			return ret;
+		}
+
+		/* Completion occurred. Expect response buffer back. */
+		if (virtqueue_get_buf(dev->vq, &dev->readable)) {
+			dev->driver_has_buffer = true;
+
+			if (dev->readable > TPM_BUFSIZE) {
+				dev_crit(&chip->dev,
+						"hypervisor bug: response exceeds max size, %u > %u\n",
+						dev->readable,
+						(unsigned int) TPM_BUFSIZE);
+				dev->readable = TPM_BUFSIZE;
+				return -EPROTO;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int vtpm_op_send(struct tpm_chip *chip, u8 *caller_buf, size_t len)
+{
+	int ret;
+	bool did_kick;
+	struct scatterlist sg_outbuf, sg_inbuf;
+	struct scatterlist *sgs[2] = { &sg_outbuf, &sg_inbuf };
+	struct vtpm_device *dev = dev_get_drvdata(&chip->dev);
+	u8 *virtqueue_buf = dev->virtqueue_buffer;
+
+	dev_dbg(&chip->dev, __func__ " %zu bytes\n", len);
+
+	if (len > TPM_BUFSIZE) {
+		dev_err(&chip->dev,
+				"command is too long, %zu > %zu\n",
+				len, (size_t) TPM_BUFSIZE);
+		return -EINVAL;
+	}
+
+	/*
+	 * Wait until hypervisor relinquishes ownership of the virtqueue buffer.
+	 *
+	 * This may block if the previous recv operation timed out in the guest
+	 * kernel but is still being processed by the hypervisor. Also may block
+	 * if send operations are performed back-to-back, such as if something
+	 * in the caller failed in between a send and recv.
+	 *
+	 * During normal operation absent of any errors or timeouts, this does
+	 * not block.
+	 */
+	ret = vtpm_wait_for_buffer(dev, "waiting to begin send");
+	if (ret)
+		return ret;
+
+	/* Driver owns virtqueue buffer and may now write into it. */
+	memcpy(virtqueue_buf, caller_buf, len);
+
+	/*
+	 * Enqueue the virtqueue buffer once as outgoing virtio data (written by
+	 * the virtual machine and read by the hypervisor) and again as incoming
+	 * data (written by the hypervisor and read by the virtual machine).
+	 * This step moves ownership of the virtqueue buffer from driver to
+	 * hypervisor.
+	 *
+	 * Note that we don't know here how big of a buffer the caller will use
+	 * with their later call to recv. We allow the hypervisor to write up to
+	 * the TPM max message size. If the caller ends up using a smaller
+	 * buffer with recv that is too small to hold the entire response, the
+	 * recv will return an error. This conceivably breaks TPM
+	 * implementations that want to produce a different verbosity of
+	 * response depending on the receiver's buffer size.
+	 */
+	sg_init_one(&sg_outbuf, virtqueue_buf, len);
+	sg_init_one(&sg_inbuf, virtqueue_buf, TPM_BUFSIZE);
+	ret = virtqueue_add_sgs(dev->vq, sgs, 1, 1, virtqueue_buf, GFP_KERNEL);
+	if (ret) {
+		dev_err(&chip->dev, "failed virtqueue_add_sgs\n");
+		return ret;
+	}
+
+	/* Kick the other end of the virtqueue after having added a buffer. */
+	did_kick = virtqueue_kick(dev->vq);
+	if (!did_kick) {
+		dev->needs_kick = true;
+		dev_notice(&chip->dev, "kick failed; will retry\n");
+
+		/*
+		 * We return 0 anyway because what the caller doesn't know can't
+		 * hurt them. They can call recv and it will retry the kick. If
+		 * that works, everything is fine.
+		 *
+		 * If the retry in recv fails too, they will get -EBUSY from
+		 * recv.
+		 */
+	}
+
+	/*
+	 * Hypervisor is now processing the TPM command asynchronously. It will
+	 * read the command from the output buffer and write the response into
+	 * the input buffer (which are the same buffer). When done, it will send
+	 * back the buffers over virtio and the driver's virtio callback will
+	 * complete dev->complete so that we know the response is ready to be
+	 * read.
+	 *
+	 * It is important to have copied data out of the caller's buffer into
+	 * the driver's virtqueue buffer because the caller is free to destroy
+	 * their buffer when this call returns. We can't avoid copying by
+	 * waiting here for the hypervisor to finish reading, because if that
+	 * wait times out, we return and the caller may destroy their buffer
+	 * while the hypervisor is continuing to read from it.
+	 */
+	dev->driver_has_buffer = false;
+	return 0;
+}
+
+static int vtpm_op_recv(struct tpm_chip *chip, u8 *caller_buf, size_t len)
+{
+	int ret;
+	struct vtpm_device *dev = dev_get_drvdata(&chip->dev);
+	u8 *virtqueue_buf = dev->virtqueue_buffer;
+
+	dev_dbg(&chip->dev, __func__ "\n");
+
+	/*
+	 * Wait until the virtqueue buffer is owned by the driver.
+	 *
+	 * This will usually block while the hypervisor finishes processing the
+	 * most recent TPM command.
+	 */
+	ret = vtpm_wait_for_buffer(dev, "waiting for TPM response");
+	if (ret)
+		return ret;
+
+	dev_dbg(&chip->dev, "received %u bytes\n", dev->readable);
+
+	if (dev->readable > len) {
+		dev_notice(&chip->dev,
+				"TPM response is bigger than receiver's buffer: %u > %zu\n",
+				dev->readable, len);
+		return -EINVAL;
+	}
+
+	/* Copy response over to the caller. */
+	memcpy(caller_buf, virtqueue_buf, dev->readable);
+
+	return dev->readable;
+}
+
+static void vtpm_op_cancel(struct tpm_chip *chip)
+{
+	/*
+	 * Cancel is not called in this driver's use of tpm-interface.c. It may
+	 * be triggered through tpm-sysfs but that can be implemented as needed.
+	 * Be aware that tpm-sysfs performs cancellation without holding the
+	 * tpm_mutex that protects our send and recv operations, so a future
+	 * implementation will need to consider thread safety of concurrent
+	 * send/recv and cancel.
+	 */
+	dev_notice(&chip->dev, "cancellation is not implemented\n");
+}
+
+static u8 vtpm_op_status(struct tpm_chip *chip)
+{
+	/*
+	 * Status is for TPM drivers that want tpm-interface.c to poll for
+	 * completion before calling recv. Usually this is when the hardware
+	 * needs to be polled i.e. there is no other way for recv to block on
+	 * the TPM command completion.
+	 *
+	 * Polling goes until `(status & complete_mask) == complete_val`. This
+	 * driver defines both complete_mask and complete_val as 0 and blocks on
+	 * our own completion object in recv instead.
+	 */
+	return 0;
+}
+
+static const struct tpm_class_ops vtpm_ops = {
+	.flags = TPM_OPS_AUTO_STARTUP,
+	.send = vtpm_op_send,
+	.recv = vtpm_op_recv,
+	.cancel = vtpm_op_cancel,
+	.status = vtpm_op_status,
+	.req_complete_mask = 0,
+	.req_complete_val = 0,
+};
+
+static void vtpm_virtio_complete(struct virtqueue *vq)
+{
+	struct virtio_device *vdev = vq->vdev;
+	struct vtpm_device *dev = vdev->priv;
+
+	complete(&dev->complete);
+}
+
+static int vtpm_probe(struct virtio_device *vdev)
+{
+	int err;
+	struct vtpm_device *dev;
+	struct virtqueue *vq;
+	struct tpm_chip *chip;
+
+	dev_dbg(&vdev->dev, __func__ "\n");
+
+	dev = kzalloc(sizeof(struct vtpm_device), GFP_KERNEL);
+	if (!dev) {
+		err = -ENOMEM;
+		dev_err(&vdev->dev, "failed kzalloc\n");
+		goto err_dev_alloc;
+	}
+	vdev->priv = dev;
+
+	vq = virtio_find_single_vq(vdev, vtpm_virtio_complete, "vtpm");
+	if (IS_ERR(vq)) {
+		err = PTR_ERR(vq);
+		dev_err(&vdev->dev, "failed virtio_find_single_vq\n");
+		goto err_virtio_find;
+	}
+	dev->vq = vq;
+
+	chip = tpm_chip_alloc(&vdev->dev, &vtpm_ops);
+	if (IS_ERR(chip)) {
+		err = PTR_ERR(chip);
+		dev_err(&vdev->dev, "failed tpm_chip_alloc\n");
+		goto err_chip_alloc;
+	}
+	dev_set_drvdata(&chip->dev, dev);
+	chip->flags |= TPM_CHIP_FLAG_TPM2;
+	dev->chip = chip;
+
+	init_completion(&dev->complete);
+	dev->driver_has_buffer = true;
+	dev->needs_kick = false;
+	dev->readable = 0;
+
+	/*
+	 * Required in order to enable vq use in probe function for auto
+	 * startup.
+	 */
+	virtio_device_ready(vdev);
+
+	err = tpm_chip_register(dev->chip);
+	if (err) {
+		dev_err(&vdev->dev, "failed tpm_chip_register\n");
+		goto err_chip_register;
+	}
+
+	return 0;
+
+err_chip_register:
+	put_device(&dev->chip->dev);
+err_chip_alloc:
+	vdev->config->del_vqs(vdev);
+err_virtio_find:
+	kfree(dev);
+err_dev_alloc:
+	return err;
+}
+
+static void vtpm_remove(struct virtio_device *vdev)
+{
+	struct vtpm_device *dev = vdev->priv;
+
+	/* Undo tpm_chip_register. */
+	tpm_chip_unregister(dev->chip);
+
+	/* Undo tpm_chip_alloc. */
+	put_device(&dev->chip->dev);
+
+	vdev->config->reset(vdev);
+	vdev->config->del_vqs(vdev);
+
+	kfree(dev);
+}
+
+#define VIRTIO_ID_TPM 31
+
+static struct virtio_device_id id_table[] = {
+	{
+		.device = VIRTIO_ID_TPM,
+		.vendor = VIRTIO_DEV_ANY_ID,
+	},
+	{},
+};
+
+static struct virtio_driver vtpm_driver = {
+	.driver.name = KBUILD_MODNAME,
+	.driver.owner = THIS_MODULE,
+	.id_table = id_table,
+	.probe = vtpm_probe,
+	.remove = vtpm_remove,
+};
+
+module_virtio_driver(vtpm_driver);
+
+MODULE_AUTHOR("David Tolnay (dtolnay@gmail.com)");
+MODULE_DESCRIPTION("Virtio vTPM Driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
-- 
2.20.1

             reply	other threads:[~2019-02-22  2:14 UTC|newest]

Thread overview: 57+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-02-22  2:14 David Tolnay [this message]
2019-02-22  5:51 ` [PATCH] tpm: Add driver for TPM over virtio Michael S. Tsirkin
2019-02-22  5:51   ` Michael S. Tsirkin
2019-02-22 21:40   ` David Tolnay
2019-02-22 22:24     ` Michael S. Tsirkin
2019-02-22 22:24       ` Michael S. Tsirkin
2019-02-23  1:23       ` David Tolnay
2019-02-25  9:58   ` Jarkko Sakkinen
2019-02-22 10:26 ` Jarkko Sakkinen
2019-02-22 15:23   ` Michael S. Tsirkin
2019-02-22 15:23     ` Michael S. Tsirkin
2019-02-22 19:31     ` Jarkko Sakkinen
2019-02-22 19:33       ` Jarkko Sakkinen
2019-02-22 21:25         ` Michael S. Tsirkin
2019-02-22 21:25           ` Michael S. Tsirkin
2019-02-22 21:50           ` Jarkko Sakkinen
2019-02-22 22:24             ` David Tolnay
2019-02-22 22:36               ` Jarkko Sakkinen
2019-02-22 23:05                 ` Michael S. Tsirkin
2019-02-22 23:05                   ` Michael S. Tsirkin
2019-02-24  9:33                   ` Jarkko Sakkinen
2019-02-22 20:55       ` Michael S. Tsirkin
2019-02-22 20:55         ` Michael S. Tsirkin
2019-02-22 21:30         ` Jarkko Sakkinen
2019-02-22 10:30 ` Jarkko Sakkinen
2019-02-22 15:30 ` James Bottomley
2019-02-22 21:16   ` Michael S. Tsirkin
2019-02-22 21:16     ` Michael S. Tsirkin
2019-02-22 21:31     ` Jason Gunthorpe
2019-02-22 21:59       ` Jarkko Sakkinen
2019-02-22 22:07         ` Michael S. Tsirkin
2019-02-22 22:07           ` Michael S. Tsirkin
2019-02-22 22:14           ` Jarkko Sakkinen
2019-02-22 22:00   ` David Tolnay
2019-02-22 22:18     ` James Bottomley
2019-02-23  0:45       ` David Tolnay
2019-02-23  1:34         ` James Bottomley
2019-02-23  2:41           ` David Tolnay
2019-02-24 16:30             ` James Bottomley
2019-02-24 17:51               ` Jarkko Sakkinen
2019-02-24 22:12               ` David Tolnay
2019-02-25  9:55                 ` Jarkko Sakkinen
2019-02-25 15:36                 ` James Bottomley
2019-02-25 19:17                   ` Matthew Garrett
2019-02-25 19:54                     ` Jarkko Sakkinen
2019-02-25 20:20                     ` James Bottomley
2019-02-25 21:00                       ` Matthew Garrett
2019-02-25 21:02                         ` Matthew Garrett
2019-02-25 22:14                         ` James Bottomley
2019-02-25 22:24                           ` Matthew Garrett
2019-02-25 22:32                             ` James Bottomley
2019-02-25 22:43                               ` Matthew Garrett
2019-02-25 22:51                                 ` James Bottomley
2019-02-25 23:02                                   ` Matthew Garrett
2019-02-25 23:09                                     ` James Bottomley
2019-02-25 21:05                       ` Jarkko Sakkinen
2019-02-25 22:24                         ` James Bottomley

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=388c5b80-21a7-1e91-a11f-3a1c1432368b@gmail.com \
    --to=dtolnay@gmail.com \
    --cc=apronin@chromium.org \
    --cc=dgreid@chromium.org \
    --cc=jarkko.sakkinen@linux.intel.com \
    --cc=jasowang@redhat.com \
    --cc=jgg@ziepe.ca \
    --cc=linux-integrity@vger.kernel.org \
    --cc=mst@redhat.com \
    --cc=peterhuewe@gmx.de \
    --cc=virtualization@lists.linux-foundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.