Bluetooth: Add key size checks for SMP
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / net / bluetooth / hci_core.c
index b5a8afc2be331f5ac5b26649e4609e21039f8b85..f62ca1935f5a30f608cbef11ef9443a397ec4cae 100644 (file)
@@ -42,6 +42,7 @@
 #include <linux/notifier.h>
 #include <linux/rfkill.h>
 #include <linux/timer.h>
+#include <linux/crypto.h>
 #include <net/sock.h>
 
 #include <asm/system.h>
 static void hci_cmd_task(unsigned long arg);
 static void hci_rx_task(unsigned long arg);
 static void hci_tx_task(unsigned long arg);
-static void hci_notify(struct hci_dev *hdev, int event);
 
 static DEFINE_RWLOCK(hci_task_lock);
 
+static int enable_smp;
+
 /* HCI device list */
 LIST_HEAD(hci_dev_list);
 DEFINE_RWLOCK(hci_dev_list_lock);
@@ -1021,18 +1023,54 @@ struct link_key *hci_find_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr)
        return NULL;
 }
 
-int hci_add_link_key(struct hci_dev *hdev, int new_key, bdaddr_t *bdaddr,
-                                               u8 *val, u8 type, u8 pin_len)
+static int hci_persistent_key(struct hci_dev *hdev, struct hci_conn *conn,
+                                               u8 key_type, u8 old_key_type)
+{
+       /* Legacy key */
+       if (key_type < 0x03)
+               return 1;
+
+       /* Debug keys are insecure so don't store them persistently */
+       if (key_type == HCI_LK_DEBUG_COMBINATION)
+               return 0;
+
+       /* Changed combination key and there's no previous one */
+       if (key_type == HCI_LK_CHANGED_COMBINATION && old_key_type == 0xff)
+               return 0;
+
+       /* Security mode 3 case */
+       if (!conn)
+               return 1;
+
+       /* Neither local nor remote side had no-bonding as requirement */
+       if (conn->auth_type > 0x01 && conn->remote_auth > 0x01)
+               return 1;
+
+       /* Local side had dedicated bonding as requirement */
+       if (conn->auth_type == 0x02 || conn->auth_type == 0x03)
+               return 1;
+
+       /* Remote side had dedicated bonding as requirement */
+       if (conn->remote_auth == 0x02 || conn->remote_auth == 0x03)
+               return 1;
+
+       /* If none of the above criteria match, then don't store the key
+        * persistently */
+       return 0;
+}
+
+int hci_add_link_key(struct hci_dev *hdev, struct hci_conn *conn, int new_key,
+                               bdaddr_t *bdaddr, u8 *val, u8 type, u8 pin_len)
 {
        struct link_key *key, *old_key;
-       u8 old_key_type;
+       u8 old_key_type, persistent;
 
        old_key = hci_find_link_key(hdev, bdaddr);
        if (old_key) {
                old_key_type = old_key->type;
                key = old_key;
        } else {
-               old_key_type = 0xff;
+               old_key_type = conn ? conn->key_type : 0xff;
                key = kzalloc(sizeof(*key), GFP_ATOMIC);
                if (!key)
                        return -ENOMEM;
@@ -1041,16 +1079,37 @@ int hci_add_link_key(struct hci_dev *hdev, int new_key, bdaddr_t *bdaddr,
 
        BT_DBG("%s key for %s type %u", hdev->name, batostr(bdaddr), type);
 
+       /* Some buggy controller combinations generate a changed
+        * combination key for legacy pairing even when there's no
+        * previous key */
+       if (type == HCI_LK_CHANGED_COMBINATION &&
+                                       (!conn || conn->remote_auth == 0xff) &&
+                                       old_key_type == 0xff) {
+               type = HCI_LK_COMBINATION;
+               if (conn)
+                       conn->key_type = type;
+       }
+
        bacpy(&key->bdaddr, bdaddr);
        memcpy(key->val, val, 16);
-       key->type = type;
        key->pin_len = pin_len;
 
-       if (new_key)
-               mgmt_new_key(hdev->id, key, old_key_type);
-
-       if (type == 0x06)
+       if (type == HCI_LK_CHANGED_COMBINATION)
                key->type = old_key_type;
+       else
+               key->type = type;
+
+       if (!new_key)
+               return 0;
+
+       persistent = hci_persistent_key(hdev, conn, type, old_key_type);
+
+       mgmt_new_key(hdev->id, key, persistent);
+
+       if (!persistent) {
+               list_del(&key->list);
+               kfree(key);
+       }
 
        return 0;
 }
@@ -1082,6 +1141,150 @@ static void hci_cmd_timer(unsigned long arg)
        tasklet_schedule(&hdev->cmd_task);
 }
 
+struct oob_data *hci_find_remote_oob_data(struct hci_dev *hdev,
+                                                       bdaddr_t *bdaddr)
+{
+       struct oob_data *data;
+
+       list_for_each_entry(data, &hdev->remote_oob_data, list)
+               if (bacmp(bdaddr, &data->bdaddr) == 0)
+                       return data;
+
+       return NULL;
+}
+
+int hci_remove_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr)
+{
+       struct oob_data *data;
+
+       data = hci_find_remote_oob_data(hdev, bdaddr);
+       if (!data)
+               return -ENOENT;
+
+       BT_DBG("%s removing %s", hdev->name, batostr(bdaddr));
+
+       list_del(&data->list);
+       kfree(data);
+
+       return 0;
+}
+
+int hci_remote_oob_data_clear(struct hci_dev *hdev)
+{
+       struct oob_data *data, *n;
+
+       list_for_each_entry_safe(data, n, &hdev->remote_oob_data, list) {
+               list_del(&data->list);
+               kfree(data);
+       }
+
+       return 0;
+}
+
+int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 *hash,
+                                                               u8 *randomizer)
+{
+       struct oob_data *data;
+
+       data = hci_find_remote_oob_data(hdev, bdaddr);
+
+       if (!data) {
+               data = kmalloc(sizeof(*data), GFP_ATOMIC);
+               if (!data)
+                       return -ENOMEM;
+
+               bacpy(&data->bdaddr, bdaddr);
+               list_add(&data->list, &hdev->remote_oob_data);
+       }
+
+       memcpy(data->hash, hash, sizeof(data->hash));
+       memcpy(data->randomizer, randomizer, sizeof(data->randomizer));
+
+       BT_DBG("%s for %s", hdev->name, batostr(bdaddr));
+
+       return 0;
+}
+
+static void hci_clear_adv_cache(unsigned long arg)
+{
+       struct hci_dev *hdev = (void *) arg;
+
+       hci_dev_lock(hdev);
+
+       hci_adv_entries_clear(hdev);
+
+       hci_dev_unlock(hdev);
+}
+
+int hci_adv_entries_clear(struct hci_dev *hdev)
+{
+       struct adv_entry *entry, *tmp;
+
+       list_for_each_entry_safe(entry, tmp, &hdev->adv_entries, list) {
+               list_del(&entry->list);
+               kfree(entry);
+       }
+
+       BT_DBG("%s adv cache cleared", hdev->name);
+
+       return 0;
+}
+
+struct adv_entry *hci_find_adv_entry(struct hci_dev *hdev, bdaddr_t *bdaddr)
+{
+       struct adv_entry *entry;
+
+       list_for_each_entry(entry, &hdev->adv_entries, list)
+               if (bacmp(bdaddr, &entry->bdaddr) == 0)
+                       return entry;
+
+       return NULL;
+}
+
+static inline int is_connectable_adv(u8 evt_type)
+{
+       if (evt_type == ADV_IND || evt_type == ADV_DIRECT_IND)
+               return 1;
+
+       return 0;
+}
+
+int hci_add_adv_entry(struct hci_dev *hdev,
+                                       struct hci_ev_le_advertising_info *ev)
+{
+       struct adv_entry *entry;
+
+       if (!is_connectable_adv(ev->evt_type))
+               return -EINVAL;
+
+       /* Only new entries should be added to adv_entries. So, if
+        * bdaddr was found, don't add it. */
+       if (hci_find_adv_entry(hdev, &ev->bdaddr))
+               return 0;
+
+       entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
+       if (!entry)
+               return -ENOMEM;
+
+       bacpy(&entry->bdaddr, &ev->bdaddr);
+       entry->bdaddr_type = ev->bdaddr_type;
+
+       list_add(&entry->list, &hdev->adv_entries);
+
+       BT_DBG("%s adv entry added: address %s type %u", hdev->name,
+                               batostr(&entry->bdaddr), entry->bdaddr_type);
+
+       return 0;
+}
+
+static struct crypto_blkcipher *alloc_cypher(void)
+{
+       if (enable_smp)
+               return crypto_alloc_blkcipher("ecb(aes)", 0, CRYPTO_ALG_ASYNC);
+
+       return ERR_PTR(-ENOTSUPP);
+}
+
 /* Register HCI device */
 int hci_register_dev(struct hci_dev *hdev)
 {
@@ -1146,6 +1349,12 @@ int hci_register_dev(struct hci_dev *hdev)
 
        INIT_LIST_HEAD(&hdev->link_keys);
 
+       INIT_LIST_HEAD(&hdev->remote_oob_data);
+
+       INIT_LIST_HEAD(&hdev->adv_entries);
+       setup_timer(&hdev->adv_timer, hci_clear_adv_cache,
+                                               (unsigned long) hdev);
+
        INIT_WORK(&hdev->power_on, hci_power_on);
        INIT_WORK(&hdev->power_off, hci_power_off);
        setup_timer(&hdev->off_timer, hci_auto_off, (unsigned long) hdev);
@@ -1160,6 +1369,11 @@ int hci_register_dev(struct hci_dev *hdev)
        if (!hdev->workqueue)
                goto nomem;
 
+       hdev->tfm = alloc_cypher();
+       if (IS_ERR(hdev->tfm))
+               BT_INFO("Failed to load transform for ecb(aes): %ld",
+                                                       PTR_ERR(hdev->tfm));
+
        hci_register_sysfs(hdev);
 
        hdev->rfkill = rfkill_alloc(hdev->name, &hdev->dev,
@@ -1208,6 +1422,9 @@ int hci_unregister_dev(struct hci_dev *hdev)
                                        !test_bit(HCI_SETUP, &hdev->flags))
                mgmt_index_removed(hdev->id);
 
+       if (!IS_ERR(hdev->tfm))
+               crypto_free_blkcipher(hdev->tfm);
+
        hci_notify(hdev, HCI_DEV_UNREG);
 
        if (hdev->rfkill) {
@@ -1218,6 +1435,7 @@ int hci_unregister_dev(struct hci_dev *hdev)
        hci_unregister_sysfs(hdev);
 
        hci_del_off_timer(hdev);
+       del_timer(&hdev->adv_timer);
 
        destroy_workqueue(hdev->workqueue);
 
@@ -1225,6 +1443,8 @@ int hci_unregister_dev(struct hci_dev *hdev)
        hci_blacklist_clear(hdev);
        hci_uuids_clear(hdev);
        hci_link_keys_clear(hdev);
+       hci_remote_oob_data_clear(hdev);
+       hci_adv_entries_clear(hdev);
        hci_dev_unlock_bh(hdev);
 
        __hci_dev_put(hdev);
@@ -1274,7 +1494,7 @@ int hci_recv_frame(struct sk_buff *skb)
 EXPORT_SYMBOL(hci_recv_frame);
 
 static int hci_reassembly(struct hci_dev *hdev, int type, void *data,
-                         int count, __u8 index, gfp_t gfp_mask)
+                                                 int count, __u8 index)
 {
        int len = 0;
        int hlen = 0;
@@ -1304,7 +1524,7 @@ static int hci_reassembly(struct hci_dev *hdev, int type, void *data,
                        break;
                }
 
-               skb = bt_skb_alloc(len, gfp_mask);
+               skb = bt_skb_alloc(len, GFP_ATOMIC);
                if (!skb)
                        return -ENOMEM;
 
@@ -1390,8 +1610,7 @@ int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count)
                return -EILSEQ;
 
        while (count) {
-               rem = hci_reassembly(hdev, type, data, count,
-                                               type - 1, GFP_ATOMIC);
+               rem = hci_reassembly(hdev, type, data, count, type - 1);
                if (rem < 0)
                        return rem;
 
@@ -1425,8 +1644,8 @@ int hci_recv_stream_fragment(struct hci_dev *hdev, void *data, int count)
                } else
                        type = bt_cb(skb)->pkt_type;
 
-               rem = hci_reassembly(hdev, type, data,
-                                       count, STREAM_REASSEMBLY, GFP_ATOMIC);
+               rem = hci_reassembly(hdev, type, data, count,
+                                                       STREAM_REASSEMBLY);
                if (rem < 0)
                        return rem;
 
@@ -1769,7 +1988,7 @@ static inline void hci_sched_acl(struct hci_dev *hdev)
                while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
                        BT_DBG("skb %p len %d", skb, skb->len);
 
-                       hci_conn_enter_active_mode(conn);
+                       hci_conn_enter_active_mode(conn, bt_cb(skb)->force_active);
 
                        hci_send_frame(skb);
                        hdev->acl_last_tx = jiffies;
@@ -1908,7 +2127,7 @@ static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb)
        if (conn) {
                register struct hci_proto *hp;
 
-               hci_conn_enter_active_mode(conn);
+               hci_conn_enter_active_mode(conn, bt_cb(skb)->force_active);
 
                /* Send to upper protocol */
                hp = hci_proto[HCI_PROTO_L2CAP];
@@ -2042,3 +2261,6 @@ static void hci_cmd_task(unsigned long arg)
                }
        }
 }
+
+module_param(enable_smp, bool, 0644);
+MODULE_PARM_DESC(enable_smp, "Enable SMP support (LE only)");