Skip to content

Commit

Permalink
thunderbolt: Add support for preboot ACL
Browse files Browse the repository at this point in the history
Preboot ACL is a mechanism that allows connecting Thunderbolt devices
boot time in more secure way than the legacy Thunderbolt boot support.
As with the legacy boot option, this also needs to be enabled from the
BIOS before booting is allowed. Difference to the legacy mode is that
the userspace software explicitly adds device UUIDs by sending a special
message to the ICM firmware. Only the devices listed in the boot ACL are
connected automatically during the boot. This works in both "user" and
"secure" security levels.

We implement this in Linux by exposing a new sysfs attribute (boot_acl)
below each Thunderbolt domain. The userspace software can then update
the full list as needed.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
  • Loading branch information
westeri committed Mar 9, 2018
1 parent 14862ee commit 9aaa3b8
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 10 deletions.
23 changes: 23 additions & 0 deletions Documentation/ABI/testing/sysfs-bus-thunderbolt
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
What: /sys/bus/thunderbolt/devices/.../domainX/boot_acl
Date: Jun 2018
KernelVersion: 4.17
Contact: thunderbolt-software@lists.01.org
Description: Holds a comma separated list of device unique_ids that
are allowed to be connected automatically during system
startup (e.g boot devices). The list always contains
maximum supported number of unique_ids where unused
entries are empty. This allows the userspace software
to determine how many entries the controller supports.
If there are multiple controllers, each controller has
its own ACL list and size may be different between the
controllers.

System BIOS may have an option "Preboot ACL" or similar
that needs to be selected before this list is taken into
consideration.

Software always updates a full list in each write.

If a device is authorized automatically during boot its
boot attribute is set to 1.

What: /sys/bus/thunderbolt/devices/.../domainX/security
Date: Sep 2017
KernelVersion: 4.13
Expand Down
123 changes: 123 additions & 0 deletions drivers/thunderbolt/domain.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,110 @@ static const char * const tb_security_names[] = {
[TB_SECURITY_DPONLY] = "dponly",
};

static ssize_t boot_acl_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct tb *tb = container_of(dev, struct tb, dev);
uuid_t *uuids;
ssize_t ret;
int i;

uuids = kcalloc(tb->nboot_acl, sizeof(uuid_t), GFP_KERNEL);
if (!uuids)
return -ENOMEM;

if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out;
}
ret = tb->cm_ops->get_boot_acl(tb, uuids, tb->nboot_acl);
if (ret) {
mutex_unlock(&tb->lock);
goto out;
}
mutex_unlock(&tb->lock);

for (ret = 0, i = 0; i < tb->nboot_acl; i++) {
if (!uuid_is_null(&uuids[i]))
ret += snprintf(buf + ret, PAGE_SIZE - ret, "%pUb",
&uuids[i]);

ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s",
i < tb->nboot_acl - 1 ? "," : "\n");
}

out:
kfree(uuids);
return ret;
}

static ssize_t boot_acl_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct tb *tb = container_of(dev, struct tb, dev);
char *str, *s, *uuid_str;
ssize_t ret = 0;
uuid_t *acl;
int i = 0;

/*
* Make sure the value is not bigger than tb->nboot_acl * UUID
* length + commas and optional "\n". Also the smallest allowable
* string is tb->nboot_acl * ",".
*/
if (count > (UUID_STRING_LEN + 1) * tb->nboot_acl + 1)
return -EINVAL;
if (count < tb->nboot_acl - 1)
return -EINVAL;

str = kstrdup(buf, GFP_KERNEL);
if (!str)
return -ENOMEM;

acl = kcalloc(tb->nboot_acl, sizeof(uuid_t), GFP_KERNEL);
if (!acl) {
ret = -ENOMEM;
goto err_free_str;
}

uuid_str = strim(str);
while ((s = strsep(&uuid_str, ",")) != NULL && i < tb->nboot_acl) {
size_t len = strlen(s);

if (len) {
if (len != UUID_STRING_LEN) {
ret = -EINVAL;
goto err_free_acl;
}
ret = uuid_parse(s, &acl[i]);
if (ret)
goto err_free_acl;
}

i++;
}

if (s || i < tb->nboot_acl) {
ret = -EINVAL;
goto err_free_acl;
}

if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto err_free_acl;
}
ret = tb->cm_ops->set_boot_acl(tb, acl, tb->nboot_acl);
mutex_unlock(&tb->lock);

err_free_acl:
kfree(acl);
err_free_str:
kfree(str);

return ret ?: count;
}
static DEVICE_ATTR_RW(boot_acl);

static ssize_t security_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
Expand All @@ -129,11 +233,30 @@ static ssize_t security_show(struct device *dev, struct device_attribute *attr,
static DEVICE_ATTR_RO(security);

static struct attribute *domain_attrs[] = {
&dev_attr_boot_acl.attr,
&dev_attr_security.attr,
NULL,
};

static umode_t domain_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct tb *tb = container_of(dev, struct tb, dev);

if (attr == &dev_attr_boot_acl.attr) {
if (tb->nboot_acl &&
tb->cm_ops->get_boot_acl &&
tb->cm_ops->set_boot_acl)
return attr->mode;
return 0;
}

return attr->mode;
}

static struct attribute_group domain_attr_group = {
.is_visible = domain_attr_is_visible,
.attrs = domain_attrs,
};

Expand Down
Loading

0 comments on commit 9aaa3b8

Please sign in to comment.