[PATCH 4.14 110/218] dlm: fix missing lkb refcount handling

From: Greg Kroah-Hartman
Date: Mon Jun 13 2022 - 06:50:39 EST

From: Alexander Aring <aahringo@xxxxxxxxxx>

commit 1689c169134f4b5a39156122d799b7dca76d8ddb upstream.

We always call hold_lkb(lkb) if we increment lkb->lkb_wait_count.
So, we always need to call unhold_lkb(lkb) if we decrement
lkb->lkb_wait_count. This patch will add missing unhold_lkb(lkb) if we
decrement lkb->lkb_wait_count. In case of setting lkb->lkb_wait_count to
zero we need to countdown until reaching zero and call unhold_lkb(lkb).
The waiters list unhold_lkb(lkb) can be removed because it's done for
the last lkb_wait_count decrement iteration as it's done in

This issue was discovered by a dlm gfs2 test case which use excessively
dlm_unlock(LKF_CANCEL) feature. Probably the lkb->lkb_wait_count value
never reached above 1 if this feature isn't used and so it was not
discovered before.

The testcase ended in a rsb on the rsb keep data structure with a
refcount of 1 but no lkb was associated with it, which is itself
an invalid behaviour. A side effect of that was a condition in which
the dlm was sending remove messages in a looping behaviour. With this
patch that has not been reproduced.

Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Alexander Aring <aahringo@xxxxxxxxxx>
Signed-off-by: David Teigland <teigland@xxxxxxxxxx>
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
fs/dlm/lock.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)

--- a/fs/dlm/lock.c
+++ b/fs/dlm/lock.c
@@ -1554,6 +1554,7 @@ static int _remove_from_waiters(struct d
lkb->lkb_wait_type = 0;
lkb->lkb_flags &= ~DLM_IFL_OVERLAP_CANCEL;
+ unhold_lkb(lkb);
goto out_del;

@@ -1580,6 +1581,7 @@ static int _remove_from_waiters(struct d
log_error(ls, "remwait error %x reply %d wait_type %d overlap",
lkb->lkb_id, mstype, lkb->lkb_wait_type);
+ unhold_lkb(lkb);
lkb->lkb_wait_type = 0;

@@ -5311,11 +5313,16 @@ int dlm_recover_waiters_post(struct dlm_
lkb->lkb_flags &= ~DLM_IFL_OVERLAP_UNLOCK;
lkb->lkb_flags &= ~DLM_IFL_OVERLAP_CANCEL;
lkb->lkb_wait_type = 0;
- lkb->lkb_wait_count = 0;
+ /* drop all wait_count references we still
+ * hold a reference for this iteration.
+ */
+ while (lkb->lkb_wait_count) {
+ lkb->lkb_wait_count--;
+ unhold_lkb(lkb);
+ }
- unhold_lkb(lkb); /* for waiters list */

if (oc || ou) {
/* do an unlock or cancel instead of resending */