Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f67cf491 authored by Mika Westerberg's avatar Mika Westerberg Committed by Greg Kroah-Hartman
Browse files

thunderbolt: Add support for Internal Connection Manager (ICM)



Starting from Intel Falcon Ridge the internal connection manager running
on the Thunderbolt host controller has been supporting 4 security
levels. One reason for this is to prevent DMA attacks and only allow
connecting devices the user trusts.

The internal connection manager (ICM) is the preferred way of connecting
Thunderbolt devices over software only implementation typically used on
Macs. The driver communicates with ICM using special Thunderbolt ring 0
(control channel) messages. In order to handle these messages we add
support for the ICM messages to the control channel.

The security levels are as follows:

  none - No security, all tunnels are created automatically
  user - User needs to approve the device before tunnels are created
  secure - User need to approve the device before tunnels are created.
	   The device is sent a challenge on future connects to be able
	   to verify it is actually the approved device.
  dponly - Only Display Port and USB tunnels can be created and those
           are created automatically.

The security levels are typically configurable from the system BIOS and
by default it is set to "user" on many systems.

In this patch each Thunderbolt device will have either one or two new
sysfs attributes: authorized and key. The latter appears for devices
that support secure connect.

In order to identify the device the user can read identication
information, including UUID and name of the device from sysfs and based
on that make a decision to authorize the device. The device is
authorized by simply writing 1 to the "authorized" sysfs attribute. This
is following the USB bus device authorization mechanism. The secure
connect requires an additional challenge step (writing 2 to the
"authorized" attribute) in future connects when the key has already been
stored to the NVM of the device.

Non-ICM systems (before Alpine Ridge) continue to use the existing
functionality and the security level is set to none. For systems with
Alpine Ridge, even on Apple hardware, we will use ICM.

This code is based on the work done by Amir Levy and Michael Jamet.

Signed-off-by: default avatarMichael Jamet <michael.jamet@intel.com>
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: default avatarYehezkel Bernat <yehezkel.bernat@intel.com>
Reviewed-by: default avatarAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: default avatarAndreas Noever <andreas.noever@gmail.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent bdccf295
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
What: /sys/bus/thunderbolt/devices/.../domainX/security
Date:		Sep 2017
KernelVersion:	4.13
Contact:	thunderbolt-software@lists.01.org
Description:	This attribute holds current Thunderbolt security level
		set by the system BIOS. Possible values are:

		none: All devices are automatically authorized
		user: Devices are only authorized based on writing
		      appropriate value to the authorized attribute
		secure: Require devices that support secure connect at
			minimum. User needs to authorize each device.
		dponly: Automatically tunnel Display port (and USB). No
			PCIe tunnels are created.

What: /sys/bus/thunderbolt/devices/.../authorized
Date:		Sep 2017
KernelVersion:	4.13
Contact:	thunderbolt-software@lists.01.org
Description:	This attribute is used to authorize Thunderbolt devices
		after they have been connected. If the device is not
		authorized, no devices such as PCIe and Display port are
		available to the system.

		Contents of this attribute will be 0 when the device is not
		yet authorized.

		Possible values are supported:
		1: The device will be authorized and connected

		When key attribute contains 32 byte hex string the possible
		values are:
		1: The 32 byte hex string is added to the device NVM and
		   the device is authorized.
		2: Send a challenge based on the 32 byte hex string. If the
		   challenge response from device is valid, the device is
		   authorized. In case of failure errno will be ENOKEY if
		   the device did not contain a key at all, and
		   EKEYREJECTED if the challenge response did not match.

What: /sys/bus/thunderbolt/devices/.../key
Date:		Sep 2017
KernelVersion:	4.13
Contact:	thunderbolt-software@lists.01.org
Description:	When a devices supports Thunderbolt secure connect it will
		have this attribute. Writing 32 byte hex string changes
		authorization to use the secure connection method instead.

