| From 64e6801f25e703952d18df0a72728e5cdd81a494 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 |
| --- 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 |
| --- /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 |
| @@ -1463,6 +1463,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 |
| @@ -741,6 +741,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.35.0.rc0.227.g00780c9af4-goog |
| |