Message ID | 1571882902-23966-1-git-send-email-dwysocha@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | cifs: Fix cifsInodeInfo lock_sem deadlock when reconnect occurs | expand |
Reviewed-by me ----- Original Message ----- From: "Dave Wysochanski" <dwysocha@redhat.com> To: linux-cifs@vger.kernel.org Sent: Thursday, 24 October, 2019 12:08:22 PM Subject: [PATCH] cifs: Fix cifsInodeInfo lock_sem deadlock when reconnect occurs There's a deadlock that is possible and can easily be seen with a test where multiple readers open/read/close of the same file and a disruption occurs causing reconnect. The deadlock is due a reader thread inside cifs_strict_readv calling down_read and obtaining lock_sem, and then after reconnect inside cifs_reopen_file calling down_read a second time. If in between the two down_read calls, a down_write comes from another process, deadlock occurs. CPU0 CPU1 ---- ---- cifs_strict_readv() down_read(&cifsi->lock_sem); _cifsFileInfo_put OR cifs_new_fileinfo down_write(&cifsi->lock_sem); cifs_reopen_file() down_read(&cifsi->lock_sem); Fix the above by changing all down_write(lock_sem) calls to down_write_trylock(lock_sem)/msleep() loop, which in turn makes the second down_read call benign since it will never block behind the writer while holding lock_sem. Signed-off-by: Dave Wysochanski <dwysocha@redhat.com> Suggested-by: Ronnie Sahlberg <lsahlber@redhat.com> --- fs/cifs/cifsglob.h | 5 +++++ fs/cifs/file.c | 24 ++++++++++++++++-------- fs/cifs/smb2file.c | 3 ++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 50dfd9049370..2c4a7adbcb4e 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1391,6 +1391,11 @@ struct cifs_writedata { struct cifsInodeInfo { bool can_cache_brlcks; struct list_head llist; /* locks helb by this inode */ + /* + * NOTE: Some code paths call down_read(lock_sem) twice, so + * we must always use use down_write_trylock()/msleep() loop + * to avoid deadlock. + */ struct rw_semaphore lock_sem; /* protect the fields above */ /* BB add in lists for dirty pages i.e. write caching info for oplock */ struct list_head openFileList; diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 5ad15de2bb4f..52454df5ae39 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -306,7 +306,8 @@ struct cifsFileInfo * INIT_LIST_HEAD(&fdlocks->locks); fdlocks->cfile = cfile; cfile->llist = fdlocks; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); list_add(&fdlocks->llist, &cinode->llist); up_write(&cinode->lock_sem); @@ -464,7 +465,8 @@ void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_handler) * Delete any outstanding lock records. We'll lose them when the file * is closed anyway. */ - down_write(&cifsi->lock_sem); + while (!down_write_trylock(&cifsi->lock_sem)) + msleep(125); list_for_each_entry_safe(li, tmp, &cifs_file->llist->locks, llist) { list_del(&li->llist); cifs_del_lock_waiters(li); @@ -1027,7 +1029,8 @@ int cifs_closedir(struct inode *inode, struct file *file) cifs_lock_add(struct cifsFileInfo *cfile, struct cifsLockInfo *lock) { struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry)); - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); list_add_tail(&lock->llist, &cfile->llist->locks); up_write(&cinode->lock_sem); } @@ -1049,7 +1052,8 @@ int cifs_closedir(struct inode *inode, struct file *file) try_again: exist = false; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); exist = cifs_find_lock_conflict(cfile, lock->offset, lock->length, lock->type, lock->flags, &conf_lock, @@ -1072,7 +1076,8 @@ int cifs_closedir(struct inode *inode, struct file *file) (lock->blist.next == &lock->blist)); if (!rc) goto try_again; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); list_del_init(&lock->blist); } @@ -1125,7 +1130,8 @@ int cifs_closedir(struct inode *inode, struct file *file) return rc; try_again: - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); if (!cinode->can_cache_brlcks) { up_write(&cinode->lock_sem); return rc; @@ -1331,7 +1337,8 @@ struct lock_to_push { int rc = 0; /* we are going to update can_cache_brlcks here - need a write access */ - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); if (!cinode->can_cache_brlcks) { up_write(&cinode->lock_sem); return rc; @@ -1522,7 +1529,8 @@ struct lock_to_push { if (!buf) return -ENOMEM; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); for (i = 0; i < 2; i++) { cur = buf; num = 0; diff --git a/fs/cifs/smb2file.c b/fs/cifs/smb2file.c index e6a1fc72018f..61f6cc8d9cc9 100644 --- a/fs/cifs/smb2file.c +++ b/fs/cifs/smb2file.c @@ -145,7 +145,8 @@ cur = buf; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); list_for_each_entry_safe(li, tmp, &cfile->llist->locks, llist) { if (flock->fl_start > li->offset || (flock->fl_start + length) <
чт, 24 окт. 2019 г. в 04:04, Dave Wysochanski <dwysocha@redhat.com>: > > There's a deadlock that is possible and can easily be seen with > a test where multiple readers open/read/close of the same file > and a disruption occurs causing reconnect. The deadlock is due > a reader thread inside cifs_strict_readv calling down_read and > obtaining lock_sem, and then after reconnect inside > cifs_reopen_file calling down_read a second time. If in > between the two down_read calls, a down_write comes from > another process, deadlock occurs. > > CPU0 CPU1 > ---- ---- > cifs_strict_readv() > down_read(&cifsi->lock_sem); > _cifsFileInfo_put > OR > cifs_new_fileinfo > down_write(&cifsi->lock_sem); > cifs_reopen_file() > down_read(&cifsi->lock_sem); > > Fix the above by changing all down_write(lock_sem) calls to > down_write_trylock(lock_sem)/msleep() loop, which in turn > makes the second down_read call benign since it will never > block behind the writer while holding lock_sem. > > Signed-off-by: Dave Wysochanski <dwysocha@redhat.com> > Suggested-by: Ronnie Sahlberg <lsahlber@redhat.com> > --- > fs/cifs/cifsglob.h | 5 +++++ > fs/cifs/file.c | 24 ++++++++++++++++-------- > fs/cifs/smb2file.c | 3 ++- > 3 files changed, 23 insertions(+), 9 deletions(-) > > diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h > index 50dfd9049370..2c4a7adbcb4e 100644 > --- a/fs/cifs/cifsglob.h > +++ b/fs/cifs/cifsglob.h > @@ -1391,6 +1391,11 @@ struct cifs_writedata { > struct cifsInodeInfo { > bool can_cache_brlcks; > struct list_head llist; /* locks helb by this inode */ > + /* > + * NOTE: Some code paths call down_read(lock_sem) twice, so > + * we must always use use down_write_trylock()/msleep() loop > + * to avoid deadlock. > + */ > struct rw_semaphore lock_sem; /* protect the fields above */ > /* BB add in lists for dirty pages i.e. write caching info for oplock */ > struct list_head openFileList; > diff --git a/fs/cifs/file.c b/fs/cifs/file.c > index 5ad15de2bb4f..52454df5ae39 100644 > --- a/fs/cifs/file.c > +++ b/fs/cifs/file.c > @@ -306,7 +306,8 @@ struct cifsFileInfo * > INIT_LIST_HEAD(&fdlocks->locks); > fdlocks->cfile = cfile; > cfile->llist = fdlocks; > - down_write(&cinode->lock_sem); > + while (!down_write_trylock(&cinode->lock_sem)) > + msleep(125); > list_add(&fdlocks->llist, &cinode->llist); > up_write(&cinode->lock_sem); > > @@ -464,7 +465,8 @@ void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_handler) > * Delete any outstanding lock records. We'll lose them when the file > * is closed anyway. > */ > - down_write(&cifsi->lock_sem); > + while (!down_write_trylock(&cifsi->lock_sem)) > + msleep(125); Please wrap the above code into a helper function e.g. cifs_acquire_lock_sem(struct cifsInodeInfo *cinode) or any other name you like. Other than that it looks good. -- Best regards, Pavel Shilovsky
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 50dfd9049370..2c4a7adbcb4e 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1391,6 +1391,11 @@ struct cifs_writedata { struct cifsInodeInfo { bool can_cache_brlcks; struct list_head llist; /* locks helb by this inode */ + /* + * NOTE: Some code paths call down_read(lock_sem) twice, so + * we must always use use down_write_trylock()/msleep() loop + * to avoid deadlock. + */ struct rw_semaphore lock_sem; /* protect the fields above */ /* BB add in lists for dirty pages i.e. write caching info for oplock */ struct list_head openFileList; diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 5ad15de2bb4f..52454df5ae39 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -306,7 +306,8 @@ struct cifsFileInfo * INIT_LIST_HEAD(&fdlocks->locks); fdlocks->cfile = cfile; cfile->llist = fdlocks; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); list_add(&fdlocks->llist, &cinode->llist); up_write(&cinode->lock_sem); @@ -464,7 +465,8 @@ void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_handler) * Delete any outstanding lock records. We'll lose them when the file * is closed anyway. */ - down_write(&cifsi->lock_sem); + while (!down_write_trylock(&cifsi->lock_sem)) + msleep(125); list_for_each_entry_safe(li, tmp, &cifs_file->llist->locks, llist) { list_del(&li->llist); cifs_del_lock_waiters(li); @@ -1027,7 +1029,8 @@ int cifs_closedir(struct inode *inode, struct file *file) cifs_lock_add(struct cifsFileInfo *cfile, struct cifsLockInfo *lock) { struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry)); - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); list_add_tail(&lock->llist, &cfile->llist->locks); up_write(&cinode->lock_sem); } @@ -1049,7 +1052,8 @@ int cifs_closedir(struct inode *inode, struct file *file) try_again: exist = false; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); exist = cifs_find_lock_conflict(cfile, lock->offset, lock->length, lock->type, lock->flags, &conf_lock, @@ -1072,7 +1076,8 @@ int cifs_closedir(struct inode *inode, struct file *file) (lock->blist.next == &lock->blist)); if (!rc) goto try_again; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); list_del_init(&lock->blist); } @@ -1125,7 +1130,8 @@ int cifs_closedir(struct inode *inode, struct file *file) return rc; try_again: - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); if (!cinode->can_cache_brlcks) { up_write(&cinode->lock_sem); return rc; @@ -1331,7 +1337,8 @@ struct lock_to_push { int rc = 0; /* we are going to update can_cache_brlcks here - need a write access */ - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); if (!cinode->can_cache_brlcks) { up_write(&cinode->lock_sem); return rc; @@ -1522,7 +1529,8 @@ struct lock_to_push { if (!buf) return -ENOMEM; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); for (i = 0; i < 2; i++) { cur = buf; num = 0; diff --git a/fs/cifs/smb2file.c b/fs/cifs/smb2file.c index e6a1fc72018f..61f6cc8d9cc9 100644 --- a/fs/cifs/smb2file.c +++ b/fs/cifs/smb2file.c @@ -145,7 +145,8 @@ cur = buf; - down_write(&cinode->lock_sem); + while (!down_write_trylock(&cinode->lock_sem)) + msleep(125); list_for_each_entry_safe(li, tmp, &cfile->llist->locks, llist) { if (flock->fl_start > li->offset || (flock->fl_start + length) <
There's a deadlock that is possible and can easily be seen with a test where multiple readers open/read/close of the same file and a disruption occurs causing reconnect. The deadlock is due a reader thread inside cifs_strict_readv calling down_read and obtaining lock_sem, and then after reconnect inside cifs_reopen_file calling down_read a second time. If in between the two down_read calls, a down_write comes from another process, deadlock occurs. CPU0 CPU1 ---- ---- cifs_strict_readv() down_read(&cifsi->lock_sem); _cifsFileInfo_put OR cifs_new_fileinfo down_write(&cifsi->lock_sem); cifs_reopen_file() down_read(&cifsi->lock_sem); Fix the above by changing all down_write(lock_sem) calls to down_write_trylock(lock_sem)/msleep() loop, which in turn makes the second down_read call benign since it will never block behind the writer while holding lock_sem. Signed-off-by: Dave Wysochanski <dwysocha@redhat.com> Suggested-by: Ronnie Sahlberg <lsahlber@redhat.com> --- fs/cifs/cifsglob.h | 5 +++++ fs/cifs/file.c | 24 ++++++++++++++++-------- fs/cifs/smb2file.c | 3 ++- 3 files changed, 23 insertions(+), 9 deletions(-)