net: nfc: Fix use-after-free caused by nfc_llcp_find_local
[linux-2.6-microblaze.git] / net / nfc / llcp_core.c
index a27e184..f60e424 100644 (file)
@@ -17,6 +17,8 @@
 static u8 llcp_magic[3] = {0x46, 0x66, 0x6d};
 
 static LIST_HEAD(llcp_devices);
+/* Protects llcp_devices list */
+static DEFINE_SPINLOCK(llcp_devices_lock);
 
 static void nfc_llcp_rx_skb(struct nfc_llcp_local *local, struct sk_buff *skb);
 
@@ -141,7 +143,7 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device,
        write_unlock(&local->raw_sockets.lock);
 }
 
-struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local)
+static struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local)
 {
        kref_get(&local->ref);
 
@@ -169,7 +171,6 @@ static void local_release(struct kref *ref)
 
        local = container_of(ref, struct nfc_llcp_local, ref);
 
-       list_del(&local->list);
        local_cleanup(local);
        kfree(local);
 }
@@ -282,12 +283,33 @@ static void nfc_llcp_sdreq_timer(struct timer_list *t)
 struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev)
 {
        struct nfc_llcp_local *local;
+       struct nfc_llcp_local *res = NULL;
 
+       spin_lock(&llcp_devices_lock);
        list_for_each_entry(local, &llcp_devices, list)
-               if (local->dev == dev)
+               if (local->dev == dev) {
+                       res = nfc_llcp_local_get(local);
+                       break;
+               }
+       spin_unlock(&llcp_devices_lock);
+
+       return res;
+}
+
+static struct nfc_llcp_local *nfc_llcp_remove_local(struct nfc_dev *dev)
+{
+       struct nfc_llcp_local *local, *tmp;
+
+       spin_lock(&llcp_devices_lock);
+       list_for_each_entry_safe(local, tmp, &llcp_devices, list)
+               if (local->dev == dev) {
+                       list_del(&local->list);
+                       spin_unlock(&llcp_devices_lock);
                        return local;
+               }
+       spin_unlock(&llcp_devices_lock);
 
-       pr_debug("No device found\n");
+       pr_warn("Shutting down device not found\n");
 
        return NULL;
 }
@@ -608,12 +630,15 @@ u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len)
 
        *general_bytes_len = local->gb_len;
 
+       nfc_llcp_local_put(local);
+
        return local->gb;
 }
 
 int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len)
 {
        struct nfc_llcp_local *local;
+       int err;
 
        if (gb_len < 3 || gb_len > NFC_MAX_GT_LEN)
                return -EINVAL;
@@ -630,12 +655,16 @@ int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len)
 
        if (memcmp(local->remote_gb, llcp_magic, 3)) {
                pr_err("MAC does not support LLCP\n");
-               return -EINVAL;
+               err = -EINVAL;
+               goto out;
        }
 
-       return nfc_llcp_parse_gb_tlv(local,
+       err = nfc_llcp_parse_gb_tlv(local,
                                     &local->remote_gb[3],
                                     local->remote_gb_len - 3);
+out:
+       nfc_llcp_local_put(local);
+       return err;
 }
 
 static u8 nfc_llcp_dsap(const struct sk_buff *pdu)
@@ -1517,6 +1546,8 @@ int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb)
 
        __nfc_llcp_recv(local, skb);
 
+       nfc_llcp_local_put(local);
+
        return 0;
 }
 
@@ -1533,6 +1564,8 @@ void nfc_llcp_mac_is_down(struct nfc_dev *dev)
 
        /* Close and purge all existing sockets */
        nfc_llcp_socket_release(local, true, 0);
+
+       nfc_llcp_local_put(local);
 }
 
 void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,
@@ -1558,6 +1591,8 @@ void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,
                mod_timer(&local->link_timer,
                          jiffies + msecs_to_jiffies(local->remote_lto));
        }
+
+       nfc_llcp_local_put(local);
 }
 
 int nfc_llcp_register_device(struct nfc_dev *ndev)
@@ -1608,7 +1643,7 @@ int nfc_llcp_register_device(struct nfc_dev *ndev)
 
 void nfc_llcp_unregister_device(struct nfc_dev *dev)
 {
-       struct nfc_llcp_local *local = nfc_llcp_find_local(dev);
+       struct nfc_llcp_local *local = nfc_llcp_remove_local(dev);
 
        if (local == NULL) {
                pr_debug("No such device\n");