What:		/sys/bus/thunderbolt/devices/.../device
Date:		Sep 2017
KernelVersion:	4.13
+6 −6
Original line number Diff line number Diff line
menuconfig THUNDERBOLT
	tristate "Thunderbolt support for Apple devices"
	tristate "Thunderbolt support"
	depends on PCI
	depends on X86 || COMPILE_TEST
	select APPLE_PROPERTIES if EFI_STUB && X86
	select CRC32
	select CRYPTO
	select CRYPTO_HASH
	help
	  Cactus Ridge Thunderbolt Controller driver
	  This driver is required if you want to hotplug Thunderbolt devices on
	  Apple hardware.

	  Device chaining is currently not supported.
	  Thunderbolt Controller driver. This driver is required if you
	  want to hotplug Thunderbolt devices on Apple hardware or on PCs
	  with Intel Falcon Ridge or newer.

	  To compile this driver a module, choose M here. The module will be
	  called thunderbolt.
+1 −1
Original line number Diff line number Diff line
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o
thunderbolt-objs += domain.o dma_port.o
thunderbolt-objs += domain.o dma_port.o icm.o
+2 −0
Original line number Diff line number Diff line
@@ -463,6 +463,8 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
				   "RX: checksum mismatch, dropping packet\n");
			goto rx;
		}
		/* Fall through */
	case TB_CFG_PKG_ICM_EVENT:
		tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size);
		goto rx;

+195 −0
Original line number Diff line number Diff line
@@ -13,11 +13,43 @@
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <crypto/hash.h>

#include "tb.h"

static DEFINE_IDA(tb_domain_ida);

static const char * const tb_security_names[] = {
	[TB_SECURITY_NONE] = "none",
	[TB_SECURITY_USER] = "user",
	[TB_SECURITY_SECURE] = "secure",
	[TB_SECURITY_DPONLY] = "dponly",
};

static ssize_t security_show(struct device *dev, struct device_attribute *attr,
			     char *buf)
{
	struct tb *tb = container_of(dev, struct tb, dev);

	return sprintf(buf, "%s\n", tb_security_names[tb->security_level]);
}
static DEVICE_ATTR_RO(security);

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

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

static const struct attribute_group *domain_attr_groups[] = {
	&domain_attr_group,
	NULL,
};

struct bus_type tb_bus_type = {
	.name = "thunderbolt",
};
@@ -82,6 +114,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize)
	tb->dev.parent = &nhi->pdev->dev;
	tb->dev.bus = &tb_bus_type;
	tb->dev.type = &tb_domain_type;
	tb->dev.groups = domain_attr_groups;
	dev_set_name(&tb->dev, "domain%d", tb->index);
	device_initialize(&tb->dev);

@@ -140,6 +173,12 @@ int tb_domain_add(struct tb *tb)
	 */
	tb_ctl_start(tb->ctl);

	if (tb->cm_ops->driver_ready) {
		ret = tb->cm_ops->driver_ready(tb);
		if (ret)
			goto err_ctl_stop;
	}

	ret = device_add(&tb->dev);
	if (ret)
		goto err_ctl_stop;
@@ -231,6 +270,162 @@ int tb_domain_resume_noirq(struct tb *tb)
	return ret;
}

int tb_domain_suspend(struct tb *tb)
{
	int ret;

	mutex_lock(&tb->lock);
	if (tb->cm_ops->suspend) {
		ret = tb->cm_ops->suspend(tb);
		if (ret) {
			mutex_unlock(&tb->lock);
			return ret;
		}
	}
	mutex_unlock(&tb->lock);
	return 0;
}

void tb_domain_complete(struct tb *tb)
{
	mutex_lock(&tb->lock);
	if (tb->cm_ops->complete)
		tb->cm_ops->complete(tb);
	mutex_unlock(&tb->lock);
}

