UBI: Fix stale pointers in ubi->lookuptbl
authorRichard Weinberger <richard@nod.at>
Mon, 10 Nov 2014 15:16:23 +0000 (16:16 +0100)
committerRichard Weinberger <richard@nod.at>
Thu, 26 Mar 2015 21:46:04 +0000 (22:46 +0100)
In some error paths the WL sub-system gives up on a PEB
and frees it's ubi_wl_entry struct but does not set
the entry in ubi->lookuptbl to NULL.
Fastmap can stumble over such a stale pointer as it uses
ubi->lookuptbl to find all PEBs.

Fix this by introducing a new helper function which free()s
a WL entry and removes the reference from the lookup table.

Signed-off-by: Richard Weinberger <richard@nod.at>
drivers/mtd/ubi/wl.c

index 609b16d45406bac83f3c848101de167ec438c2d8..83848324daa27772907d567a4af7cc09a348d7e7 100644 (file)
@@ -215,6 +215,20 @@ static void wl_tree_add(struct ubi_wl_entry *e, struct rb_root *root)
        rb_insert_color(&e->u.rb, root);
 }
 
+/**
+ * wl_tree_destroy - destroy a wear-leveling entry.
+ * @ubi: UBI device description object
+ * @e: the wear-leveling entry to add
+ *
+ * This function destroys a wear leveling entry and removes
+ * the reference from the lookup table.
+ */
+static void wl_entry_destroy(struct ubi_device *ubi, struct ubi_wl_entry *e)
+{
+       ubi->lookuptbl[e->pnum] = NULL;
+       kmem_cache_free(ubi_wl_entry_slab, e);
+}
+
 /**
  * do_work - do one pending work.
  * @ubi: UBI device description object
@@ -1258,7 +1272,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
        err = do_sync_erase(ubi, e1, vol_id, lnum, 0);
        if (err) {
                if (e2)
-                       kmem_cache_free(ubi_wl_entry_slab, e2);
+                       wl_entry_destroy(ubi, e2);
                goto out_ro;
        }
 
@@ -1326,8 +1340,8 @@ out_error:
        spin_unlock(&ubi->wl_lock);
 
        ubi_free_vid_hdr(ubi, vid_hdr);
-       kmem_cache_free(ubi_wl_entry_slab, e1);
-       kmem_cache_free(ubi_wl_entry_slab, e2);
+       wl_entry_destroy(ubi, e1);
+       wl_entry_destroy(ubi, e2);
 
 out_ro:
        ubi_ro_mode(ubi);
@@ -1469,7 +1483,7 @@ static int erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk,
        if (shutdown) {
                dbg_wl("cancel erasure of PEB %d EC %d", pnum, e->ec);
                kfree(wl_wrk);
-               kmem_cache_free(ubi_wl_entry_slab, e);
+               wl_entry_destroy(ubi, e);
                return 0;
        }
 
@@ -1515,7 +1529,7 @@ static int erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk,
                return err;
        }
 
-       kmem_cache_free(ubi_wl_entry_slab, e);
+       wl_entry_destroy(ubi, e);
        if (err != -EIO)
                /*
                 * If this is not %-EIO, we have no idea what to do. Scheduling
@@ -1807,9 +1821,10 @@ int ubi_wl_flush(struct ubi_device *ubi, int vol_id, int lnum)
 
 /**
  * tree_destroy - destroy an RB-tree.
+ * @ubi: UBI device description object
  * @root: the root of the tree to destroy
  */
-static void tree_destroy(struct rb_root *root)
+static void tree_destroy(struct ubi_device *ubi, struct rb_root *root)
 {
        struct rb_node *rb;
        struct ubi_wl_entry *e;
@@ -1831,7 +1846,7 @@ static void tree_destroy(struct rb_root *root)
                                        rb->rb_right = NULL;
                        }
 
-                       kmem_cache_free(ubi_wl_entry_slab, e);
+                       wl_entry_destroy(ubi, e);
                }
        }
 }
@@ -1962,7 +1977,7 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai)
                ubi_assert(!ubi_is_fm_block(ubi, e->pnum));
                ubi->lookuptbl[e->pnum] = e;
                if (schedule_erase(ubi, e, aeb->vol_id, aeb->lnum, 0)) {
-                       kmem_cache_free(ubi_wl_entry_slab, e);
+                       wl_entry_destroy(ubi, e);
                        goto out_free;
                }
 
@@ -2056,9 +2071,9 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai)
 
 out_free:
        shutdown_work(ubi);
-       tree_destroy(&ubi->used);
-       tree_destroy(&ubi->free);
-       tree_destroy(&ubi->scrub);
+       tree_destroy(ubi, &ubi->used);
+       tree_destroy(ubi, &ubi->free);
+       tree_destroy(ubi, &ubi->scrub);
        kfree(ubi->lookuptbl);
        return err;
 }
@@ -2075,7 +2090,7 @@ static void protection_queue_destroy(struct ubi_device *ubi)
        for (i = 0; i < UBI_PROT_QUEUE_LEN; ++i) {
                list_for_each_entry_safe(e, tmp, &ubi->pq[i], u.list) {
                        list_del(&e->u.list);
-                       kmem_cache_free(ubi_wl_entry_slab, e);
+                       wl_entry_destroy(ubi, e);
                }
        }
 }
@@ -2107,10 +2122,10 @@ void ubi_wl_close(struct ubi_device *ubi)
        ubi_fastmap_close(ubi);
        shutdown_work(ubi);
        protection_queue_destroy(ubi);
-       tree_destroy(&ubi->used);
-       tree_destroy(&ubi->erroneous);
-       tree_destroy(&ubi->free);
-       tree_destroy(&ubi->scrub);
+       tree_destroy(ubi, &ubi->used);
+       tree_destroy(ubi, &ubi->erroneous);
+       tree_destroy(ubi, &ubi->free);
+       tree_destroy(ubi, &ubi->scrub);
        kfree(ubi->lookuptbl);
 }