File fix-potential-double-call-of-drbd_backing_devic.patch of Package drbd.13405

From 3f30589d3c31fc4e89b75d8321db2672925ecc55 Mon Sep 17 00:00:00 2001
From: Lars Ellenberg <lars.ellenberg@linbit.com>
Date: Fri, 26 Apr 2019 15:46:50 +0200
Subject: [PATCH] drbd: fix potential double call of drbd_backing_device_free

During down/detach, we schedule "DESTROY_DISK" on the DRBD resource worker.
But we also schedule a generic work drbd_device_finalize_work_fn() from the
drbd_destroy_device() destructor.

Both call
	drbd_backing_dev_free(device, device->ldev);
	device->ldev = NULL;

If due to some race both run at "exactly" the same time,
we get a "double free", two calls to bd_unlink_disk_holder(), and
| kernfs: can not remove 'dm-0', no directory
| WARNING: CPU: 0 PID: 4468 at fs/kernfs/dir.c:1504 kernfs_remove_by_name_ns

So we better call that only once.

The backing device life time is unrelated to device life time,
so the correct place is when detaching from the backing device,
not during device destruction.

But we need to wait for the worker to actually process the DESTROY_DISK.

Once (interruptible) from adm_detach(), so a not interrupted
administrative detach will only return after the backing device was
in fact released.

And once in adm_del_minor(), before unregistering the device object,
because otherwise with a quick "detach, del_minor" (aka: "down")
we have a race[*] where the worker might not find the device object
anymore that was scheduled to have its backing device detached.

This wait needs to be uninterruptible.

[*] that race would lead to drbd_backing_device_free() never being called, and
| vgchange -an scratch
|  /sys/block/drbd100/dev: fopen failed: No such file or directory
|  /dev/scratch/s0_00: failed to find associated device structure for holder /dev/drbd100.
|  Logical volume scratch/s0_00 is used by another device.
|  Can't deactivate volume group "scratch" with 1 open logical volume(s)
---
 drbd/drbd_main.c | 5 +----
 drbd/drbd_nl.c   | 8 +++++++-
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/drbd/drbd_main.c b/drbd/drbd_main.c
index 69e1d15b..e57ea696 100644
--- a/drbd/drbd_main.c
+++ b/drbd/drbd_main.c
@@ -2943,13 +2943,10 @@ static void drbd_device_finalize_work_fn(struct work_struct *work)
 {
 	struct drbd_device *device = container_of(work, struct drbd_device, finalize_work);
 	struct drbd_resource *resource = device->resource;
-	
+
 	if (device->this_bdev)
 		bdput(device->this_bdev);
 
-	drbd_backing_dev_free(device, device->ldev);
-	device->ldev = NULL;
-
 	if (device->bitmap) {
 		drbd_bm_free(device->bitmap);
 		device->bitmap = NULL;
diff --git a/drbd/drbd_nl.c b/drbd/drbd_nl.c
index a77f9297..bccc6de2 100644
--- a/drbd/drbd_nl.c
+++ b/drbd/drbd_nl.c
@@ -3124,8 +3124,11 @@ static enum drbd_state_rv adm_detach(struct drbd_device *device, bool force, boo
 	drbd_resume_io(device);
 	ret = wait_event_interruptible(device->misc_wait,
 			get_disk_state(device) != D_DETACHING);
-	if (retcode >= SS_SUCCESS)
+	if (retcode >= SS_SUCCESS) {
+		/* wait for completion of drbd_ldev_destroy() */
+		wait_event_interruptible(device->misc_wait, !test_bit(GOING_DISKLESS, &device->flags));
 		drbd_cleanup_device(device);
+	}
 	else
 		device->device_conf.intentional_diskless = false;
 	if (retcode == SS_IS_DISKLESS)
@@ -5856,6 +5859,9 @@ static enum drbd_ret_code adm_del_minor(struct drbd_device *device)
 		stable_change_repl_state(peer_device, L_OFF,
 					 CS_VERBOSE | CS_WAIT_COMPLETE);
 
+	/* If the worker still has to find it to call drbd_ldev_destroy(),
+	 * we must not unregister the device yet. */
+	wait_event(device->misc_wait, !test_bit(GOING_DISKLESS, &device->flags));
 	/*
 	 * Flush the resource work queue to make sure that no more events like
 	 * state change notifications for this device are queued: we want the
-- 
2.16.4