Skip to contentSkip to conversation

mm/hmm: potential deadlock in nonblocking code

PATCH Superseded Dan Carpenter 3 participants 5 messages Feb 4, 2019 – Feb 11, 2019

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 mm/hmm: potential deadlock in nonblocking code

There is a deadlock bug when these functions are used in nonblocking mode. The else side is only meant to be taken in when the code is used in blocking mode. But the way it's written now, if we manage to take the lock without blocking then we try to take it a second time in the else statement which leads to a deadlock.

Fixes a3402cb621c1 ("mm/hmm: improve driver API to work and wait over a range")
---
 mm/hmm.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)
mm/hmm.c +10 -6
index e14e0aa4d2cb..3b97bb087b28 100644
@@ -207,9 +207,11 @@ static int hmm_invalidate_range_start(struct mmu_notifier *mn,
 	update.event = HMM_UPDATE_INVALIDATE;
 	update.blockable = nrange->blockable;
 
-	if (!nrange->blockable && !mutex_trylock(&hmm->lock)) {
-		ret = -EAGAIN;
-		goto out;
+	if (!nrange->blockable) {
+		if (!mutex_trylock(&hmm->lock)) {
+			ret = -EAGAIN;
+			goto out;
+		}
 	} else
 		mutex_lock(&hmm->lock);
 	hmm->notifiers++;
@@ -222,9 +224,11 @@ static int hmm_invalidate_range_start(struct mmu_notifier *mn,
 	mutex_unlock(&hmm->lock);
 
 
-	if (!nrange->blockable && !down_read_trylock(&hmm->mirrors_sem)) {
-		ret = -EAGAIN;
-		goto out;
+	if (!nrange->blockable) {
+		if (!down_read_trylock(&hmm->mirrors_sem)) {
+			ret = -EAGAIN;
+			goto out;
+		}
 	} else
 		down_read(&hmm->mirrors_sem);
 	list_for_each_entry(mirror, &hmm->mirrors, list) {
Signature · 1 line
2.17.1
#1 lore raw

mm/hmm.c static int hmm_invalidate_range_start(struct mmu_notifier *mn, · line 216

Quoted 11 lines · Dan Carpenter
mm/hmm.c +5 -3
-	if (!nrange->blockable && !mutex_trylock(&hmm->lock)) {
-		ret = -EAGAIN;
-		goto out;
+	if (!nrange->blockable) {
+		if (!mutex_trylock(&hmm->lock)) {
+			ret = -EAGAIN;
+			goto out;
+		}
 	} else
 		mutex_lock(&hmm->lock);

I think this would be more readable written as:

	ret = -EAGAIN;
	if (nrange->blockable)
		mutex_lock(&hmm->lock);
	else if (!mutex_trylock(&hmm->lock))
		goto out;

but it'll be up to Jerome.

#2 lore raw
Quoted 15 lines · Matthew Wilcox

mm/hmm.c static int hmm_invalidate_range_start(struct mmu_notifier *mn, · line 216

Quoted 11 lines · Dan Carpenter
mm/hmm.c +5 -3
-	if (!nrange->blockable && !mutex_trylock(&hmm->lock)) {
-		ret = -EAGAIN;
-		goto out;
+	if (!nrange->blockable) {
+		if (!mutex_trylock(&hmm->lock)) {
+			ret = -EAGAIN;
+			goto out;
+		}
 	} else
 		mutex_lock(&hmm->lock);

I think this would be more readable written as:

Quoted 5 lines
	ret = -EAGAIN;
	if (nrange->blockable)
		mutex_lock(&hmm->lock);
	else if (!mutex_trylock(&hmm->lock))
		goto out;

I agree, that does look nicer. I will resend.

regards,
dan carpenter

#3 lore raw

PATCH v2 1/1 mm/hmm: potential deadlock in nonblocking code

In reply to Dan Carpenter

There is a deadlock bug when these functions are used in nonblocking mode.

The else side of the if/else statement is only meant to be taken in when the code is used in blocking mode. But, unfortunately, the way the code is now, if we're in non-blocking mode and we succeed in taking the lock then we do the else statement. The else side tries to take lock a second time which results in a deadlock.

Fixes a3402cb621c1 ("mm/hmm: improve driver API to work and wait over a range")

---
V2: improve the style and tweak the commit description

 hmm.c |   15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)
mm/hmm.c +8 -7
index e14e0aa4d2cb..3c9781037918 100644
@@ -207,11 +207,12 @@ static int hmm_invalidate_range_start(struct mmu_notifier *mn,
 	update.event = HMM_UPDATE_INVALIDATE;
 	update.blockable = nrange->blockable;
 
-	if (!nrange->blockable && !mutex_trylock(&hmm->lock)) {
+	if (nrange->blockable)
+		mutex_lock(&hmm->lock);
+	else if (!mutex_trylock(&hmm->lock)) {
 		ret = -EAGAIN;
 		goto out;
-	} else
-		mutex_lock(&hmm->lock);
+	}
 	hmm->notifiers++;
 	list_for_each_entry(range, &hmm->ranges, list) {
 		if (update.end < range->start || update.start >= range->end)
@@ -221,12 +222,12 @@ static int hmm_invalidate_range_start(struct mmu_notifier *mn,
 	}
 	mutex_unlock(&hmm->lock);
 
-
-	if (!nrange->blockable && !down_read_trylock(&hmm->mirrors_sem)) {
+	if (nrange->blockable)
+		down_read(&hmm->mirrors_sem);
+	else if (!down_read_trylock(&hmm->mirrors_sem)) {
 		ret = -EAGAIN;
 		goto out;
-	} else
-		down_read(&hmm->mirrors_sem);
+	}
 	list_for_each_entry(mirror, &hmm->mirrors, list) {
 		int ret;
 
Reviewed-by on v2 1/1
#4 lore raw
Quoted 11 lines · Dan Carpenter

There is a deadlock bug when these functions are used in nonblocking mode.

The else side of the if/else statement is only meant to be taken in when the code is used in blocking mode. But, unfortunately, the way the code is now, if we're in non-blocking mode and we succeed in taking the lock then we do the else statement. The else side tries to take lock a second time which results in a deadlock.

Fixes a3402cb621c1 ("mm/hmm: improve driver API to work and wait over a range")
Quoted 43 lines

---
V2: improve the style and tweak the commit description

 hmm.c |   15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)
mm/hmm.c +8 -7
index e14e0aa4d2cb..3c9781037918 100644
@@ -207,11 +207,12 @@ static int hmm_invalidate_range_start(struct mmu_notifier *mn,
 	update.event = HMM_UPDATE_INVALIDATE;
 	update.blockable = nrange->blockable;
 
-	if (!nrange->blockable && !mutex_trylock(&hmm->lock)) {
+	if (nrange->blockable)
+		mutex_lock(&hmm->lock);
+	else if (!mutex_trylock(&hmm->lock)) {
 		ret = -EAGAIN;
 		goto out;
-	} else
-		mutex_lock(&hmm->lock);
+	}
 	hmm->notifiers++;
 	list_for_each_entry(range, &hmm->ranges, list) {
 		if (update.end < range->start || update.start >= range->end)
@@ -221,12 +222,12 @@ static int hmm_invalidate_range_start(struct mmu_notifier *mn,
 	}
 	mutex_unlock(&hmm->lock);
 
-
-	if (!nrange->blockable && !down_read_trylock(&hmm->mirrors_sem)) {
+	if (nrange->blockable)
+		down_read(&hmm->mirrors_sem);
+	else if (!down_read_trylock(&hmm->mirrors_sem)) {
 		ret = -EAGAIN;
 		goto out;
-	} else
-		down_read(&hmm->mirrors_sem);
+	}
 	list_for_each_entry(mirror, &hmm->mirrors, list) {
 		int ret;