/**
 * tb_domain_approve_switch() - Approve switch
 * @tb: Domain the switch belongs to
 * @sw: Switch to approve
 *
 * This will approve switch by connection manager specific means. In
 * case of success the connection manager will create tunnels for all
 * supported protocols.
 */
int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw)
{
	struct tb_switch *parent_sw;

	if (!tb->cm_ops->approve_switch)
		return -EPERM;

	/* The parent switch must be authorized before this one */
	parent_sw = tb_to_switch(sw->dev.parent);
	if (!parent_sw || !parent_sw->authorized)
		return -EINVAL;

	return tb->cm_ops->approve_switch(tb, sw);
}

/**
 * tb_domain_approve_switch_key() - Approve switch and add key
 * @tb: Domain the switch belongs to
 * @sw: Switch to approve
 *
 * For switches that support secure connect, this function first adds
 * key to the switch NVM using connection manager specific means. If
 * adding the key is successful, the switch is approved and connected.
 *
 * Return: %0 on success and negative errno in case of failure.
 */
int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw)
{
	struct tb_switch *parent_sw;
	int ret;

	if (!tb->cm_ops->approve_switch || !tb->cm_ops->add_switch_key)
		return -EPERM;

	/* The parent switch must be authorized before this one */
	parent_sw = tb_to_switch(sw->dev.parent);
	if (!parent_sw || !parent_sw->authorized)
		return -EINVAL;

	ret = tb->cm_ops->add_switch_key(tb, sw);
	if (ret)
		return ret;

	return tb->cm_ops->approve_switch(tb, sw);
}

/**
 * tb_domain_challenge_switch_key() - Challenge and approve switch
 * @tb: Domain the switch belongs to
 * @sw: Switch to approve
 *
 * For switches that support secure connect, this function generates
 * random challenge and sends it to the switch. The switch responds to
 * this and if the response matches our random challenge, the switch is
 * approved and connected.
 *
 * Return: %0 on success and negative errno in case of failure.
 */
int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw)
{
	u8 challenge[TB_SWITCH_KEY_SIZE];
	u8 response[TB_SWITCH_KEY_SIZE];
	u8 hmac[TB_SWITCH_KEY_SIZE];
	struct tb_switch *parent_sw;
	struct crypto_shash *tfm;
	struct shash_desc *shash;
	int ret;

	if (!tb->cm_ops->approve_switch || !tb->cm_ops->challenge_switch_key)
		return -EPERM;

	/* The parent switch must be authorized before this one */
	parent_sw = tb_to_switch(sw->dev.parent);
	if (!parent_sw || !parent_sw->authorized)
		return -EINVAL;

	get_random_bytes(challenge, sizeof(challenge));
	ret = tb->cm_ops->challenge_switch_key(tb, sw, challenge, response);
	if (ret)
		return ret;

	tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
	if (IS_ERR(tfm))
		return PTR_ERR(tfm);

	ret = crypto_shash_setkey(tfm, sw->key, TB_SWITCH_KEY_SIZE);
	if (ret)
		goto err_free_tfm;

	shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm),
			GFP_KERNEL);
	if (!shash) {
		ret = -ENOMEM;
		goto err_free_tfm;
	}

	shash->tfm = tfm;
	shash->flags = CRYPTO_TFM_REQ_MAY_SLEEP;

	memset(hmac, 0, sizeof(hmac));
	ret = crypto_shash_digest(shash, challenge, sizeof(hmac), hmac);
	if (ret)
		goto err_free_shash;

	/* The returned HMAC must match the one we calculated */
	if (memcmp(response, hmac, sizeof(hmac))) {
		ret = -EKEYREJECTED;
		goto err_free_shash;
	}

	crypto_free_shash(tfm);
	kfree(shash);

	return tb->cm_ops->approve_switch(tb, sw);

err_free_shash:
	kfree(shash);
err_free_tfm:
	crypto_free_shash(tfm);

	return ret;
}

int tb_domain_init(void)
{
	return bus_register(&tb_bus_type);
Loading