Commit fdd514e16bb2531c0c61ae8a1f87740ce217f630

Authored by Tejun Heo
Committed by Jens Axboe
1 parent c3af54afba

block: make disk_block_events() properly wait for work cancellation

disk_block_events() should guarantee that the event work is not in
flight on return and once blocked it shouldn't issue further
cancellations.

Because there was no synchronization between the first blocker doing
cancel_delayed_work_sync() and the following blockers, the following
blockers could finish before cancellation was complete, which broke
both guarantees - event work could be in flight and cancellation could
happen after return.

This bug triggered WARN_ON_ONCE() in disk_clear_events() reported in
bug#34662.

  https://bugzilla.kernel.org/show_bug.cgi?id=34662

Fix it by adding an outer mutex which protects both block count
manipulation and work cancellation.

-v2: Use outer mutex instead of bit waitqueue per Linus.

Signed-off-by: Tejun Heo <tj@kernel.org>
Tested-by: Sitsofe Wheeler <sitsofe@yahoo.com>
Reported-by: Sitsofe Wheeler <sitsofe@yahoo.com>
Reported-by: Borislav Petkov <bp@alien8.de>
Reported-by: Meelis Roos <mroos@linux.ee>
Reported-by: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Jens Axboe <axboe@kernel.dk>
Cc: Kay Sievers <kay.sievers@vrfy.org>
Signed-off-by: Jens Axboe <jaxboe@fusionio.com>

Showing 1 changed file with 10 additions and 0 deletions Side-by-side Diff

... ... @@ -1371,6 +1371,7 @@
1371 1371 struct gendisk *disk; /* the associated disk */
1372 1372 spinlock_t lock;
1373 1373  
  1374 + struct mutex block_mutex; /* protects blocking */
1374 1375 int block; /* event blocking depth */
1375 1376 unsigned int pending; /* events already sent out */
1376 1377 unsigned int clearing; /* events being cleared */
1377 1378  
... ... @@ -1438,12 +1439,20 @@
1438 1439 if (!ev)
1439 1440 return;
1440 1441  
  1442 + /*
  1443 + * Outer mutex ensures that the first blocker completes canceling
  1444 + * the event work before further blockers are allowed to finish.
  1445 + */
  1446 + mutex_lock(&ev->block_mutex);
  1447 +
1441 1448 spin_lock_irqsave(&ev->lock, flags);
1442 1449 cancel = !ev->block++;
1443 1450 spin_unlock_irqrestore(&ev->lock, flags);
1444 1451  
1445 1452 if (cancel)
1446 1453 cancel_delayed_work_sync(&disk->ev->dwork);
  1454 +
  1455 + mutex_unlock(&ev->block_mutex);
1447 1456 }
1448 1457  
1449 1458 static void __disk_unblock_events(struct gendisk *disk, bool check_now)
... ... @@ -1751,6 +1760,7 @@
1751 1760 INIT_LIST_HEAD(&ev->node);
1752 1761 ev->disk = disk;
1753 1762 spin_lock_init(&ev->lock);
  1763 + mutex_init(&ev->block_mutex);
1754 1764 ev->block = 1;
1755 1765 ev->poll_msecs = -1;
1756 1766 INIT_DELAYED_WORK(&ev->dwork, disk_events_workfn);