diff mbox series

[11/12] PCI/CMA: Expose in sysfs whether devices are authenticated

Message ID 821682573e57e0384162f365652171e5ee1e6611.1695921657.git.lukas@wunner.de (mailing list archive)
State New
Headers show
Series PCI device authentication | expand

Commit Message

Lukas Wunner Sept. 28, 2023, 5:32 p.m. UTC
The PCI core has just been amended to authenticate CMA-capable devices
on enumeration and store the result in an "authenticated" bit in struct
pci_dev->spdm_state.

Expose the bit to user space through an eponymous sysfs attribute.

Allow user space to trigger reauthentication (e.g. after it has updated
the CMA keyring) by writing to the sysfs attribute.

Subject to further discussion, a future commit might add a user-defined
policy to forbid driver binding to devices which failed authentication,
similar to the "authorized" attribute for USB.

Alternatively, authentication success might be signaled to user space
through a uevent, whereupon it may bind a (blacklisted) driver.
A uevent signaling authentication failure might similarly cause user
space to unbind or outright remove the potentially malicious device.

Traffic from devices which failed authentication could also be filtered
through ACS I/O Request Blocking Enable (PCIe r6.1 sec 7.7.11.3) or
through Link Disable (PCIe r6.1 sec 7.5.3.7).  Unlike an IOMMU, that
will not only protect the host, but also prevent malicious peer-to-peer
traffic to other devices.

Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
 Documentation/ABI/testing/sysfs-bus-pci | 27 +++++++++
 drivers/pci/Kconfig                     |  3 +
 drivers/pci/Makefile                    |  1 +
 drivers/pci/cma-sysfs.c                 | 73 +++++++++++++++++++++++++
 drivers/pci/cma.c                       |  2 +
 drivers/pci/doe.c                       |  2 +
 drivers/pci/pci-sysfs.c                 |  3 +
 drivers/pci/pci.h                       |  1 +
 include/linux/pci.h                     |  2 +
 9 files changed, 114 insertions(+)
 create mode 100644 drivers/pci/cma-sysfs.c

Comments

Ilpo Järvinen Oct. 3, 2023, 9:04 a.m. UTC | #1
On Thu, 28 Sep 2023, Lukas Wunner wrote:

> The PCI core has just been amended to authenticate CMA-capable devices
> on enumeration and store the result in an "authenticated" bit in struct
> pci_dev->spdm_state.
> 
> Expose the bit to user space through an eponymous sysfs attribute.
> 
> Allow user space to trigger reauthentication (e.g. after it has updated
> the CMA keyring) by writing to the sysfs attribute.
> 
> Subject to further discussion, a future commit might add a user-defined
> policy to forbid driver binding to devices which failed authentication,
> similar to the "authorized" attribute for USB.
> 
> Alternatively, authentication success might be signaled to user space
> through a uevent, whereupon it may bind a (blacklisted) driver.
> A uevent signaling authentication failure might similarly cause user
> space to unbind or outright remove the potentially malicious device.
> 
> Traffic from devices which failed authentication could also be filtered
> through ACS I/O Request Blocking Enable (PCIe r6.1 sec 7.7.11.3) or
> through Link Disable (PCIe r6.1 sec 7.5.3.7).  Unlike an IOMMU, that
> will not only protect the host, but also prevent malicious peer-to-peer
> traffic to other devices.

IMO it would be good to mention the DOE stuff also in the changelog (it's 
currently only in the sysfs docs).
Jonathan Cameron Oct. 3, 2023, 3:28 p.m. UTC | #2
On Thu, 28 Sep 2023 19:32:41 +0200
Lukas Wunner <lukas@wunner.de> wrote:

> The PCI core has just been amended to authenticate CMA-capable devices
> on enumeration and store the result in an "authenticated" bit in struct
> pci_dev->spdm_state.
> 
> Expose the bit to user space through an eponymous sysfs attribute.
> 
> Allow user space to trigger reauthentication (e.g. after it has updated
> the CMA keyring) by writing to the sysfs attribute.

