diff mbox series

[v2,08/18] PCI/CMA: Authenticate devices on enumeration

Message ID 6d4361f13a942efc4b4d33d22e56b564c4362328.1719771133.git.lukas@wunner.de
State New
Headers show
Series PCI device authentication | expand

Commit Message

Lukas Wunner June 30, 2024, 7:43 p.m. UTC
From: Jonathan Cameron <Jonathan.Cameron@huawei.com>

Component Measurement and Authentication (CMA, PCIe r6.2 sec 6.31)
allows for measurement and authentication of PCIe devices.  It is
based on the Security Protocol and Data Model specification (SPDM,
https://www.dmtf.org/dsp/DSP0274).

CMA-SPDM in turn forms the basis for Integrity and Data Encryption
(IDE, PCIe r6.2 sec 6.33) because the key material used by IDE is
transmitted over a CMA-SPDM session.

As a first step, authenticate CMA-capable devices on enumeration.
A subsequent commit will expose the result in sysfs.

When allocating SPDM session state with spdm_create(), the maximum SPDM
message length needs to be passed.  Make the PCI_DOE_MAX_LENGTH macro
public and calculate the maximum payload length from it.

Credits:  Jonathan wrote a proof-of-concept of this CMA implementation.
Lukas reworked it for upstream.  Wilfred contributed fixes for issues
discovered during testing.

Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Co-developed-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Signed-off-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Co-developed-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
 MAINTAINERS             |   1 +
 drivers/pci/Kconfig     |  13 ++++++
 drivers/pci/Makefile    |   2 +
 drivers/pci/cma.c       | 100 ++++++++++++++++++++++++++++++++++++++++
 drivers/pci/doe.c       |   3 --
 drivers/pci/pci.h       |   8 ++++
 drivers/pci/probe.c     |   1 +
 drivers/pci/remove.c    |   1 +
 include/linux/pci-doe.h |   4 ++
 include/linux/pci.h     |   4 ++
 10 files changed, 134 insertions(+), 3 deletions(-)
 create mode 100644 drivers/pci/cma.c

Comments

Dan Williams July 9, 2024, 6:10 p.m. UTC | #1
Lukas Wunner wrote:
> From: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> 
> Component Measurement and Authentication (CMA, PCIe r6.2 sec 6.31)
> allows for measurement and authentication of PCIe devices.  It is
> based on the Security Protocol and Data Model specification (SPDM,
> https://www.dmtf.org/dsp/DSP0274).
> 
> CMA-SPDM in turn forms the basis for Integrity and Data Encryption
> (IDE, PCIe r6.2 sec 6.33) because the key material used by IDE is
> transmitted over a CMA-SPDM session.
> 
> As a first step, authenticate CMA-capable devices on enumeration.
> A subsequent commit will expose the result in sysfs.
> 
> When allocating SPDM session state with spdm_create(), the maximum SPDM
> message length needs to be passed.  Make the PCI_DOE_MAX_LENGTH macro
> public and calculate the maximum payload length from it.
> 
> Credits:  Jonathan wrote a proof-of-concept of this CMA implementation.
> Lukas reworked it for upstream.  Wilfred contributed fixes for issues
> discovered during testing.
> 
> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Co-developed-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
> Signed-off-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
> Co-developed-by: Lukas Wunner <lukas@wunner.de>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
> ---
>  MAINTAINERS             |   1 +
>  drivers/pci/Kconfig     |  13 ++++++
>  drivers/pci/Makefile    |   2 +
>  drivers/pci/cma.c       | 100 ++++++++++++++++++++++++++++++++++++++++
>  drivers/pci/doe.c       |   3 --
>  drivers/pci/pci.h       |   8 ++++
>  drivers/pci/probe.c     |   1 +
>  drivers/pci/remove.c    |   1 +
>  include/linux/pci-doe.h |   4 ++
>  include/linux/pci.h     |   4 ++
>  10 files changed, 134 insertions(+), 3 deletions(-)
>  create mode 100644 drivers/pci/cma.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index dbe16eea8818..9aad3350da16 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20153,6 +20153,7 @@ L:	linux-cxl@vger.kernel.org
>  L:	linux-pci@vger.kernel.org
>  S:	Maintained
>  T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
> +F:	drivers/pci/cma*
>  F:	include/linux/spdm.h
>  F:	lib/spdm/
>  
> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> index d35001589d88..f656211d707a 100644
> --- a/drivers/pci/Kconfig
> +++ b/drivers/pci/Kconfig
> @@ -121,6 +121,19 @@ config XEN_PCIDEV_FRONTEND
>  config PCI_ATS
>  	bool
>  
> +config PCI_CMA
> +	bool "Component Measurement and Authentication (CMA-SPDM)"

What is driving the requirement for CMA to be built-in?

All of the use cases I know about to date are built around userspace
policy auditing devices after the fact. Certainly a deployment could
choose to build it in, but it is a significant amount of infrastructure
that could tolerate late loading.

PCI TSM will be late loaded, so it is already the case that depending on
the authentication mechanism chosen (native, or TSM) the system needs to
be prepared for late / dynamic authentication.
Lukas Wunner July 9, 2024, 7:32 p.m. UTC | #2
On Tue, Jul 09, 2024 at 11:10:57AM -0700, Dan Williams wrote:
> Lukas Wunner wrote:
> > --- a/drivers/pci/Kconfig
> > +++ b/drivers/pci/Kconfig
> > @@ -121,6 +121,19 @@ config XEN_PCIDEV_FRONTEND
> >  config PCI_ATS
> >  	bool
> >  
> > +config PCI_CMA
> > +	bool "Component Measurement and Authentication (CMA-SPDM)"
> 
> What is driving the requirement for CMA to be built-in?

There is no way to auto-load modules needed for certain PCI features.
We'd have to call request_module() on PCI bus enumeration when
encountering devices with specific PCI features.  But what do we do
if module loading fails?  The PCI bus is enumerated in a subsys_initcall,
when neither the root filesystem has been mounted nor run_init_process()
has been called.  So any PCI core modules would have to be in the initrd.
What if they aren't?  Kernel panic?  That question seems particularly
pertinent for a security feature like CMA.

So we've made PCI core features non-modular by default.
In seven cases we even switched from tristate to bool because building
as modules turned out not to be working properly:

82280f7af729 ("PCI: shpchp: Convert SHPC to be builtin only")
a4959d8c1eaa ("PCI: Remove DPC tristate module option")
774104399459 ("PCI: Convert ioapic to be builtin only, not modular")
67f43f38eeb3 ("s390/pci/hotplug: convert to be builtin only")
c10cc483bf3f ("PCI: pciehp: Convert pciehp to be builtin only, not modular")
7cd29f4b22be ("PCI: hotplug: Convert to be builtin only, not modular")
6037a803b05e ("PCI: acpiphp: Convert acpiphp to be builtin only, not modular")

There has not been a single case where we switched from bool to tristate,
with the exception of PCI_IOAPIC with commit b95a7bd70046, but that was
subsequently reverted back to bool with the above-listed 774104399459.


> All of the use cases I know about to date are built around userspace
> policy auditing devices after the fact.

I think we should also support use cases where user space sets a policy
(e.g. not to bind devices to a driver unless they authenticate) and lets
the kernel do the rest (i.e. autonomously authenticate devices based on
a set of trusted root certificates).  User space does not *have* to be
the one auditing each device on a case-by-case basis, although I do see
the usefulness of such scenarios and am open to supporting them.  In fact
this v2 takes a step in that direction by exposing signatures received
from the device to user space and doing so even if the kernel cannot
validate the device's certificate chains as well-formed and trusted.

In other words, I'm trying to support both:  Fully autonomous in-kernel
authentication of certificates, but also allowing user space to make a
decision if it wants to.  It's simply not clear to me at the moment
what the use cases will be.  I can very well imagine that, say,
ChromeBooks will want to authenticate Thunderbolt-attached PCI devices
based on a keyring of trusted vendor certificates.  The fully autonomous
in-kernel authentication caters to such a use case.  I don't want to
preclude such use cases just because Confidential Computing in the
cloud happens to be the buzzword du jour.

Thanks,

Lukas
Dan Williams July 9, 2024, 11:31 p.m. UTC | #3
Lukas Wunner wrote:
> On Tue, Jul 09, 2024 at 11:10:57AM -0700, Dan Williams wrote:
> > Lukas Wunner wrote:
> > > --- a/drivers/pci/Kconfig
> > > +++ b/drivers/pci/Kconfig
> > > @@ -121,6 +121,19 @@ config XEN_PCIDEV_FRONTEND
> > >  config PCI_ATS
> > >  	bool
> > >  
> > > +config PCI_CMA
> > > +	bool "Component Measurement and Authentication (CMA-SPDM)"
> > 
> > What is driving the requirement for CMA to be built-in?
> 
> There is no way to auto-load modules needed for certain PCI features.

TSM is taking the approach of dynamically adjusting the visibility of
TSM attributes when the platform TSM driver registers with the PCI core.
It is forced to do this because a TSM controller may itself be a PCI
that needs a driver to load before the PCI core attributes are usable.

For native functionality, yes, it would indeed take synthetic device to
play the same role.

> We'd have to call request_module() on PCI bus enumeration when
> encountering devices with specific PCI features.  But what do we do
> if module loading fails?  The PCI bus is enumerated in a subsys_initcall,
> when neither the root filesystem has been mounted nor run_init_process()
> has been called.  So any PCI core modules would have to be in the initrd.
> What if they aren't?  Kernel panic?  That question seems particularly
> pertinent for a security feature like CMA.

Non-authenticated operation is the status quo. CMA is a building block
to other security features. Nothing currently cares about CMA being
established before a driver loads and it is not clear that now is the
time to for the kernel to paint itself into a corner to make that
guarantee.

> So we've made PCI core features non-modular by default.
> In seven cases we even switched from tristate to bool because building
> as modules turned out not to be working properly:
> 
> 82280f7af729 ("PCI: shpchp: Convert SHPC to be builtin only")
> a4959d8c1eaa ("PCI: Remove DPC tristate module option")
> 774104399459 ("PCI: Convert ioapic to be builtin only, not modular")
> 67f43f38eeb3 ("s390/pci/hotplug: convert to be builtin only")
> c10cc483bf3f ("PCI: pciehp: Convert pciehp to be builtin only, not modular")
> 7cd29f4b22be ("PCI: hotplug: Convert to be builtin only, not modular")
> 6037a803b05e ("PCI: acpiphp: Convert acpiphp to be builtin only, not modular")
> 
> There has not been a single case where we switched from bool to tristate,
> with the exception of PCI_IOAPIC with commit b95a7bd70046, but that was
> subsequently reverted back to bool with the above-listed 774104399459.

That's good history that I was not aware, thanks for that.

However, most of those seem to be knock-on effects of:

https://lore.kernel.org/all/20121207062454.11051.12739.stgit@amt.stowe/

...where init order constraints between ACPI and PCI functionality led
to modules not being viable. The DPC one does not fit that model, but
DPC is small enough and entangled with AER to not really justify it
being a standalone module.

> > All of the use cases I know about to date are built around userspace
> > policy auditing devices after the fact.
> 
> I think we should also support use cases where user space sets a policy
> (e.g. not to bind devices to a driver unless they authenticate) and lets
> the kernel do the rest (i.e. autonomously authenticate devices based on
> a set of trusted root certificates).  User space does not *have* to be
> the one auditing each device on a case-by-case basis, although I do see
> the usefulness of such scenarios and am open to supporting them.  In fact
> this v2 takes a step in that direction by exposing signatures received
> from the device to user space and doing so even if the kernel cannot
> validate the device's certificate chains as well-formed and trusted.

Userspace validation of authentication and measurement is separate from
whether the functionality is built-in or not.

> In other words, I'm trying to support both:  Fully autonomous in-kernel
> authentication of certificates, but also allowing user space to make a
> decision if it wants to.  It's simply not clear to me at the moment
> what the use cases will be.  I can very well imagine that, say,
> ChromeBooks will want to authenticate Thunderbolt-attached PCI devices
> based on a keyring of trusted vendor certificates.  The fully autonomous
> in-kernel authentication caters to such a use case.

I think you are conflating automatic authentication and built-in
functionality. There are counter examples of security features like
encrypted root filesystems built on top of module drivers.

What I am trying to avoid is CMA setting unnecessary expectations that
can not be duplicated by TSM like "all authentication capable PCI
devices will be authenticated prior to driver attach".

Now, might there be a reason for native TSM and CMA to diverge on this
policy / capability in the future, maybe? It certainly is not here
today.

> preclude such use cases just because Confidential Computing in the
> cloud happens to be the buzzword du jour.

As if CMA is somehow not part of the "buzzword du jour" of Confidential
Computing?
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index dbe16eea8818..9aad3350da16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20153,6 +20153,7 @@  L:	linux-cxl@vger.kernel.org
 L:	linux-pci@vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
+F:	drivers/pci/cma*
 F:	include/linux/spdm.h
 F:	lib/spdm/
 
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index d35001589d88..f656211d707a 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -121,6 +121,19 @@  config XEN_PCIDEV_FRONTEND
 config PCI_ATS
 	bool
 
+config PCI_CMA
+	bool "Component Measurement and Authentication (CMA-SPDM)"
+	select CRYPTO_ECDSA
+	select CRYPTO_RSA
+	select CRYPTO_SHA256
+	select CRYPTO_SHA512
+	select PCI_DOE
+	select SPDM
+	help
+	  Authenticate devices on enumeration per PCIe r6.2 sec 6.31.
+	  A PCI DOE mailbox is used as transport for DMTF SPDM based
+	  authentication, measurement and secure channel establishment.
+
 config PCI_DOE
 	bool
 
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 175302036890..6bcfeb698961 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -35,6 +35,8 @@  obj-$(CONFIG_VGA_ARB)		+= vgaarb.o
 obj-$(CONFIG_PCI_DOE)		+= doe.o
 obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
 
+obj-$(CONFIG_PCI_CMA)		+= cma.o
+
 # Endpoint library must be initialized before its users
 obj-$(CONFIG_PCI_ENDPOINT)	+= endpoint/
 
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
new file mode 100644
index 000000000000..275338b95640
--- /dev/null
+++ b/drivers/pci/cma.c
@@ -0,0 +1,100 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Component Measurement and Authentication (CMA-SPDM, PCIe r6.2 sec 6.31)
+ *
+ * Copyright (C) 2021 Huawei
+ *     Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#define dev_fmt(fmt) "CMA: " fmt
+
+#include <linux/pci.h>
+#include <linux/pci-doe.h>
+#include <linux/pm_runtime.h>
+#include <linux/spdm.h>
+
+#include "pci.h"
+
+/* Keyring that userspace can poke certs into */
+static struct key *pci_cma_keyring;
+
+#define PCI_DOE_FEATURE_CMA 1
+
+static ssize_t pci_doe_transport(void *priv, struct device *dev,
+				 const void *request, size_t request_sz,
+				 void *response, size_t response_sz)
+{
+	struct pci_doe_mb *doe = priv;
+	ssize_t rc;
+
+	/*
+	 * CMA-SPDM operation in non-D0 states is optional (PCIe r6.2
+	 * sec 6.31.3).  The spec does not define a way to determine
+	 * if it's supported, so resume to D0 unconditionally.
+	 */
+	rc = pm_runtime_resume_and_get(dev);
+	if (rc)
+		return rc;
+
+	rc = pci_doe(doe, PCI_VENDOR_ID_PCI_SIG, PCI_DOE_FEATURE_CMA,
+		     request, request_sz, response, response_sz);
+
+	pm_runtime_put(dev);
+
+	return rc;
+}
+
+void pci_cma_init(struct pci_dev *pdev)
+{
+	struct pci_doe_mb *doe;
+
+	if (IS_ERR(pci_cma_keyring))
+		return;
+
+	if (!pci_is_pcie(pdev))
+		return;
+
+	doe = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
+				   PCI_DOE_FEATURE_CMA);
+	if (!doe)
+		return;
+
+	pdev->spdm_state = spdm_create(&pdev->dev, pci_doe_transport, doe,
+				       PCI_DOE_MAX_PAYLOAD, pci_cma_keyring);
+	if (!pdev->spdm_state)
+		return;
+
+	/*
+	 * Keep spdm_state allocated even if initial authentication fails
+	 * to allow for provisioning of certificates and reauthentication.
+	 */
+	spdm_authenticate(pdev->spdm_state);
+}
+
+void pci_cma_destroy(struct pci_dev *pdev)
+{
+	if (!pdev->spdm_state)
+		return;
+
+	spdm_destroy(pdev->spdm_state);
+}
+
+__init static int pci_cma_keyring_init(void)
+{
+	pci_cma_keyring = keyring_alloc(".cma", KUIDT_INIT(0), KGIDT_INIT(0),
+					current_cred(),
+					(KEY_POS_ALL & ~KEY_POS_SETATTR) |
+					KEY_USR_VIEW | KEY_USR_READ |
+					KEY_USR_WRITE | KEY_USR_SEARCH,
+					KEY_ALLOC_NOT_IN_QUOTA |
+					KEY_ALLOC_SET_KEEP, NULL, NULL);
+	if (IS_ERR(pci_cma_keyring)) {
+		pr_err("PCI: Could not allocate .cma keyring\n");
+		return PTR_ERR(pci_cma_keyring);
+	}
+
+	return 0;
+}
+arch_initcall(pci_cma_keyring_init);
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index 652d63df9d22..34bb8f232799 100644
--- a/drivers/pci/doe.c
+++ b/drivers/pci/doe.c
@@ -31,9 +31,6 @@ 
 #define PCI_DOE_FLAG_CANCEL	0
 #define PCI_DOE_FLAG_DEAD	1
 
-/* Max data object length is 2^18 dwords */
-#define PCI_DOE_MAX_LENGTH	(1 << 18)
-
 /**
  * struct pci_doe_mb - State for a single DOE mailbox
  *
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index fd44565c4756..fc90845caf83 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -333,6 +333,14 @@  static inline void pci_doe_destroy(struct pci_dev *pdev) { }
 static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
 #endif
 
+#ifdef CONFIG_PCI_CMA
+void pci_cma_init(struct pci_dev *pdev);
+void pci_cma_destroy(struct pci_dev *pdev);
+#else
+static inline void pci_cma_init(struct pci_dev *pdev) { }
+static inline void pci_cma_destroy(struct pci_dev *pdev) { }
+#endif
+
 /**
  * pci_dev_set_io_state - Set the new error state if possible.
  *
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 8e696e547565..5297f9a08ca2 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2484,6 +2484,7 @@  static void pci_init_capabilities(struct pci_dev *dev)
 	pci_dpc_init(dev);		/* Downstream Port Containment */
 	pci_rcec_init(dev);		/* Root Complex Event Collector */
 	pci_doe_init(dev);		/* Data Object Exchange */
+	pci_cma_init(dev);		/* Component Measurement & Auth */
 
 	pcie_report_downtraining(dev);
 	pci_init_reset_methods(dev);
diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
index d749ea8250d6..f009ac578997 100644
--- a/drivers/pci/remove.c
+++ b/drivers/pci/remove.c
@@ -39,6 +39,7 @@  static void pci_destroy_dev(struct pci_dev *dev)
 	list_del(&dev->bus_list);
 	up_write(&pci_bus_sem);
 
+	pci_cma_destroy(dev);
 	pci_doe_destroy(dev);
 	pcie_aspm_exit_link_state(dev);
 	pci_bridge_d3_update(dev);
diff --git a/include/linux/pci-doe.h b/include/linux/pci-doe.h
index 1f14aed4354b..0d3d7656c456 100644
--- a/include/linux/pci-doe.h
+++ b/include/linux/pci-doe.h
@@ -15,6 +15,10 @@ 
 
 struct pci_doe_mb;
 
+/* Max data object length is 2^18 dwords (including 2 dwords for header) */
+#define PCI_DOE_MAX_LENGTH	(1 << 18)
+#define PCI_DOE_MAX_PAYLOAD	((PCI_DOE_MAX_LENGTH - 2) * sizeof(u32))
+
 struct pci_doe_mb *pci_find_doe_mailbox(struct pci_dev *pdev, u16 vendor,
 					u8 type);
 
diff --git a/include/linux/pci.h b/include/linux/pci.h
index fb004fd4e889..cb2a0be57196 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -39,6 +39,7 @@ 
 #include <linux/io.h>
 #include <linux/resource_ext.h>
 #include <linux/msi_api.h>
+#include <linux/spdm.h>
 #include <uapi/linux/pci.h>
 
 #include <linux/pci_ids.h>
@@ -517,6 +518,9 @@  struct pci_dev {
 #endif
 #ifdef CONFIG_PCI_DOE
 	struct xarray	doe_mbs;	/* Data Object Exchange mailboxes */
+#endif
+#ifdef CONFIG_PCI_CMA
+	struct spdm_state *spdm_state;	/* Security Protocol and Data Model */
 #endif
 	u16		acs_cap;	/* ACS Capability offset */
 	phys_addr_t	rom;		/* Physical address if not from BAR */