thunderbolt: Add support for preboot ACL
authorMika Westerberg <mika.westerberg@linux.intel.com>
Sun, 21 Jan 2018 10:08:04 +0000 (12:08 +0200)
committerMika Westerberg <mika.westerberg@linux.intel.com>
Fri, 9 Mar 2018 09:54:11 +0000 (12:54 +0300)
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>
Documentation/ABI/testing/sysfs-bus-thunderbolt
drivers/thunderbolt/domain.c
drivers/thunderbolt/icm.c
drivers/thunderbolt/tb.h
drivers/thunderbolt/tb_msgs.h
include/linux/thunderbolt.h

index 1f145b727d7641dfb3f876d7d4d7d5a17bdf47e0..4ed229789852673cc3bb0f9b3ae454bd95776304 100644 (file)
@@ -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
index 9b90115319ce8ef5065bd3e418b76a34fab1051e..ab4b304306f7c8907cc0d77994172ecf0303e40c 100644 (file)
@@ -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)
 {
@@ -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,
 };
 
index bece5540b06b24a2512a1dd43a02b651f8c3097f..93a198a17f42f2c21bb9315d312b158591632eb3 100644 (file)
@@ -56,6 +56,7 @@
  * @vnd_cap: Vendor defined capability where PCIe2CIO mailbox resides
  *          (only set when @upstream_port is not %NULL)
  * @safe_mode: ICM is in safe mode
+ * @max_boot_acl: Maximum number of preboot ACL entries (%0 if not supported)
  * @is_supported: Checks if we can support ICM on this controller
  * @get_mode: Read and return the ICM firmware mode (optional)
  * @get_route: Find a route string for given switch
@@ -69,13 +70,15 @@ struct icm {
        struct mutex request_lock;
        struct delayed_work rescan_work;
        struct pci_dev *upstream_port;
+       size_t max_boot_acl;
        int vnd_cap;
        bool safe_mode;
        bool (*is_supported)(struct tb *tb);
        int (*get_mode)(struct tb *tb);
        int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route);
        int (*driver_ready)(struct tb *tb,
-                           enum tb_security_level *security_level);
+                           enum tb_security_level *security_level,
+                           size_t *nboot_acl);
        void (*device_connected)(struct tb *tb,
                                 const struct icm_pkg_header *hdr);
        void (*device_disconnected)(struct tb *tb,
@@ -250,7 +253,8 @@ err_free:
 }
 
 static int
-icm_fr_driver_ready(struct tb *tb, enum tb_security_level *security_level)
+icm_fr_driver_ready(struct tb *tb, enum tb_security_level *security_level,
+                   size_t *nboot_acl)
 {
        struct icm_fr_pkg_driver_ready_response reply;
        struct icm_pkg_driver_ready request = {
@@ -827,6 +831,30 @@ static int icm_ar_get_mode(struct tb *tb)
        return nhi_mailbox_mode(nhi);
 }
 
+static int
+icm_ar_driver_ready(struct tb *tb, enum tb_security_level *security_level,
+                   size_t *nboot_acl)
+{
+       struct icm_ar_pkg_driver_ready_response reply;
+       struct icm_pkg_driver_ready request = {
+               .hdr.code = ICM_DRIVER_READY,
+       };
+       int ret;
+
+       memset(&reply, 0, sizeof(reply));
+       ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+                         1, ICM_TIMEOUT);
+       if (ret)
+               return ret;
+
+       if (security_level)
+               *security_level = reply.info & ICM_AR_INFO_SLEVEL_MASK;
+       if (nboot_acl && (reply.info & ICM_AR_INFO_BOOT_ACL_SUPPORTED))
+               *nboot_acl = (reply.info & ICM_AR_INFO_BOOT_ACL_MASK) >>
+                               ICM_AR_INFO_BOOT_ACL_SHIFT;
+       return 0;
+}
+
 static int icm_ar_get_route(struct tb *tb, u8 link, u8 depth, u64 *route)
 {
        struct icm_ar_pkg_get_route_response reply;
@@ -849,6 +877,87 @@ static int icm_ar_get_route(struct tb *tb, u8 link, u8 depth, u64 *route)
        return 0;
 }
 