Ah.  That answers the question I asked in previous patch review ;)
Maybe add a comment to the cma_init code to say that's why it fails with
side effects (leaves the spdm_state around).

> 
> Subject to further discussion, a future commit might add a user-defined
> policy to forbid driver binding to devices which failed authentication,
> similar to the "authorized" attribute for USB.
> 
> Alternatively, authentication success might be signaled to user space
> through a uevent, whereupon it may bind a (blacklisted) driver.
> A uevent signaling authentication failure might similarly cause user
> space to unbind or outright remove the potentially malicious device.
> 
> Traffic from devices which failed authentication could also be filtered
> through ACS I/O Request Blocking Enable (PCIe r6.1 sec 7.7.11.3) or
> through Link Disable (PCIe r6.1 sec 7.5.3.7).  Unlike an IOMMU, that
> will not only protect the host, but also prevent malicious peer-to-peer
> traffic to other devices.
> 
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
Seems good to me, though I agree with Ilpo that it would be good to mention
the DOE init fail in the patch description as that's a bit subtle.

One trivial comment inline.

Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
 
> ---
>  Documentation/ABI/testing/sysfs-bus-pci | 27 +++++++++
>  drivers/pci/Kconfig                     |  3 +
>  drivers/pci/Makefile                    |  1 +
>  drivers/pci/cma-sysfs.c                 | 73 +++++++++++++++++++++++++
>  drivers/pci/cma.c                       |  2 +
>  drivers/pci/doe.c                       |  2 +
>  drivers/pci/pci-sysfs.c                 |  3 +
>  drivers/pci/pci.h                       |  1 +
>  include/linux/pci.h                     |  2 +
>  9 files changed, 114 insertions(+)
>  create mode 100644 drivers/pci/cma-sysfs.c
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
> index ecf47559f495..2ea9b8deffcc 100644
> --- a/Documentation/ABI/testing/sysfs-bus-pci
> +++ b/Documentation/ABI/testing/sysfs-bus-pci
> @@ -500,3 +500,30 @@ Description:
>  		console drivers from the device.  Raw users of pci-sysfs
>  		resourceN attributes must be terminated prior to resizing.
>  		Success of the resizing operation is not guaranteed.
> +
> +What:		/sys/bus/pci/devices/.../authenticated
> +Date:		September 2023
> +Contact:	Lukas Wunner <lukas@wunner.de>
> +Description:
> +		This file contains 1 if the device authenticated successfully
> +		with CMA-SPDM (PCIe r6.1 sec 6.31).  It contains 0 if the
> +		device failed authentication (and may thus be malicious).
> +
> +		Writing anything to this file causes reauthentication.
> +		That may be opportune after updating the .cma keyring.
> +
> +		The file is not visible if authentication is unsupported
> +		by the device.
> +
> +		If the kernel could not determine whether authentication is
> +		supported because memory was low or DOE communication with
> +		the device was not working, the file is visible but accessing
> +		it fails with error code ENOTTY.
> +
> +		This prevents downgrade attacks where an attacker consumes
> +		memory or disturbs DOE communication in order to create the
> +		appearance that a device does not support authentication.
> +
> +		The reason why authentication support could not be determined
> +		is apparent from "dmesg".  To probe for authentication support
> +		again, exercise the "remove" and "rescan" attributes.
> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> index c9aa5253ac1f..51df3be3438e 100644
> --- a/drivers/pci/Kconfig
> +++ b/drivers/pci/Kconfig
> @@ -129,6 +129,9 @@ config PCI_CMA
>  	  A PCI DOE mailbox is used as transport for DMTF SPDM based
>  	  attestation, measurement and secure channel establishment.
>  
> +config PCI_CMA_SYSFS
> +	def_bool PCI_CMA && SYSFS
> +
>  config PCI_DOE
>  	bool
>  
> diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
> index a18812b8832b..612ae724cd2d 100644
> --- a/drivers/pci/Makefile
> +++ b/drivers/pci/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_PCI_DOE)		+= doe.o
>  obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
>  
>  obj-$(CONFIG_PCI_CMA)		+= cma.o cma-x509.o cma.asn1.o
> +obj-$(CONFIG_PCI_CMA_SYSFS)	+= cma-sysfs.o
>  $(obj)/cma-x509.o:		$(obj)/cma.asn1.h
>  $(obj)/cma.asn1.o:		$(obj)/cma.asn1.c $(obj)/cma.asn1.h
>  
> diff --git a/drivers/pci/cma-sysfs.c b/drivers/pci/cma-sysfs.c
> new file mode 100644
> index 000000000000..b2d45f96601a
> --- /dev/null
> +++ b/drivers/pci/cma-sysfs.c
> @@ -0,0 +1,73 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Component Measurement and Authentication (CMA-SPDM, PCIe r6.1 sec 6.31)
> + *
> + * Copyright (C) 2023 Intel Corporation
> + */
> +
> +#include <linux/pci.h>
> +#include <linux/spdm.h>
> +#include <linux/sysfs.h>
> +
> +#include "pci.h"
> +
> +static ssize_t authenticated_store(struct device *dev,
> +				   struct device_attribute *attr,
> +				   const char *buf, size_t count)
> +{
> +	struct pci_dev *pdev = to_pci_dev(dev);
> +	ssize_t rc;
> +
> +	if (!pdev->cma_capable &&
> +	    (pdev->cma_init_failed || pdev->doe_init_failed))
> +		return -ENOTTY;
> +
> +	rc = pci_cma_reauthenticate(pdev);
> +	if (rc)
> +		return rc;

> +
> +	return count;
> +}
> +
> +static ssize_t authenticated_show(struct device *dev,
> +				  struct device_attribute *attr, char *buf)
> +{
> +	struct pci_dev *pdev = to_pci_dev(dev);
> +
> +	if (!pdev->cma_capable &&
> +	    (pdev->cma_init_failed || pdev->doe_init_failed))
> +		return -ENOTTY;
> +
> +	return sysfs_emit(buf, "%u\n", spdm_authenticated(pdev->spdm_state));
> +}
> +static DEVICE_ATTR_RW(authenticated);
> +
> +static struct attribute *pci_cma_attrs[] = {
> +	&dev_attr_authenticated.attr,
> +	NULL
> +};
> +
> +static umode_t pci_cma_attrs_are_visible(struct kobject *kobj,
> +					 struct attribute *a, int n)
> +{
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct pci_dev *pdev = to_pci_dev(dev);
> +
> +	/*
> +	 * If CMA or DOE initialization failed, CMA attributes must be visible
> +	 * and return an error on access.  This prevents downgrade attacks
> +	 * where an attacker disturbs memory allocation or DOE communication
> +	 * in order to create the appearance that CMA is unsupported.
> +	 * The attacker may achieve that by simply hogging memory.
> +	 */
> +	if (!pdev->cma_capable &&
> +	    !pdev->cma_init_failed && !pdev->doe_init_failed)
> +		return 0;
> +
> +	return a->mode;
> +}
> +
> +const struct attribute_group pci_cma_attr_group = {
> +	.attrs  = pci_cma_attrs,

I'd go with a single space here as the double doesn't make
it any more readable.


> +	.is_visible = pci_cma_attrs_are_visible,
> +};
Bjorn Helgaas Oct. 5, 2023, 8:20 p.m. UTC | #3
On Thu, Sep 28, 2023 at 07:32:41PM +0200, Lukas Wunner wrote:
> The PCI core has just been amended to authenticate CMA-capable devices
> on enumeration and store the result in an "authenticated" bit in struct
> pci_dev->spdm_state.

