blob: 61245bfd212f544996b055c6ed53c516ac31cce7 [file] [log] [blame]
From 0db4447e8fadbaf61ac9e0c2265b869475e7ff8e 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
[rebase66(tzungbi):
Revised commit message.
Squashed:
CHROMIUM: pci/allowlist: Log & send udev events for allowing/blocking drivers
(https://crrev.com/c/2465192)
CHROMIUM: pci/allowlist: sysfs attribute to lockdown external devices completely
(https://crrev.com/c/2494960)
CHROMIUM: pci/allowlist: Release lockdown_sem before attaching devices
(https://crrev.com/c/2551845)
CHROMIUM: pci-drvr-allowlist: Use the upstreamed attribute
(https://crrev.com/c/3163527)
CHROMIUM: pci/allowlist: Fixup the log message
(https://crrev.com/c/3191481)
FIXUP: CHROMIUM: PCI: Allowlist for drivers allowed to bind to untrusted devices
]
Signed-off-by: Tzung-Bi Shih <tzungbi@chromium.org>
---
drivers/pci/Makefile | 4 +-
drivers/pci/drvr-allowlist.c | 238 +++++++++++++++++++++++++++++++++++
drivers/pci/pci-driver.c | 2 +-
drivers/pci/pci.h | 2 +
4 files changed, 243 insertions(+), 3 deletions(-)
create mode 100644 drivers/pci/drvr-allowlist.c
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 1753020368909e1f8e770ef171ff7d7c1bf40437..96d45e89e70563dd0810a7ddb78349ad52e0d729 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 \
rom.o setup-res.o irq.o vpd.o \
- setup-bus.o vc.o mmap.o devres.o
-
+ setup-bus.o vc.o mmap.o devres.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..c3754b87c838a658923e6f19bb78f90997244e60
--- /dev/null
+++ b/drivers/pci/drvr-allowlist.c
@@ -0,0 +1,238 @@
+// 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 essentially disable allowlist code (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"
+
+/*
+ * Locks down the binding of drivers to untrusted devices
+ * (No PCI drivers to bind to any new untrusted PCI device)
+ */
+static bool drivers_allowlist_lockdown = true;
+static DECLARE_RWSEM(lockdown_sem);
+
+static ssize_t drivers_allowlist_show(const 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(const 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 ssize_t drivers_allowlist_lockdown_show(const struct bus_type *bus, char *buf)
+{
+ int ret;
+
+ down_read(&lockdown_sem);
+ ret = sprintf(buf, "%u\n", drivers_allowlist_lockdown);
+ up_read(&lockdown_sem);
+
+ return ret;
+}
+
+static ssize_t
+drivers_allowlist_lockdown_store(const struct bus_type *bus, const char *buf,
+ size_t count)
+{
+ bool lockdown, state_changed = false;
+ struct pci_dev *dev = NULL;
+
+ if (strtobool(buf, &lockdown))
+ return -EINVAL;
+
+ down_write(&lockdown_sem);
+ if (drivers_allowlist_lockdown != lockdown) {
+ drivers_allowlist_lockdown = lockdown;
+ state_changed = true;
+ }
+ up_write(&lockdown_sem);
+
+ if (state_changed && !lockdown) {
+ /* Attach any devices blocked earlier, subject to allowlist */
+ for_each_pci_dev(dev) {
+ if (dev_is_removable(&dev->dev) &&
+ !device_attach(&dev->dev))
+ pci_dbg(dev, "No driver\n");
+ }
+ }
+ return count;
+}
+static BUS_ATTR_RW(drivers_allowlist_lockdown);
+
+static int __init pci_drivers_allowlist_init(void)
+{
+ int ret;
+
+ if (trust_external_pci_devices)
+ return 0;
+
+ ret = bus_create_file(&pci_bus_type, &bus_attr_drivers_allowlist);
+ if (ret) {
+ pr_err("%s: failed to create allowlist in sysfs\n", __func__);
+ return ret;
+ }
+
+ ret = bus_create_file(&pci_bus_type,
+ &bus_attr_drivers_allowlist_lockdown);
+ if (ret) {
+ pr_err("%s: failed to create allowlist_lockdown\n", __func__);
+ bus_remove_file(&pci_bus_type, &bus_attr_drivers_allowlist);
+ }
+ return ret;
+}
+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_allowed_to_attach(struct pci_driver *drv, struct pci_dev *dev)
+{
+ char event[16], drvr[32], *reason;
+ char *udev_env[] = { event, drvr, NULL };
+
+ snprintf(drvr, sizeof(drvr), "DRVR=%s", drv->name);
+
+ /* Bypass Allowlist code, if platform wants so */
+ if (trust_external_pci_devices) {
+ reason = "trust_external_pci_devices";
+ goto allowed;
+ }
+
+ /* Allow internal devices */
+ if (!dev_is_removable(&dev->dev)) {
+ reason = "internal device";
+ goto allowed;
+ }
+
+ /* Don't allow any driver attaches, if locked down */
+ down_read(&lockdown_sem);
+ if (drivers_allowlist_lockdown) {
+ up_read(&lockdown_sem);
+ reason = "drivers_allowlist_lockdown enforced";
+ goto not_allowed;
+ }
+ up_read(&lockdown_sem);
+
+ /* Allow if driver is in allowlist */
+ if (pci_driver_is_allowed(drv->name)) {
+ reason = "drvr in allowlist";
+ goto allowed;
+ }
+ reason = "drvr not in allowlist";
+
+not_allowed:
+ pci_err(dev, "attach not allowed to drvr %s [%s]\n", drv->name, reason);
+ snprintf(event, sizeof(event), "EVENT=BLOCKED");
+ kobject_uevent_env(&dev->dev.kobj, KOBJ_CHANGE, udev_env);
+ return false;
+
+allowed:
+ pci_info(dev, "attach allowed to drvr %s [%s]\n", drv->name, reason);
+ snprintf(event, sizeof(event), "EVENT=ALLOWED");
+ kobject_uevent_env(&dev->dev.kobj, KOBJ_CHANGE, udev_env);
+ return true;
+}
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index af2996d0d17ffc0fa2ae730eb09d58243cf79a5b..7a047a871e74119ef377b909e9bd9332ae948828 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -1514,7 +1514,7 @@ static int pci_bus_match(struct device *dev, struct device_driver *drv)
pci_drv = to_pci_driver(drv);
found_id = pci_match_device(pci_drv, pci_dev);
- if (found_id)
+ if (found_id && pci_allowed_to_attach(pci_drv, pci_dev))
return 1;
return 0;
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 17fed18468474b8ed75694216c742ef55c9079e1..a2a2e71ea458950cc42be80f2ab5c6fd62462a96 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -792,6 +792,8 @@ 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);
+bool pci_allowed_to_attach(struct pci_driver *drv, struct pci_dev *dev);
#ifdef CONFIG_X86_INTEL_MID
bool pci_use_mid_pm(void);
--
2.44.0.478.gd926399ef9-goog