diff options
author | Lars Ellenberg <lars.ellenberg@linbit.com> | 2012-06-19 03:40:00 -0400 |
---|---|---|
committer | Philipp Reisner <philipp.reisner@linbit.com> | 2012-07-24 08:15:16 -0400 |
commit | c12e9c8964215aaf2b5dcd06048444c2b672f0b9 (patch) | |
tree | a13c5561ad0325ca247f4c1d9d0b7770da0c64bb /drivers/block | |
parent | 63a6d0bb3dd69afedb2b2952eb1d1e8340c11d0d (diff) |
drbd: fix potential access after free
Occasionally, if we disconnect, we triggered this assert:
block drbd7: ASSERT FAILED tl_hash[27] == c30b0f04, expected NULL
hlist_del() happens only on master bio completion.
We used to wait for pending IO to complete before freeing tl_hash
on disconnect. We no longer do so, since we learned to "freeze"
IO on disconnect.
If the local disk is too slow, we may reach C_STANDALONE early,
and there are still some requests pending locally when we call
drbd_free_tl_hash().
If we now free the tl_hash, and later the local IO completion completes
the master bio, which then does hlist_del() and clobbers freed memory.
Do hlist_del_init() and hlist_add_fake() before kfree(tl_hash),
so the hlist_del() on master bio completion is harmless.
Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
Diffstat (limited to 'drivers/block')
-rw-r--r-- | drivers/block/drbd/drbd_receiver.c | 17 |
1 files changed, 12 insertions, 5 deletions
diff --git a/drivers/block/drbd/drbd_receiver.c b/drivers/block/drbd/drbd_receiver.c index ea4836e0ae98..83d99133f94b 100644 --- a/drivers/block/drbd/drbd_receiver.c +++ b/drivers/block/drbd/drbd_receiver.c | |||
@@ -3801,11 +3801,18 @@ void drbd_free_tl_hash(struct drbd_conf *mdev) | |||
3801 | mdev->ee_hash = NULL; | 3801 | mdev->ee_hash = NULL; |
3802 | mdev->ee_hash_s = 0; | 3802 | mdev->ee_hash_s = 0; |
3803 | 3803 | ||
3804 | /* paranoia code */ | 3804 | /* We may not have had the chance to wait for all locally pending |
3805 | for (h = mdev->tl_hash; h < mdev->tl_hash + mdev->tl_hash_s; h++) | 3805 | * application requests. The hlist_add_fake() prevents access after |
3806 | if (h->first) | 3806 | * free on master bio completion. */ |
3807 | dev_err(DEV, "ASSERT FAILED tl_hash[%u] == %p, expected NULL\n", | 3807 | for (h = mdev->tl_hash; h < mdev->tl_hash + mdev->tl_hash_s; h++) { |
3808 | (int)(h - mdev->tl_hash), h->first); | 3808 | struct drbd_request *req; |
3809 | struct hlist_node *pos, *n; | ||
3810 | hlist_for_each_entry_safe(req, pos, n, h, collision) { | ||
3811 | hlist_del_init(&req->collision); | ||
3812 | hlist_add_fake(&req->collision); | ||
3813 | } | ||
3814 | } | ||
3815 | |||
3809 | kfree(mdev->tl_hash); | 3816 | kfree(mdev->tl_hash); |
3810 | mdev->tl_hash = NULL; | 3817 | mdev->tl_hash = NULL; |
3811 | mdev->tl_hash_s = 0; | 3818 | mdev->tl_hash_s = 0; |