>  drivers/pci/cma-sysfs.c                 | 73 +++++++++++++++++++++++++

Not really sure it's worth splitting this into cma.c, cma-sysfs.c,
cma-x509.c.  They're all tiny and ping-ponging between files is a bit
of a hassle:

 151 drivers/pci/cma.c
  73 drivers/pci/cma-sysfs.c
 119 drivers/pci/cma-x509.c

Bjorn
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index ecf47559f495..2ea9b8deffcc 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -500,3 +500,30 @@  Description:
 		console drivers from the device.  Raw users of pci-sysfs
 		resourceN attributes must be terminated prior to resizing.
 		Success of the resizing operation is not guaranteed.
+
+What:		/sys/bus/pci/devices/.../authenticated
+Date:		September 2023
+Contact:	Lukas Wunner <lukas@wunner.de>
+Description:
+		This file contains 1 if the device authenticated successfully
+		with CMA-SPDM (PCIe r6.1 sec 6.31).  It contains 0 if the
+		device failed authentication (and may thus be malicious).
+
+		Writing anything to this file causes reauthentication.
+		That may be opportune after updating the .cma keyring.
+
+		The file is not visible if authentication is unsupported
+		by the device.
+
+		If the kernel could not determine whether authentication is
+		supported because memory was low or DOE communication with
+		the device was not working, the file is visible but accessing
+		it fails with error code ENOTTY.
+
+		This prevents downgrade attacks where an attacker consumes
+		memory or disturbs DOE communication in order to create the
+		appearance that a device does not support authentication.
+
+		The reason why authentication support could not be determined
+		is apparent from "dmesg".  To probe for authentication support
+		again, exercise the "remove" and "rescan" attributes.
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index c9aa5253ac1f..51df3be3438e 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -129,6 +129,9 @@  config PCI_CMA
 	  A PCI DOE mailbox is used as transport for DMTF SPDM based
 	  attestation, measurement and secure channel establishment.
 
