blob: 8806d27f90f800c70eea1831ae4dcaa2a19f4cc1 [file] [log] [blame]
From 1dae40b1dc60faf4731310c2a6797ba346d5e456 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 | 3 +-
drivers/pci/drvr-allowlist.c | 137 +++++++++++++++++++++++++++++++++++
drivers/pci/pci-driver.c | 3 +
drivers/pci/pci.h | 1 +
4 files changed, 143 insertions(+), 1 deletion(-)
create mode 100644 drivers/pci/drvr-allowlist.c
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -5,7 +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 msi.o
+ setup-bus.o vc.o mmap.o setup-irq.o msi.o \
+ drvr-allowlist.o
obj-$(CONFIG_PCI) += pcie/
diff --git a/drivers/pci/drvr-allowlist.c b/drivers/pci/drvr-allowlist.c
new file mode 100644
--- /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
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -1465,6 +1465,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
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -743,5 +743,6 @@ 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);
#endif /* DRIVERS_PCI_H */
--
2.33.0.464.g1972c5931b-goog