blob: 39e2059d2657a04924435cc28084875690a610be [file] [log] [blame]
From fa9f3efbfe36c34ab3eaa04ca090e2c9aac6c626 Mon Sep 17 00:00:00 2001
From: Rajat Jain <rajatja@google.com>
Date: Fri, 10 Jul 2020 13:53:50 -0700
Subject: [PATCH] CHROMIUM: PCI: Allowlist for drivers allowed to bind to
untrusted devices
Today, firmware can mark certain rootports as "external-facing" and the
devices downstream those are treated as "untrusted". In chromeos, this
mechanism is used to mark thunderbolt port and SD express ports as
external-facing, an thus any external PCI devices further downstream as
untrusted.
This patch restricts the PCI drivers that are allowed to bind to those
external PCI (untrusted) devices. The allowlist of drivers is to be fed
by userspace via a sysfs attribute:
/sys/bus/pci/drivers/drivers_allowlist
When read, shows the current allowlist of drivers (one per line).
When written, allows to add a driver to the allowlist.
Note that adding drivers to allowlist only affects future binding of
that driver (to external devices that are discovered in future).
PS: The approach of having an allowlist for PCI drivers for external PCI
devices was discussed at length upstream, but unfortunately nack'ed by
upstream. The discussion was scattered across multiple (long) threads.
https://lore.kernel.org/linux-pci/20200607113632.GA49147@kroah.com/t/#u
https://lkml.org/lkml/2020/6/30/23
https://lore.kernel.org/linux-pci/20200626002710.110200-2-rajatja@google.com/
https://lore.kernel.org/linux-pci/20200616011742.138975-4-rajatja@google.com/T/#u
BUG=b:153180503,b:151065827,b:140645321
TEST=Test using the Caldigit docking station that the external PCI
devices are attached to driver only when the driver entry is added
to the allowlist.
Signed-off-by: Rajat Jain <rajatja@google.com>
Change-Id: I0647e671c1c098dbe59b19883a1d78c37069161d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/2298049
Reviewed-by: Alex Levin <levinale@google.com>
Reviewed-by: Prashant Malani <pmalani@chromium.org>
[rebase510(groeck): Context conflicts (drivers/pci/pci.h);
Squashed:
FIXUP: CHROMIUM: PCI: Allowlist for drivers allowed to bind to untrusted devices
]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
---
drivers/pci/Makefile | 4 +-
drivers/pci/drvr-allowlist.c | 137 +++++++++++++++++++++++++++++++++++
drivers/pci/pci-driver.c | 3 +
drivers/pci/pci.h | 1 +
4 files changed, 143 insertions(+), 2 deletions(-)
create mode 100644 drivers/pci/drvr-allowlist.c
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 2680e4c92f0abb6959b397a89c04c37363f8891d..45d97157c20cb3d1bea9c2e005353d8c2c2b7af2 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -5,8 +5,8 @@
obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \
remove.o pci.o pci-driver.o search.o \
pci-sysfs.o rom.o setup-res.o irq.o vpd.o \
- setup-bus.o vc.o mmap.o setup-irq.o
-
+ setup-bus.o vc.o mmap.o setup-irq.o \
+ drvr-allowlist.o
obj-$(CONFIG_PCI) += msi/
obj-$(CONFIG_PCI) += pcie/
diff --git a/drivers/pci/drvr-allowlist.c b/drivers/pci/drvr-allowlist.c
new file mode 100644
index 0000000000000000000000000000000000000000..72446f2b592f116abdb25f5f5317261da3224c1a
--- /dev/null
+++ b/drivers/pci/drvr-allowlist.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Allowlist of PCI drivers that are allowed to bind to external devices
+ */
+
+#include <linux/ctype.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include "pci.h"
+
+/*
+ * Parameter to disable allowlist (thus allow all drivers to connect
+ * to any external PCI devices).
+ */
+static bool trust_external_pci_devices;
+core_param(trust_external_pci_devices, trust_external_pci_devices, bool, 0444);
+
+/* Driver allowlist */
+struct allowlist_entry {
+ const char *drvr_name;
+ struct list_head node;
+};
+
+static LIST_HEAD(allowlist);
+static DECLARE_RWSEM(allowlist_sem);
+
+#define TRUNCATED "...<truncated>\n"
+
+static ssize_t drivers_allowlist_show(struct bus_type *bus, char *buf)
+{
+ size_t count = 0;
+ struct allowlist_entry *entry;
+
+ down_read(&allowlist_sem);
+ list_for_each_entry(entry, &allowlist, node) {
+ if (count + strlen(entry->drvr_name) + sizeof(TRUNCATED) <
+ PAGE_SIZE) {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%s\n", entry->drvr_name);
+ } else {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ TRUNCATED);
+ break;
+ }
+ }
+ up_read(&allowlist_sem);
+ return count;
+}
+
+static ssize_t drivers_allowlist_store(struct bus_type *bus, const char *buf,
+ size_t count)
+{
+ struct allowlist_entry *entry;
+ ssize_t ret = count;
+ unsigned int i;
+ char *drv;
+
+ if (!count)
+ return -EINVAL;
+
+ drv = kstrndup(buf, count, GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ /* Remove any trailing white spaces */
+ strim(drv);
+ if (!*drv) {
+ ret = -EINVAL;
+ goto out_kfree;
+ }
+
+ /* Driver names cannot have special characters */
+ for (i = 0; i < strlen(drv); i++)
+ if (!isalnum(drv[i]) && drv[i] != '_') {
+ ret = -EINVAL;
+ goto out_kfree;
+ }
+
+ down_write(&allowlist_sem);
+
+ /* Lookup in the allowlist */
+ list_for_each_entry(entry, &allowlist, node)
+ if (!strcmp(drv, entry->drvr_name)) {
+ ret = -EEXIST;
+ goto out_release_sem;
+ }
+
+ /* Add a driver to the allowlist */
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ ret = -ENOMEM;
+ goto out_release_sem;
+ }
+ entry->drvr_name = drv;
+ list_add_tail(&entry->node, &allowlist);
+ up_write(&allowlist_sem);
+ return ret;
+
+out_release_sem:
+ up_write(&allowlist_sem);
+out_kfree:
+ kfree(drv);
+ return ret;
+}
+static BUS_ATTR_RW(drivers_allowlist);
+
+static int __init pci_drivers_allowlist_init(void)
+{
+ if (trust_external_pci_devices)
+ return 0;
+
+ return bus_create_file(&pci_bus_type, &bus_attr_drivers_allowlist);
+}
+late_initcall(pci_drivers_allowlist_init);
+
+static bool pci_driver_is_allowed(const char *name)
+{
+ struct allowlist_entry *entry;
+
+ down_read(&allowlist_sem);
+ list_for_each_entry(entry, &allowlist, node) {
+ if (!strcmp(name, entry->drvr_name)) {
+ up_read(&allowlist_sem);
+ return true;
+ }
+ }
+ up_read(&allowlist_sem);
+ return false;
+}
+
+bool pci_drv_allowed_for_untrusted_devs(struct device_driver *drv)
+{
+ if (trust_external_pci_devices || pci_driver_is_allowed(drv->name))
+ return true;
+
+ return false;
+}
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index 107d77f3c846708f0eac6c857b757502e2cc6de0..a75cb3adb855ed948f0ce5644e642fdad9b57609 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -1500,6 +1500,9 @@ static int pci_bus_match(struct device *dev, struct device_driver *drv)
struct pci_driver *pci_drv;
const struct pci_device_id *found_id;
+ if (pci_dev->untrusted && !pci_drv_allowed_for_untrusted_devs(drv))
+ return 0;
+
if (!pci_dev->match_driver)
return 0;
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index b1ebb7ab8805170d224b15b292be52b092090457..e70a6acf7122006efd5a98b82d8ef1bcc6df0612 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -756,6 +756,7 @@ extern const struct attribute_group aspm_ctrl_attr_group;
#endif
extern const struct attribute_group pci_dev_reset_method_attr_group;
+bool pci_drv_allowed_for_untrusted_devs(struct device_driver *drvr);
#ifdef CONFIG_X86_INTEL_MID
bool pci_use_mid_pm(void);
--
2.38.1.584.g0f3c55d4c2-goog