+config PCI_CMA_SYSFS
+	def_bool PCI_CMA && SYSFS
+
 config PCI_DOE
 	bool
 
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index a18812b8832b..612ae724cd2d 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -35,6 +35,7 @@  obj-$(CONFIG_PCI_DOE)		+= doe.o
 obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
 
 obj-$(CONFIG_PCI_CMA)		+= cma.o cma-x509.o cma.asn1.o
+obj-$(CONFIG_PCI_CMA_SYSFS)	+= cma-sysfs.o
 $(obj)/cma-x509.o:		$(obj)/cma.asn1.h
 $(obj)/cma.asn1.o:		$(obj)/cma.asn1.c $(obj)/cma.asn1.h
 
diff --git a/drivers/pci/cma-sysfs.c b/drivers/pci/cma-sysfs.c
new file mode 100644
index 000000000000..b2d45f96601a
--- /dev/null
+++ b/drivers/pci/cma-sysfs.c
@@ -0,0 +1,73 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Component Measurement and Authentication (CMA-SPDM, PCIe r6.1 sec 6.31)
+ *
+ * Copyright (C) 2023 Intel Corporation
+ */
+
+#include <linux/pci.h>
+#include <linux/spdm.h>
+#include <linux/sysfs.h>
+
+#include "pci.h"
+
+static ssize_t authenticated_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	ssize_t rc;
+
+	if (!pdev->cma_capable &&
+	    (pdev->cma_init_failed || pdev->doe_init_failed))
+		return -ENOTTY;
+
+	rc = pci_cma_reauthenticate(pdev);
+	if (rc)
+		return rc;
+
+	return count;
+}
+
+static ssize_t authenticated_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+
+	if (!pdev->cma_capable &&
+	    (pdev->cma_init_failed || pdev->doe_init_failed))
+		return -ENOTTY;
+
+	return sysfs_emit(buf, "%u\n", spdm_authenticated(pdev->spdm_state));
+}
+static DEVICE_ATTR_RW(authenticated);
+
+static struct attribute *pci_cma_attrs[] = {
+	&dev_attr_authenticated.attr,
+	NULL
+};
+
+static umode_t pci_cma_attrs_are_visible(struct kobject *kobj,
+					 struct attribute *a, int n)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct pci_dev *pdev = to_pci_dev(dev);
+
+	/*
+	 * If CMA or DOE initialization failed, CMA attributes must be visible
+	 * and return an error on access.  This prevents downgrade attacks
+	 * where an attacker disturbs memory allocation or DOE communication
+	 * in order to create the appearance that CMA is unsupported.
+	 * The attacker may achieve that by simply hogging memory.
+	 */
+	if (!pdev->cma_capable &&
+	    !pdev->cma_init_failed && !pdev->doe_init_failed)
+		return 0;
+
+	return a->mode;
+}
+
+const struct attribute_group pci_cma_attr_group = {
+	.attrs  = pci_cma_attrs,
+	.is_visible = pci_cma_attrs_are_visible,
+};
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
index 89d23fdc37ec..c539ad85a28f 100644
--- a/drivers/pci/cma.c
+++ b/drivers/pci/cma.c
@@ -52,6 +52,7 @@  void pci_cma_init(struct pci_dev *pdev)
 	int rc;
 
 	if (!pci_cma_keyring) {
+		pdev->cma_init_failed = true;
 		return;
 	}
 
