From: Christoph Hellwig <hch@lst.de> Date: 2021-08-11 12:45:35
Hi Josef and Jens,
this series fixed the lock order reversal that is showing up with
nbd lately. The first patch contains the asynchronous deletion
from Hou which is needed as a baseline.
From: Christoph Hellwig <hch@lst.de> Date: 2021-08-11 12:46:32
From: Hou Tao <redacted>
Now open_mutex is used to synchronize partition operations (e.g,
blk_drop_partitions() and blkdev_reread_part()), however it makes
nbd driver broken, because nbd may call del_gendisk() in nbd_release()
or nbd_genl_disconnect() if NBD_CFLAG_DESTROY_ON_DISCONNECT is enabled,
and deadlock occurs, as shown below:
// AB-BA dead-lock
nbd_genl_disconnect blkdev_open
nbd_disconnect_and_put
lock bd_mutex
// last ref
nbd_put
lock nbd_index_mutex
del_gendisk
nbd_open
try lock nbd_index_mutex
try lock bd_mutex
or
// AA dead-lock
nbd_release
lock bd_mutex
nbd_put
try lock bd_mutex
Instead of fixing block layer (e.g, introduce another lock), fixing
the nbd driver to call del_gendisk() in a kworker when
NBD_DESTROY_ON_DISCONNECT is enabled. When NBD_DESTROY_ON_DISCONNECT
is disabled, nbd device will always be destroy through module removal,
and there is no risky of deadlock.
To ensure the reuse of nbd index succeeds, moving the calling of
idr_remove() after del_gendisk(), so if the reused index is not found
in nbd_index_idr, the old disk must have been deleted. And reusing
the existing destroy_complete mechanism to ensure nbd_genl_connect()
will wait for the completion of del_gendisk().
Also adding a new workqueue for nbd removal, so nbd_cleanup()
can ensure all removals complete before exits.
Reported-by: syzbot+0fe7752e52337864d29b@syzkaller.appspotmail.com
Fixes: c76f48eb5c08 ("block: take bd_mutex around delete_partitions in del_gendisk")
Signed-off-by: Hou Tao <redacted>
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/nbd.c | 70 +++++++++++++++++++++++++++++++++++++++------
1 file changed, 61 insertions(+), 9 deletions(-)
@@ -1679,6 +1717,7 @@ static int nbd_dev_add(int index)nbd->tag_set.flags=BLK_MQ_F_SHOULD_MERGE|BLK_MQ_F_BLOCKING;nbd->tag_set.driver_data=nbd;+INIT_WORK(&nbd->remove_work,nbd_dev_remove_work);nbd->destroy_complete=NULL;nbd->backend=NULL;
@@ -2416,7 +2455,14 @@ static int __init nbd_init(void)if(register_blkdev(NBD_MAJOR,"nbd"))return-EIO;+nbd_del_wq=alloc_workqueue("nbd-del",WQ_UNBOUND,0);+if(!nbd_del_wq){+unregister_blkdev(NBD_MAJOR,"nbd");+return-ENOMEM;+}+if(genl_register_family(&nbd_genl_family)){+destroy_workqueue(nbd_del_wq);unregister_blkdev(NBD_MAJOR,"nbd");return-EINVAL;}
@@ -2434,7 +2480,10 @@ static int nbd_exit_cb(int id, void *ptr, void *data)structlist_head*list=(structlist_head*)data;structnbd_device*nbd=ptr;-list_add_tail(&nbd->list,list);+/* Skip nbd that is being removed asynchronously */+if(refcount_read(&nbd->refs))+list_add_tail(&nbd->list,list);+return0;}
@@ -2457,6 +2506,9 @@ static void __exit nbd_cleanup(void)nbd_put(nbd);}+/* Also wait for nbd_dev_remove_work() completes */+destroy_workqueue(nbd_del_wq);+idr_destroy(&nbd_index_idr);genl_unregister_family(&nbd_genl_family);unregister_blkdev(NBD_MAJOR,"nbd");
From: Christoph Hellwig <hch@lst.de> Date: 2021-08-11 12:47:34
Share common code for the synchronous and workqueue based device removal,
and remove the pointless use of refcount_dec_and_mutex_lock.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/nbd.c | 37 +++++++++++++------------------------
1 file changed, 13 insertions(+), 24 deletions(-)
From: Christoph Hellwig <hch@lst.de> Date: 2021-08-11 12:48:46
Fold nbd_del_disk and remove the pointless NULL check on ->disk given
that it is always set for a successfully allocated nbd_device structure.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/nbd.c | 17 +++++------------
1 file changed, 5 insertions(+), 12 deletions(-)
From: Christoph Hellwig <hch@lst.de> Date: 2021-08-11 12:50:15
Return the device we just allocated instead of doing an extra search for
it in the caller.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/nbd.c | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)
@@ -1753,7 +1753,7 @@ static int nbd_dev_add(int index)sprintf(disk->disk_name,"nbd%d",index);add_disk(disk);nbd_total_devices++;-returnindex;+returnnbd;out_free_idr:idr_remove(&nbd_index_idr,index);
@@ -1762,7 +1762,7 @@ static int nbd_dev_add(int index)out_free_nbd:kfree(nbd);out:-returnerr;+returnERR_PTR(err);}staticintfind_free_cb(intid,void*ptr,void*data)
@@ -1848,25 +1848,22 @@ static int nbd_genl_connect(struct sk_buff *skb, struct genl_info *info)if(index==-1){ret=idr_for_each(&nbd_index_idr,&find_free_cb,&nbd);if(ret==0){-intnew_index;-new_index=nbd_dev_add(-1);-if(new_index<0){+nbd=nbd_dev_add(-1);+if(IS_ERR(nbd)){mutex_unlock(&nbd_index_mutex);printk(KERN_ERR"nbd: failed to add new device\n");-returnnew_index;+returnPTR_ERR(nbd);}-nbd=idr_find(&nbd_index_idr,new_index);}}else{nbd=idr_find(&nbd_index_idr,index);if(!nbd){-ret=nbd_dev_add(index);-if(ret<0){+nbd=nbd_dev_add(index);+if(IS_ERR(nbd)){mutex_unlock(&nbd_index_mutex);printk(KERN_ERR"nbd: failed to add new device\n");-returnret;+returnPTR_ERR(nbd);}-nbd=idr_find(&nbd_index_idr,index);}}if(!nbd){
From: Christoph Hellwig <hch@lst.de> Date: 2021-08-11 12:51:50
Use idr_for_each_entry instead of the awkward callback to find an
existing device for the index == -1 case, and de-duplicate the device
allocation if no existing device was found.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/nbd.c | 45 ++++++++++++++-------------------------------
1 file changed, 14 insertions(+), 31 deletions(-)
@@ -1846,31 +1834,26 @@ static int nbd_genl_connect(struct sk_buff *skb, struct genl_info *info)again:mutex_lock(&nbd_index_mutex);if(index==-1){-ret=idr_for_each(&nbd_index_idr,&find_free_cb,&nbd);-if(ret==0){-nbd=nbd_dev_add(-1);-if(IS_ERR(nbd)){-mutex_unlock(&nbd_index_mutex);-printk(KERN_ERR"nbd: failed to add new device\n");-returnPTR_ERR(nbd);+structnbd_device*tmp;+intid;++idr_for_each_entry(&nbd_index_idr,tmp,id){+if(!refcount_read(&tmp->config_refs)){+nbd=tmp;+break;}}}else{nbd=idr_find(&nbd_index_idr,index);-if(!nbd){-nbd=nbd_dev_add(index);-if(IS_ERR(nbd)){-mutex_unlock(&nbd_index_mutex);-printk(KERN_ERR"nbd: failed to add new device\n");-returnPTR_ERR(nbd);-}-}}+if(!nbd){-printk(KERN_ERR"nbd: couldn't find device at index %d\n",-index);-mutex_unlock(&nbd_index_mutex);-return-EINVAL;+nbd=nbd_dev_add(index);+if(IS_ERR(nbd)){+mutex_unlock(&nbd_index_mutex);+pr_err("nbd: failed to add new device\n");+returnPTR_ERR(nbd);+}}if(test_bit(NBD_DESTROY_ON_DISCONNECT,&nbd->flags)&&
From: Christoph Hellwig <hch@lst.de> Date: 2021-08-11 12:52:45
nbd_index_mutex is currently held over add_disk and inside ->open, which
leads to lock order reversals. Refactor the device creation code path
so that nbd_dev_add is called without nbd_index_mutex lock held and
only takes it for the IDR insertation.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/nbd.c | 55 +++++++++++++++++++++++----------------------
1 file changed, 28 insertions(+), 27 deletions(-)
@@ -1847,34 +1849,35 @@ static int nbd_genl_connect(struct sk_buff *skb, struct genl_info *info)nbd=idr_find(&nbd_index_idr,index);}-if(!nbd){-nbd=nbd_dev_add(index);-if(IS_ERR(nbd)){+if(nbd){+if(test_bit(NBD_DESTROY_ON_DISCONNECT,&nbd->flags)&&+test_bit(NBD_DISCONNECT_REQUESTED,&nbd->flags)){+nbd->destroy_complete=&destroy_complete;mutex_unlock(&nbd_index_mutex);-pr_err("nbd: failed to add new device\n");-returnPTR_ERR(nbd);++/* wait until the nbd device is completely destroyed */+wait_for_completion(&destroy_complete);+gotoagain;}-}-if(test_bit(NBD_DESTROY_ON_DISCONNECT,&nbd->flags)&&-test_bit(NBD_DISCONNECT_REQUESTED,&nbd->flags)){-nbd->destroy_complete=&destroy_complete;+if(!refcount_inc_not_zero(&nbd->refs)){+mutex_unlock(&nbd_index_mutex);+if(index==-1)+gotoagain;+pr_err("nbd: device at index %d is going down\n",+index);+return-EINVAL;+}mutex_unlock(&nbd_index_mutex);--/* Wait untill the the nbd stuff is totally destroyed */-wait_for_completion(&destroy_complete);-gotoagain;-}--if(!refcount_inc_not_zero(&nbd->refs)){+}else{mutex_unlock(&nbd_index_mutex);-if(index==-1)-gotoagain;-printk(KERN_ERR"nbd: device at index %d is going down\n",-index);-return-EINVAL;++nbd=nbd_dev_add(index,2);+if(IS_ERR(nbd)){+pr_err("nbd: failed to add new device\n");+returnPTR_ERR(nbd);+}}-mutex_unlock(&nbd_index_mutex);mutex_lock(&nbd->config_lock);if(refcount_read(&nbd->config_refs)){
@@ -2430,10 +2433,8 @@ static int __init nbd_init(void)}nbd_dbg_init();-mutex_lock(&nbd_index_mutex);for(i=0;i<nbds_max;i++)-nbd_dev_add(i);-mutex_unlock(&nbd_index_mutex);+nbd_dev_add(i,1);return0;}
From: Eric Blake <hidden> Date: 2021-08-11 15:57:21
On Wed, Aug 11, 2021 at 02:44:28PM +0200, Christoph Hellwig wrote:
nbd_index_mutex is currently held over add_disk and inside ->open, which
leads to lock order reversals. Refactor the device creation code path
so that nbd_dev_add is called without nbd_index_mutex lock held and
only takes it for the IDR insertation.
From: Josef Bacik <josef@toxicpanda.com> Date: 2021-08-13 14:47:11
On 8/11/21 8:44 AM, Christoph Hellwig wrote:
quoted hunk
nbd_index_mutex is currently held over add_disk and inside ->open, which
leads to lock order reversals. Refactor the device creation code path
so that nbd_dev_add is called without nbd_index_mutex lock held and
only takes it for the IDR insertation.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/nbd.c | 55 +++++++++++++++++++++++----------------------
1 file changed, 28 insertions(+), 27 deletions(-)
@@ -1847,34 +1849,35 @@ static int nbd_genl_connect(struct sk_buff *skb, struct genl_info *info)nbd=idr_find(&nbd_index_idr,index);}-if(!nbd){-nbd=nbd_dev_add(index);-if(IS_ERR(nbd)){+if(nbd){+if(test_bit(NBD_DESTROY_ON_DISCONNECT,&nbd->flags)&&+test_bit(NBD_DISCONNECT_REQUESTED,&nbd->flags)){+nbd->destroy_complete=&destroy_complete;mutex_unlock(&nbd_index_mutex);-pr_err("nbd: failed to add new device\n");-returnPTR_ERR(nbd);++/* wait until the nbd device is completely destroyed */+wait_for_completion(&destroy_complete);+gotoagain;}-}-if(test_bit(NBD_DESTROY_ON_DISCONNECT,&nbd->flags)&&-test_bit(NBD_DISCONNECT_REQUESTED,&nbd->flags)){-nbd->destroy_complete=&destroy_complete;+if(!refcount_inc_not_zero(&nbd->refs)){+mutex_unlock(&nbd_index_mutex);+if(index==-1)+gotoagain;+pr_err("nbd: device at index %d is going down\n",+index);
From: Josef Bacik <josef@toxicpanda.com> Date: 2021-08-13 14:47:52
On 8/11/21 8:44 AM, Christoph Hellwig wrote:
Hi Josef and Jens,
this series fixed the lock order reversal that is showing up with
nbd lately. The first patch contains the asynchronous deletion
from Hou which is needed as a baseline.
Other than the whitespace thing everything looks good, you can add
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Thanks,
Josef
Hi Josef and Jens,
this series fixed the lock order reversal that is showing up with
nbd lately. The first patch contains the asynchronous deletion
from Hou which is needed as a baseline.