Skip to content

Commit 9fb0db4

Browse files
Paulo AlcantaraSteve French
authored andcommitted
cifs: fix potential deadlock in cache_refresh_path()
Avoid getting DFS referral from an exclusive lock in cache_refresh_path() because the tcon IPC used for getting the referral could be disconnected and thus causing a deadlock as shown below: task A task B ====== ====== cifs_demultiplex_thread() dfs_cache_find() cifs_handle_standard() cache_refresh_path() reconnect_dfs_server() down_write() dfs_cache_noreq_find() get_dfs_referral() down_read() <- deadlock smb2_get_dfs_refer() SMB2_ioctl() cifs_send_recv() compound_send_recv() wait_for_response() where task A cannot wake up task B because it is blocked on down_read() due to the exclusive lock held in cache_refresh_path() and therefore not being able to make progress. Fixes: c9f7110 ("cifs: keep referral server sessions alive") Reviewed-by: Aurélien Aptel <[email protected]> Signed-off-by: Paulo Alcantara (SUSE) <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent 5dc4c99 commit 9fb0db4

File tree

1 file changed

+23
-19
lines changed

1 file changed

+23
-19
lines changed

fs/cifs/dfs_cache.c

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -770,46 +770,50 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
770770
*/
771771
static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, const char *path)
772772
{
773-
int rc;
774-
struct cache_entry *ce;
775773
struct dfs_info3_param *refs = NULL;
774+
struct cache_entry *ce;
776775
int numrefs = 0;
777-
bool newent = false;
776+
int rc;
778777

779778
cifs_dbg(FYI, "%s: search path: %s\n", __func__, path);
780779

781-
down_write(&htable_rw_lock);
780+
down_read(&htable_rw_lock);
782781

783782
ce = lookup_cache_entry(path);
784-
if (!IS_ERR(ce)) {
785-
if (!cache_entry_expired(ce)) {
786-
dump_ce(ce);
787-
up_write(&htable_rw_lock);
788-
return 0;
789-
}
790-
} else {
791-
newent = true;
783+
if (!IS_ERR(ce) && !cache_entry_expired(ce)) {
784+
up_read(&htable_rw_lock);
785+
return 0;
792786
}
787+
/*
788+
* Unlock shared access as we don't want to hold any locks while getting
789+
* a new referral. The @ses used for performing the I/O could be
790+
* reconnecting and it acquires @htable_rw_lock to look up the dfs cache
791+
* in order to failover -- if necessary.
792+
*/
793+
up_read(&htable_rw_lock);
793794

794795
/*
795796
* Either the entry was not found, or it is expired.
796797
* Request a new DFS referral in order to create or update a cache entry.
797798
*/
798799
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
799800
if (rc)
800-
goto out_unlock;
801+
goto out;
801802

802803
dump_refs(refs, numrefs);
803804

804-
if (!newent) {
805-
rc = update_cache_entry_locked(ce, refs, numrefs);
806-
goto out_unlock;
805+
down_write(&htable_rw_lock);
806+
/* Re-check as another task might have it added or refreshed already */
807+
ce = lookup_cache_entry(path);
808+
if (!IS_ERR(ce)) {
809+
if (cache_entry_expired(ce))
810+
rc = update_cache_entry_locked(ce, refs, numrefs);
811+
} else {
812+
rc = add_cache_entry_locked(refs, numrefs);
807813
}
808814

809-
rc = add_cache_entry_locked(refs, numrefs);
810-
811-
out_unlock:
812815
up_write(&htable_rw_lock);
816+
out:
813817
free_dfs_info_array(refs, numrefs);
814818
return rc;
815819
}

0 commit comments

Comments
 (0)