@@ -67,6 +68,7 @@  void pci_cma_init(struct pci_dev *pdev)
 				       PCI_DOE_MAX_PAYLOAD, pci_cma_keyring,
 				       pci_cma_validate);
 	if (!pdev->spdm_state) {
+		pdev->cma_init_failed = true;
 		return;
 	}
 
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index 79f0336eb0c3..fabbda68edac 100644
--- a/drivers/pci/doe.c
+++ b/drivers/pci/doe.c
@@ -686,6 +686,7 @@  void pci_doe_init(struct pci_dev *pdev)
 						      PCI_EXT_CAP_ID_DOE))) {
 		doe_mb = pci_doe_create_mb(pdev, offset);
 		if (IS_ERR(doe_mb)) {
+			pdev->doe_init_failed = true;
 			pci_err(pdev, "[%x] failed to create mailbox: %ld\n",
 				offset, PTR_ERR(doe_mb));
 			continue;
@@ -693,6 +694,7 @@  void pci_doe_init(struct pci_dev *pdev)
 
 		rc = xa_insert(&pdev->doe_mbs, offset, doe_mb, GFP_KERNEL);
 		if (rc) {
+			pdev->doe_init_failed = true;
 			pci_err(pdev, "[%x] failed to insert mailbox: %d\n",
 				offset, rc);
 			pci_doe_destroy_mb(doe_mb);
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index d9eede2dbc0e..7024e08e1b9a 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -1655,6 +1655,9 @@  static const struct attribute_group *pci_dev_attr_groups[] = {
 #endif
 #ifdef CONFIG_PCIEASPM
 	&aspm_ctrl_attr_group,
+#endif
+#ifdef CONFIG_PCI_CMA_SYSFS
+	&pci_cma_attr_group,
 #endif
 	NULL,
 };
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 71092ccf4fbd..d80cc06be0cc 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -328,6 +328,7 @@  void pci_cma_destroy(struct pci_dev *pdev);
 int pci_cma_reauthenticate(struct pci_dev *pdev);
 struct x509_certificate;
 int pci_cma_validate(struct device *dev, struct x509_certificate *leaf_cert);
+extern const struct attribute_group pci_cma_attr_group;
 #else
 static inline void pci_cma_init(struct pci_dev *pdev) { }
 static inline void pci_cma_destroy(struct pci_dev *pdev) { }
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 2bc11d8b567e..2c5fde81bb85 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -516,10 +516,12 @@  struct pci_dev {
 #endif
 #ifdef CONFIG_PCI_DOE
 	struct xarray	doe_mbs;	/* Data Object Exchange mailboxes */
+	unsigned int	doe_init_failed:1;
 #endif
 #ifdef CONFIG_PCI_CMA
 	struct spdm_state *spdm_state;	/* Security Protocol and Data Model */
 	unsigned int	cma_capable:1;	/* Authentication supported */
+	unsigned int	cma_init_failed:1;
 #endif
 	u16		acs_cap;	/* ACS Capability offset */
 	phys_addr_t	rom;		/* Physical address if not from BAR */