diff options
| author | NeilBrown <neilb@suse.de> | 2011-09-21 01:30:20 -0400 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@suse.de> | 2011-10-16 17:14:53 -0400 |
| commit | 7f3b5ef8184a929f56293d6c7a88f426c7b74558 (patch) | |
| tree | 817b10c76033ad291f9e5d5d56259855016ed190 /drivers/md | |
| parent | 57fb87f0a640cd7b2e7cde21fd7c13138f8ef75b (diff) | |
md: Avoid waking up a thread after it has been freed.
commit 01f96c0a9922cd9919baf9d16febdf7016177a12 upstream.
Two related problems:
1/ some error paths call "md_unregister_thread(mddev->thread)"
without subsequently clearing ->thread. A subsequent call
to mddev_unlock will try to wake the thread, and crash.
2/ Most calls to md_wakeup_thread are protected against the thread
disappeared either by:
- holding the ->mutex
- having an active request, so something else must be keeping
the array active.
However mddev_unlock calls md_wakeup_thread after dropping the
mutex and without any certainty of an active request, so the
->thread could theoretically disappear.
So we need a spinlock to provide some protections.
So change md_unregister_thread to take a pointer to the thread
pointer, and ensure that it always does the required locking, and
clears the pointer properly.
Reported-by: "Moshe Melnikov" <moshe@zadarastorage.com>
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/md')
| -rw-r--r-- | drivers/md/md.c | 22 | ||||
| -rw-r--r-- | drivers/md/md.h | 2 | ||||
| -rw-r--r-- | drivers/md/multipath.c | 3 | ||||
| -rw-r--r-- | drivers/md/raid1.c | 3 | ||||
| -rw-r--r-- | drivers/md/raid10.c | 5 | ||||
| -rw-r--r-- | drivers/md/raid5.c | 6 |
6 files changed, 26 insertions, 15 deletions
diff --git a/drivers/md/md.c b/drivers/md/md.c index 85540829d64..bc8342812d0 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c | |||
| @@ -61,6 +61,11 @@ | |||
| 61 | static void autostart_arrays(int part); | 61 | static void autostart_arrays(int part); |
| 62 | #endif | 62 | #endif |
| 63 | 63 | ||
| 64 | /* pers_list is a list of registered personalities protected | ||
| 65 | * by pers_lock. | ||
| 66 | * pers_lock does extra service to protect accesses to | ||
| 67 | * mddev->thread when the mutex cannot be held. | ||
| 68 | */ | ||
| 64 | static LIST_HEAD(pers_list); | 69 | static LIST_HEAD(pers_list); |
| 65 | static DEFINE_SPINLOCK(pers_lock); | 70 | static DEFINE_SPINLOCK(pers_lock); |
| 66 | 71 | ||
| @@ -690,7 +695,12 @@ static void mddev_unlock(mddev_t * mddev) | |||
| 690 | } else | 695 | } else |
| 691 | mutex_unlock(&mddev->reconfig_mutex); | 696 | mutex_unlock(&mddev->reconfig_mutex); |
| 692 | 697 | ||
| 698 | /* was we've dropped the mutex we need a spinlock to | ||
| 699 | * make sur the thread doesn't disappear | ||
| 700 | */ | ||
| 701 | spin_lock(&pers_lock); | ||
| 693 | md_wakeup_thread(mddev->thread); | 702 | md_wakeup_thread(mddev->thread); |
| 703 | spin_unlock(&pers_lock); | ||
| 694 | } | 704 | } |
| 695 | 705 | ||
| 696 | static mdk_rdev_t * find_rdev_nr(mddev_t *mddev, int nr) | 706 | static mdk_rdev_t * find_rdev_nr(mddev_t *mddev, int nr) |
| @@ -6186,11 +6196,18 @@ mdk_thread_t *md_register_thread(void (*run) (mddev_t *), mddev_t *mddev, | |||
| 6186 | return thread; | 6196 | return thread; |
| 6187 | } | 6197 | } |
| 6188 | 6198 | ||
| 6189 | void md_unregister_thread(mdk_thread_t *thread) | 6199 | void md_unregister_thread(mdk_thread_t **threadp) |
| 6190 | { | 6200 | { |
| 6201 | mdk_thread_t *thread = *threadp; | ||
| 6191 | if (!thread) | 6202 | if (!thread) |
| 6192 | return; | 6203 | return; |
| 6193 | dprintk("interrupting MD-thread pid %d\n", task_pid_nr(thread->tsk)); | 6204 | dprintk("interrupting MD-thread pid %d\n", task_pid_nr(thread->tsk)); |
| 6205 | /* Locking ensures that mddev_unlock does not wake_up a | ||
| 6206 | * non-existent thread | ||
| 6207 | */ | ||
| 6208 | spin_lock(&pers_lock); | ||
| 6209 | *threadp = NULL; | ||
| 6210 | spin_unlock(&pers_lock); | ||
| 6194 | 6211 | ||
| 6195 | kthread_stop(thread->tsk); | 6212 | kthread_stop(thread->tsk); |
| 6196 | kfree(thread); | 6213 | kfree(thread); |
| @@ -7125,8 +7142,7 @@ static void reap_sync_thread(mddev_t *mddev) | |||
| 7125 | mdk_rdev_t *rdev; | 7142 | mdk_rdev_t *rdev; |
| 7126 | 7143 | ||
| 7127 | /* resync has finished, collect result */ | 7144 | /* resync has finished, collect result */ |
| 7128 | md_unregister_thread(mddev->sync_thread); | 7145 | md_unregister_thread(&mddev->sync_thread); |
| 7129 | mddev->sync_thread = NULL; | ||
| 7130 | if (!test_bit(MD_RECOVERY_INTR, &mddev->recovery) && | 7146 | if (!test_bit(MD_RECOVERY_INTR, &mddev->recovery) && |
| 7131 | !test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery)) { | 7147 | !test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery)) { |
| 7132 | /* success...*/ | 7148 | /* success...*/ |
diff --git a/drivers/md/md.h b/drivers/md/md.h index 1c26c7a08ae..ce4e328049d 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h | |||
| @@ -475,7 +475,7 @@ extern int register_md_personality(struct mdk_personality *p); | |||
| 475 | extern int unregister_md_personality(struct mdk_personality *p); | 475 | extern int unregister_md_personality(struct mdk_personality *p); |
| 476 | extern mdk_thread_t * md_register_thread(void (*run) (mddev_t *mddev), | 476 | extern mdk_thread_t * md_register_thread(void (*run) (mddev_t *mddev), |
| 477 | mddev_t *mddev, const char *name); | 477 | mddev_t *mddev, const char *name); |
| 478 | extern void md_unregister_thread(mdk_thread_t *thread); | 478 | extern void md_unregister_thread(mdk_thread_t **threadp); |
| 479 | extern void md_wakeup_thread(mdk_thread_t *thread); | 479 | extern void md_wakeup_thread(mdk_thread_t *thread); |
| 480 | extern void md_check_recovery(mddev_t *mddev); | 480 | extern void md_check_recovery(mddev_t *mddev); |
| 481 | extern void md_write_start(mddev_t *mddev, struct bio *bi); | 481 | extern void md_write_start(mddev_t *mddev, struct bio *bi); |
diff --git a/drivers/md/multipath.c b/drivers/md/multipath.c index 3535c23af28..d5b5fb30017 100644 --- a/drivers/md/multipath.c +++ b/drivers/md/multipath.c | |||
| @@ -514,8 +514,7 @@ static int multipath_stop (mddev_t *mddev) | |||
| 514 | { | 514 | { |
| 515 | multipath_conf_t *conf = mddev->private; | 515 | multipath_conf_t *conf = mddev->private; |
| 516 | 516 | ||
| 517 | md_unregister_thread(mddev->thread); | 517 | md_unregister_thread(&mddev->thread); |
| 518 | mddev->thread = NULL; | ||
| 519 | blk_sync_queue(mddev->queue); /* the unplug fn references 'conf'*/ | 518 | blk_sync_queue(mddev->queue); /* the unplug fn references 'conf'*/ |
| 520 | mempool_destroy(conf->pool); | 519 | mempool_destroy(conf->pool); |
| 521 | kfree(conf->multipaths); | 520 | kfree(conf->multipaths); |
diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index f7431b6d844..3a9e59fe7ad 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c | |||
| @@ -2045,8 +2045,7 @@ static int stop(mddev_t *mddev) | |||
| 2045 | raise_barrier(conf); | 2045 | raise_barrier(conf); |
| 2046 | lower_barrier(conf); | 2046 | lower_barrier(conf); |
| 2047 | 2047 | ||
| 2048 | md_unregister_thread(mddev->thread); | 2048 | md_unregister_thread(&mddev->thread); |
| 2049 | mddev->thread = NULL; | ||
| 2050 | if (conf->r1bio_pool) | 2049 | if (conf->r1bio_pool) |
| 2051 | mempool_destroy(conf->r1bio_pool); | 2050 | mempool_destroy(conf->r1bio_pool); |
| 2052 | kfree(conf->mirrors); | 2051 | kfree(conf->mirrors); |
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 6e846688962..17cb6ab6230 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c | |||
| @@ -2331,7 +2331,7 @@ static int run(mddev_t *mddev) | |||
| 2331 | return 0; | 2331 | return 0; |
| 2332 | 2332 | ||
| 2333 | out_free_conf: | 2333 | out_free_conf: |
| 2334 | md_unregister_thread(mddev->thread); | 2334 | md_unregister_thread(&mddev->thread); |
| 2335 | if (conf->r10bio_pool) | 2335 | if (conf->r10bio_pool) |
| 2336 | mempool_destroy(conf->r10bio_pool); | 2336 | mempool_destroy(conf->r10bio_pool); |
| 2337 | safe_put_page(conf->tmppage); | 2337 | safe_put_page(conf->tmppage); |
| @@ -2349,8 +2349,7 @@ static int stop(mddev_t *mddev) | |||
| 2349 | raise_barrier(conf, 0); | 2349 | raise_barrier(conf, 0); |
| 2350 | lower_barrier(conf); | 2350 | lower_barrier(conf); |
| 2351 | 2351 | ||
| 2352 | md_unregister_thread(mddev->thread); | 2352 | md_unregister_thread(&mddev->thread); |
| 2353 | mddev->thread = NULL; | ||
| 2354 | blk_sync_queue(mddev->queue); /* the unplug fn references 'conf'*/ | 2353 | blk_sync_queue(mddev->queue); /* the unplug fn references 'conf'*/ |
| 2355 | if (conf->r10bio_pool) | 2354 | if (conf->r10bio_pool) |
| 2356 | mempool_destroy(conf->r10bio_pool); | 2355 | mempool_destroy(conf->r10bio_pool); |
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index b72edf35ec5..2581ba12735 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c | |||
| @@ -5162,8 +5162,7 @@ static int run(mddev_t *mddev) | |||
| 5162 | 5162 | ||
| 5163 | return 0; | 5163 | return 0; |
| 5164 | abort: | 5164 | abort: |
| 5165 | md_unregister_thread(mddev->thread); | 5165 | md_unregister_thread(&mddev->thread); |
| 5166 | mddev->thread = NULL; | ||
| 5167 | if (conf) { | 5166 | if (conf) { |
| 5168 | print_raid5_conf(conf); | 5167 | print_raid5_conf(conf); |
| 5169 | free_conf(conf); | 5168 | free_conf(conf); |
| @@ -5177,8 +5176,7 @@ static int stop(mddev_t *mddev) | |||
| 5177 | { | 5176 | { |
| 5178 | raid5_conf_t *conf = mddev->private; | 5177 | raid5_conf_t *conf = mddev->private; |
| 5179 | 5178 | ||
| 5180 | md_unregister_thread(mddev->thread); | 5179 | md_unregister_thread(&mddev->thread); |
| 5181 | mddev->thread = NULL; | ||
| 5182 | if (mddev->queue) | 5180 | if (mddev->queue) |
| 5183 | mddev->queue->backing_dev_info.congested_fn = NULL; | 5181 | mddev->queue->backing_dev_info.congested_fn = NULL; |
| 5184 | free_conf(conf); | 5182 | free_conf(conf); |