+static int icm_ar_get_boot_acl(struct tb *tb, uuid_t *uuids, size_t nuuids)
+{
+       struct icm_ar_pkg_preboot_acl_response reply;
+       struct icm_ar_pkg_preboot_acl request = {
+               .hdr = { .code = ICM_PREBOOT_ACL },
+       };
+       int ret, i;
+
+       memset(&reply, 0, sizeof(reply));
+       ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+                         1, ICM_TIMEOUT);
+       if (ret)
+               return ret;
+
+       if (reply.hdr.flags & ICM_FLAGS_ERROR)
+               return -EIO;
+
+       for (i = 0; i < nuuids; i++) {
+               u32 *uuid = (u32 *)&uuids[i];
+
+               uuid[0] = reply.acl[i].uuid_lo;
+               uuid[1] = reply.acl[i].uuid_hi;
+
+               if (uuid[0] == 0xffffffff && uuid[1] == 0xffffffff) {
+                       /* Map empty entries to null UUID */
+                       uuid[0] = 0;
+                       uuid[1] = 0;
+               } else {
+                       /* Upper two DWs are always one's */
+                       uuid[2] = 0xffffffff;
+                       uuid[3] = 0xffffffff;
+               }
+       }
+
+       return ret;
+}
+
+static int icm_ar_set_boot_acl(struct tb *tb, const uuid_t *uuids,
+                              size_t nuuids)
+{
+       struct icm_ar_pkg_preboot_acl_response reply;
+       struct icm_ar_pkg_preboot_acl request = {
+               .hdr = {
+                       .code = ICM_PREBOOT_ACL,
+                       .flags = ICM_FLAGS_WRITE,
+               },
+       };
+       int ret, i;
+
+       for (i = 0; i < nuuids; i++) {
+               const u32 *uuid = (const u32 *)&uuids[i];
+
+               if (uuid_is_null(&uuids[i])) {
+                       /*
+                        * Map null UUID to the empty (all one) entries
+                        * for ICM.
+                        */
+                       request.acl[i].uuid_lo = 0xffffffff;
+                       request.acl[i].uuid_hi = 0xffffffff;
+               } else {
+                       /* Two high DWs need to be set to all one */
+                       if (uuid[2] != 0xffffffff || uuid[3] != 0xffffffff)
+                               return -EINVAL;
+
+                       request.acl[i].uuid_lo = uuid[0];
+                       request.acl[i].uuid_hi = uuid[1];
+               }
+       }
+
+       memset(&reply, 0, sizeof(reply));
+       ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
+                         1, ICM_TIMEOUT);
+       if (ret)
+               return ret;
+
+       if (reply.hdr.flags & ICM_FLAGS_ERROR)
+               return -EIO;
+
+       return 0;
+}
+
 static void icm_handle_notification(struct work_struct *work)
 {
        struct icm_notification *n = container_of(work, typeof(*n), work);
@@ -895,13 +1004,14 @@ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
 }
 
 static int
-__icm_driver_ready(struct tb *tb, enum tb_security_level *security_level)
+__icm_driver_ready(struct tb *tb, enum tb_security_level *security_level,
+                  size_t *nboot_acl)
 {
        struct icm *icm = tb_priv(tb);
        unsigned int retries = 50;
        int ret;
 
-       ret = icm->driver_ready(tb, security_level);
+       ret = icm->driver_ready(tb, security_level, nboot_acl);
        if (ret) {
                tb_err(tb, "failed to send driver ready to ICM\n");
                return ret;
@@ -1168,7 +1278,18 @@ static int icm_driver_ready(struct tb *tb)
                return 0;
        }
 
-       return __icm_driver_ready(tb, &tb->security_level);
+       ret = __icm_driver_ready(tb, &tb->security_level, &tb->nboot_acl);
+       if (ret)
+               return ret;
+
+       /*
+        * Make sure the number of supported preboot ACL matches what we
+        * expect or disable the whole feature.
+        */
+       if (tb->nboot_acl > icm->max_boot_acl)
+               tb->nboot_acl = 0;
+
+       return 0;
 }
 
 static int icm_suspend(struct tb *tb)
