blob: adff0cea44cca8a285387034bc4f30f3a40b1205 [file] [log] [blame]
// 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(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 ssize_t drivers_allowlist_lockdown_show(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(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 trusted devices */
if (!dev_is_removable(&dev->dev)) {
reason = "trusted 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;
}