diff options
author | Lars Ellenberg <lars.ellenberg@linbit.com> | 2014-02-11 02:57:18 -0500 |
---|---|---|
committer | Philipp Reisner <philipp.reisner@linbit.com> | 2014-07-10 12:34:54 -0400 |
commit | ba3c6fb87d2df008eed8faaf01bb198e512fa72f (patch) | |
tree | 85faa78b91dac8b5eadcbacab2781c1d7a26046f | |
parent | 2ed912e9d35b4d7d766a530f5dd14513a3ab9f64 (diff) |
drbd: close race when detaching from disk
BUG: unable to handle kernel NULL pointer dereference at 0000000000000058
IP: bd_release+0x21/0x70
Process drbd_w_t7146
Call Trace:
close_bdev_exclusive
drbd_free_ldev [drbd]
drbd_ldev_destroy [drbd]
w_after_state_ch [drbd]
Race probably went like this:
state.disk = D_FAILED
... first one to hit zero during D_FAILED:
put_ldev() /* ----------------> 0 */
i = atomic_dec_return()
if (i == 0)
if (state.disk == D_FAILED)
schedule_work(go_diskless)
/* 1 <------ */ get_ldev_if_state()
go_diskless()
do_some_pre_cleanup() corresponding put_ldev():
force_state(D_DISKLESS) /* 0 <------ */ i = atomic_dec_return()
if (i == 0)
atomic_inc() /* ---------> 1 */
state.disk = D_DISKLESS
schedule_work(after_state_ch) /* execution pre-empted by IRQ ? */
after_state_ch()
put_ldev()
i = atomic_dec_return() /* 0 */
if (i == 0)
if (state.disk == D_DISKLESS) if (state.disk == D_DISKLESS)
drbd_ldev_destroy() drbd_ldev_destroy();
Trying to fix this by checking the disk state *before* the
atomic_dec_return(), which implies memory barriers, and by inserting
extra memory barriers around the state assignment in __drbd_set_state().
Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
-rw-r--r-- | drivers/block/drbd/drbd_int.h | 9 | ||||
-rw-r--r-- | drivers/block/drbd/drbd_state.c | 11 |
2 files changed, 14 insertions, 6 deletions
diff --git a/drivers/block/drbd/drbd_int.h b/drivers/block/drbd/drbd_int.h index a16f9ae3c98a..a0ffc19ccf0e 100644 --- a/drivers/block/drbd/drbd_int.h +++ b/drivers/block/drbd/drbd_int.h | |||
@@ -1946,6 +1946,11 @@ static inline bool is_sync_state(enum drbd_conns connection_state) | |||
1946 | 1946 | ||
1947 | static inline void put_ldev(struct drbd_device *device) | 1947 | static inline void put_ldev(struct drbd_device *device) |
1948 | { | 1948 | { |
1949 | enum drbd_disk_state ds = device->state.disk; | ||
1950 | /* We must check the state *before* the atomic_dec becomes visible, | ||
1951 | * or we have a theoretical race where someone hitting zero, | ||
1952 | * while state still D_FAILED, will then see D_DISKLESS in the | ||
1953 | * condition below and calling into destroy, where he must not, yet. */ | ||
1949 | int i = atomic_dec_return(&device->local_cnt); | 1954 | int i = atomic_dec_return(&device->local_cnt); |
1950 | 1955 | ||
1951 | /* This may be called from some endio handler, | 1956 | /* This may be called from some endio handler, |
@@ -1954,10 +1959,10 @@ static inline void put_ldev(struct drbd_device *device) | |||
1954 | __release(local); | 1959 | __release(local); |
1955 | D_ASSERT(device, i >= 0); | 1960 | D_ASSERT(device, i >= 0); |
1956 | if (i == 0) { | 1961 | if (i == 0) { |
1957 | if (device->state.disk == D_DISKLESS) | 1962 | if (ds == D_DISKLESS) |
1958 | /* even internal references gone, safe to destroy */ | 1963 | /* even internal references gone, safe to destroy */ |
1959 | drbd_ldev_destroy(device); | 1964 | drbd_ldev_destroy(device); |
1960 | if (device->state.disk == D_FAILED) { | 1965 | if (ds == D_FAILED) { |
1961 | /* all application IO references gone. */ | 1966 | /* all application IO references gone. */ |
1962 | if (!test_and_set_bit(GO_DISKLESS, &device->flags)) | 1967 | if (!test_and_set_bit(GO_DISKLESS, &device->flags)) |
1963 | drbd_queue_work(&first_peer_device(device)->connection->sender_work, | 1968 | drbd_queue_work(&first_peer_device(device)->connection->sender_work, |
diff --git a/drivers/block/drbd/drbd_state.c b/drivers/block/drbd/drbd_state.c index 1bddd6cf8ac7..6629f4668102 100644 --- a/drivers/block/drbd/drbd_state.c +++ b/drivers/block/drbd/drbd_state.c | |||
@@ -958,7 +958,6 @@ __drbd_set_state(struct drbd_device *device, union drbd_state ns, | |||
958 | enum drbd_state_rv rv = SS_SUCCESS; | 958 | enum drbd_state_rv rv = SS_SUCCESS; |
959 | enum sanitize_state_warnings ssw; | 959 | enum sanitize_state_warnings ssw; |
960 | struct after_state_chg_work *ascw; | 960 | struct after_state_chg_work *ascw; |
961 | bool did_remote, should_do_remote; | ||
962 | 961 | ||
963 | os = drbd_read_state(device); | 962 | os = drbd_read_state(device); |
964 | 963 | ||
@@ -1010,18 +1009,22 @@ __drbd_set_state(struct drbd_device *device, union drbd_state ns, | |||
1010 | (os.disk != D_DISKLESS && ns.disk == D_DISKLESS)) | 1009 | (os.disk != D_DISKLESS && ns.disk == D_DISKLESS)) |
1011 | atomic_inc(&device->local_cnt); | 1010 | atomic_inc(&device->local_cnt); |
1012 | 1011 | ||
1013 | did_remote = drbd_should_do_remote(device->state); | ||
1014 | if (!is_sync_state(os.conn) && is_sync_state(ns.conn)) | 1012 | if (!is_sync_state(os.conn) && is_sync_state(ns.conn)) |
1015 | clear_bit(RS_DONE, &device->flags); | 1013 | clear_bit(RS_DONE, &device->flags); |
1016 | 1014 | ||
1015 | /* changes to local_cnt and device flags should be visible before | ||
1016 | * changes to state, which again should be visible before anything else | ||
1017 | * depending on that change happens. */ | ||
1018 | smp_wmb(); | ||
1017 | device->state.i = ns.i; | 1019 | device->state.i = ns.i; |
1018 | should_do_remote = drbd_should_do_remote(device->state); | ||
1019 | device->resource->susp = ns.susp; | 1020 | device->resource->susp = ns.susp; |
1020 | device->resource->susp_nod = ns.susp_nod; | 1021 | device->resource->susp_nod = ns.susp_nod; |
1021 | device->resource->susp_fen = ns.susp_fen; | 1022 | device->resource->susp_fen = ns.susp_fen; |
1023 | smp_wmb(); | ||
1022 | 1024 | ||
1023 | /* put replicated vs not-replicated requests in seperate epochs */ | 1025 | /* put replicated vs not-replicated requests in seperate epochs */ |
1024 | if (did_remote != should_do_remote) | 1026 | if (drbd_should_do_remote((union drbd_dev_state)os.i) != |
1027 | drbd_should_do_remote((union drbd_dev_state)ns.i)) | ||
1025 | start_new_tl_epoch(connection); | 1028 | start_new_tl_epoch(connection); |
1026 | 1029 | ||
1027 | if (os.disk == D_ATTACHING && ns.disk >= D_NEGOTIATING) | 1030 | if (os.disk == D_ATTACHING && ns.disk >= D_NEGOTIATING) |