@@ -1264,7 +1385,7 @@ static void icm_complete(struct tb *tb)
         * Now all existing children should be resumed, start events
         * from ICM to get updated status.
         */
-       __icm_driver_ready(tb, NULL);
+       __icm_driver_ready(tb, NULL, NULL);
 
        /*
         * We do not get notifications of devices that have been
@@ -1317,7 +1438,7 @@ static int icm_disconnect_pcie_paths(struct tb *tb)
        return nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DISCONNECT_PCIE_PATHS, 0);
 }
 
-/* Falcon Ridge and Alpine Ridge */
+/* Falcon Ridge */
 static const struct tb_cm_ops icm_fr_ops = {
        .driver_ready = icm_driver_ready,
        .start = icm_start,
@@ -1333,6 +1454,24 @@ static const struct tb_cm_ops icm_fr_ops = {
        .disconnect_xdomain_paths = icm_fr_disconnect_xdomain_paths,
 };
 
+/* Alpine Ridge */
+static const struct tb_cm_ops icm_ar_ops = {
+       .driver_ready = icm_driver_ready,
+       .start = icm_start,
+       .stop = icm_stop,
+       .suspend = icm_suspend,
+       .complete = icm_complete,
+       .handle_event = icm_handle_event,
+       .get_boot_acl = icm_ar_get_boot_acl,
+       .set_boot_acl = icm_ar_set_boot_acl,
+       .approve_switch = icm_fr_approve_switch,
+       .add_switch_key = icm_fr_add_switch_key,
+       .challenge_switch_key = icm_fr_challenge_switch_key,
+       .disconnect_pcie_paths = icm_disconnect_pcie_paths,
+       .approve_xdomain_paths = icm_fr_approve_xdomain_paths,
+       .disconnect_xdomain_paths = icm_fr_disconnect_xdomain_paths,
+};
+
 struct tb *icm_probe(struct tb_nhi *nhi)
 {
        struct icm *icm;
@@ -1364,15 +1503,16 @@ struct tb *icm_probe(struct tb_nhi *nhi)
        case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI:
        case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI:
        case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI:
+               icm->max_boot_acl = ICM_AR_PREBOOT_ACL_ENTRIES;
                icm->is_supported = icm_ar_is_supported;
                icm->get_mode = icm_ar_get_mode;
                icm->get_route = icm_ar_get_route;
-               icm->driver_ready = icm_fr_driver_ready;
+               icm->driver_ready = icm_ar_driver_ready;
                icm->device_connected = icm_fr_device_connected;
                icm->device_disconnected = icm_fr_device_disconnected;
                icm->xdomain_connected = icm_fr_xdomain_connected;
                icm->xdomain_disconnected = icm_fr_xdomain_disconnected;
-               tb->cm_ops = &icm_fr_ops;
+               tb->cm_ops = &icm_ar_ops;
                break;
        }
 
index 9c9cef875ca8a70ca61af5a9f99bc55bdf839606..9d9f0ca16bfbea9bd18695ee712757df0354730f 100644 (file)
@@ -200,6 +200,8 @@ struct tb_path {
  * @suspend: Connection manager specific suspend
  * @complete: Connection manager specific complete
  * @handle_event: Handle thunderbolt event
+ * @get_boot_acl: Get boot ACL list
+ * @set_boot_acl: Set boot ACL list
  * @approve_switch: Approve switch
  * @add_switch_key: Add key to switch
  * @challenge_switch_key: Challenge switch using key
@@ -217,6 +219,8 @@ struct tb_cm_ops {
        void (*complete)(struct tb *tb);
        void (*handle_event)(struct tb *tb, enum tb_cfg_pkg_type,
                             const void *buf, size_t size);
+       int (*get_boot_acl)(struct tb *tb, uuid_t *uuids, size_t nuuids);
+       int (*set_boot_acl)(struct tb *tb, const uuid_t *uuids, size_t nuuids);
        int (*approve_switch)(struct tb *tb, struct tb_switch *sw);
        int (*add_switch_key)(struct tb *tb, struct tb_switch *sw);
        int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw,
index 9f52f842257a4359ef01935ba846aea3d7c1b4a6..496b91f3b579ba10f3d5fb5aa71b70436a18ae68 100644 (file)
@@ -102,6 +102,7 @@ enum icm_pkg_code {
        ICM_ADD_DEVICE_KEY = 0x6,
        ICM_GET_ROUTE = 0xa,
        ICM_APPROVE_XDOMAIN = 0x10,
+       ICM_PREBOOT_ACL = 0x18,
 };
 
 enum icm_event_code {
@@ -122,12 +123,13 @@ struct icm_pkg_header {
 #define ICM_FLAGS_NO_KEY               BIT(1)
 #define ICM_FLAGS_SLEVEL_SHIFT         3
 #define ICM_FLAGS_SLEVEL_MASK          GENMASK(4, 3)
+#define ICM_FLAGS_WRITE                        BIT(7)
 
 struct icm_pkg_driver_ready {
        struct icm_pkg_header hdr;
 };
 
-/* Falcon Ridge & Alpine Ridge common messages */
+/* Falcon Ridge only messages */
 
 struct icm_fr_pkg_driver_ready_response {
        struct icm_pkg_header hdr;
@@ -138,6 +140,8 @@ struct icm_fr_pkg_driver_ready_response {
 
 #define ICM_FR_SLEVEL_MASK             0xf
 
+/* Falcon Ridge & Alpine Ridge common messages */
+
 struct icm_fr_pkg_get_topology {
        struct icm_pkg_header hdr;
 };
@@ -274,6 +278,18 @@ struct icm_fr_pkg_approve_xdomain_response {
 
 /* Alpine Ridge only messages */
 
+struct icm_ar_pkg_driver_ready_response {
+       struct icm_pkg_header hdr;
+       u8 romver;
+       u8 ramver;
+       u16 info;
+};
+
+#define ICM_AR_INFO_SLEVEL_MASK                GENMASK(3, 0)
+#define ICM_AR_INFO_BOOT_ACL_SHIFT     7
+#define ICM_AR_INFO_BOOT_ACL_MASK      GENMASK(11, 7)
+#define ICM_AR_INFO_BOOT_ACL_SUPPORTED BIT(13)
+
 struct icm_ar_pkg_get_route {
        struct icm_pkg_header hdr;
        u16 reserved;
@@ -288,6 +304,23 @@ struct icm_ar_pkg_get_route_response {
        u32 route_lo;
 };
 
+struct icm_ar_boot_acl_entry {
+       u32 uuid_lo;
+       u32 uuid_hi;
+};
+
+#define ICM_AR_PREBOOT_ACL_ENTRIES     16
+
+struct icm_ar_pkg_preboot_acl {
+       struct icm_pkg_header hdr;
+       struct icm_ar_boot_acl_entry acl[ICM_AR_PREBOOT_ACL_ENTRIES];
+};
+
+struct icm_ar_pkg_preboot_acl_response {
+       struct icm_pkg_header hdr;
+       struct icm_ar_boot_acl_entry acl[ICM_AR_PREBOOT_ACL_ENTRIES];
+};
+
 /* XDomain messages */
 
 struct tb_xdomain_header {
index 27b9be34d4b9d60a83d659d33fb1e22e67eaaf78..47251844d064820b8453d9a18c28feab418c2b45 100644 (file)
@@ -65,6 +65,7 @@ enum tb_security_level {
  * @cm_ops: Connection manager specific operations vector
  * @index: Linux assigned domain number
  * @security_level: Current security level
+ * @nboot_acl: Number of boot ACLs the domain supports
  * @privdata: Private connection manager specific data
  */
 struct tb {
@@ -77,6 +78,7 @@ struct tb {
        const struct tb_cm_ops *cm_ops;
        int index;
        enum tb_security_level security_level;
+       size_t nboot_acl;
        unsigned long privdata[0];
 };