Skip to content

Commit

Permalink
cifs: fix potential deadlock in direct reclaim
Browse files Browse the repository at this point in the history
The srv_mutex is used during writeback so cifs should ensure that
allocations done when that mutex is held are done with GFP_NOFS, to
avoid having direct reclaim ending up waiting for the same mutex and
causing a deadlock.  This is detected by lockdep with the splat below:

 ======================================================
 WARNING: possible circular locking dependency detected
 5.18.0 linux-sunxi#70 Not tainted
 ------------------------------------------------------
 kswapd0/49 is trying to acquire lock:
 ffff8880195782e0 (&tcp_ses->srv_mutex){+.+.}-{3:3}, at: compound_send_recv

 but task is already holding lock:
 ffffffffa98e66c0 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat

 which lock already depends on the new lock.

 the existing dependency chain (in reverse order) is:

 -> jwrdegoede#1 (fs_reclaim){+.+.}-{0:0}:
        fs_reclaim_acquire
        kmem_cache_alloc_trace
        __request_module
        crypto_alg_mod_lookup
        crypto_alloc_tfm_node
        crypto_alloc_shash
        cifs_alloc_hash
        smb311_crypto_shash_allocate
        smb311_update_preauth_hash
        compound_send_recv
        cifs_send_recv
        SMB2_negotiate
        smb2_negotiate
        cifs_negotiate_protocol
        cifs_get_smb_ses
        cifs_mount
        cifs_smb3_do_mount
        smb3_get_tree
        vfs_get_tree
        path_mount
        __x64_sys_mount
        do_syscall_64
        entry_SYSCALL_64_after_hwframe

 -> #0 (&tcp_ses->srv_mutex){+.+.}-{3:3}:
        __lock_acquire
        lock_acquire
        __mutex_lock
        mutex_lock_nested
        compound_send_recv
        cifs_send_recv
        SMB2_write
        smb2_sync_write
        cifs_write
        cifs_writepage_locked
        cifs_writepage
        shrink_page_list
        shrink_lruvec
        shrink_node
        balance_pgdat
        kswapd
        kthread
        ret_from_fork

 other info that might help us debug this:

  Possible unsafe locking scenario:

        CPU0                    CPU1
        ----                    ----
   lock(fs_reclaim);
                                lock(&tcp_ses->srv_mutex);
                                lock(fs_reclaim);
   lock(&tcp_ses->srv_mutex);

  *** DEADLOCK ***

 1 lock held by kswapd0/49:
  #0: ffffffffa98e66c0 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat

 stack backtrace:
 CPU: 2 PID: 49 Comm: kswapd0 Not tainted 5.18.0 linux-sunxi#70
 Call Trace:
  <TASK>
  dump_stack_lvl
  dump_stack
  print_circular_bug.cold
  check_noncircular
  __lock_acquire
  lock_acquire
  __mutex_lock
  mutex_lock_nested
  compound_send_recv
  cifs_send_recv
  SMB2_write
  smb2_sync_write
  cifs_write
  cifs_writepage_locked
  cifs_writepage
  shrink_page_list
  shrink_lruvec
  shrink_node
  balance_pgdat
  kswapd
  kthread
  ret_from_fork
  </TASK>

Fix this by using the memalloc_nofs_save/restore APIs around the places
where the srv_mutex is held.  Do this in a wrapper function for the
lock/unlock of the srv_mutex, and rename the srv_mutex to avoid missing
call sites in the conversion.

Note that there is another lockdep warning involving internal crypto
locks, which was masked by this problem and is visible after this fix,
see the discussion in this thread:

 https://lore.kernel.org/all/20220523123755.GA13668@axis.com/

Link: https://lore.kernel.org/r/CANT5p=rqcYfYMVHirqvdnnca4Mo+JQSw5Qu12v=kPfpk5yhhmg@mail.gmail.com/
Reported-by: Shyam Prasad N <nspmangalore@gmail.com>
Suggested-by: Lars Persson <larper@axis.com>
Reviewed-by: Ronnie Sahlberg <lsahlber@redhat.com>
Reviewed-by: Enzo Matsumiya <ematsumiya@suse.de>
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
  • Loading branch information
vwax authored and Steve French committed Jun 1, 2022
1 parent f66f8b9 commit cc391b6
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 53 deletions.
4 changes: 2 additions & 2 deletions fs/cifs/cifs_swn.c
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *a
int ret = 0;

/* Store the reconnect address */
mutex_lock(&tcon->ses->server->srv_mutex);
cifs_server_lock(tcon->ses->server);
if (cifs_sockaddr_equal(&tcon->ses->server->dstaddr, addr))
goto unlock;

Expand Down Expand Up @@ -501,7 +501,7 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *a
cifs_signal_cifsd_for_reconnect(tcon->ses->server, false);

unlock:
mutex_unlock(&tcon->ses->server->srv_mutex);
cifs_server_unlock(tcon->ses->server);

return ret;
}
Expand Down
8 changes: 4 additions & 4 deletions fs/cifs/cifsencrypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,9 @@ int cifs_verify_signature(struct smb_rqst *rqst,
cpu_to_le32(expected_sequence_number);
cifs_pdu->Signature.Sequence.Reserved = 0;

mutex_lock(&server->srv_mutex);
cifs_server_lock(server);
rc = cifs_calc_signature(rqst, server, what_we_think_sig_should_be);
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);

if (rc)
return rc;
Expand Down Expand Up @@ -626,7 +626,7 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp)

memcpy(ses->auth_key.response + baselen, tiblob, tilen);

mutex_lock(&ses->server->srv_mutex);
cifs_server_lock(ses->server);

rc = cifs_alloc_hash("hmac(md5)",
&ses->server->secmech.hmacmd5,
Expand Down Expand Up @@ -678,7 +678,7 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp)
cifs_dbg(VFS, "%s: Could not generate md5 hash\n", __func__);

unlock:
mutex_unlock(&ses->server->srv_mutex);
cifs_server_unlock(ses->server);
setup_ntlmv2_rsp_ret:
kfree(tiblob);

Expand Down
20 changes: 19 additions & 1 deletion fs/cifs/cifsglob.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <linux/mempool.h>
#include <linux/workqueue.h>
#include <linux/utsname.h>
#include <linux/sched/mm.h>
#include <linux/netfs.h>
#include "cifs_fs_sb.h"
#include "cifsacl.h"
Expand Down Expand Up @@ -628,7 +629,8 @@ struct TCP_Server_Info {
unsigned int in_flight; /* number of requests on the wire to server */
unsigned int max_in_flight; /* max number of requests that were on wire */
spinlock_t req_lock; /* protect the two values above */
struct mutex srv_mutex;
struct mutex _srv_mutex;
unsigned int nofs_flag;
struct task_struct *tsk;
char server_GUID[16];
__u16 sec_mode;
Expand Down Expand Up @@ -743,6 +745,22 @@ struct TCP_Server_Info {
#endif
};

static inline void cifs_server_lock(struct TCP_Server_Info *server)
{
unsigned int nofs_flag = memalloc_nofs_save();

mutex_lock(&server->_srv_mutex);
server->nofs_flag = nofs_flag;
}

static inline void cifs_server_unlock(struct TCP_Server_Info *server)
{
unsigned int nofs_flag = server->nofs_flag;

mutex_unlock(&server->_srv_mutex);
memalloc_nofs_restore(nofs_flag);
}

struct cifs_credits {
unsigned int value;
unsigned int instance;
Expand Down
26 changes: 13 additions & 13 deletions fs/cifs/connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ static void cifs_resolve_server(struct work_struct *work)
struct TCP_Server_Info *server = container_of(work,
struct TCP_Server_Info, resolve.work);

mutex_lock(&server->srv_mutex);
cifs_server_lock(server);

/*
* Resolve the hostname again to make sure that IP address is up-to-date.
Expand All @@ -159,7 +159,7 @@ static void cifs_resolve_server(struct work_struct *work)
__func__, rc);
}

mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
}

/*
Expand Down Expand Up @@ -267,7 +267,7 @@ cifs_abort_connection(struct TCP_Server_Info *server)

/* do not want to be sending data on a socket we are freeing */
cifs_dbg(FYI, "%s: tearing down socket\n", __func__);
mutex_lock(&server->srv_mutex);
cifs_server_lock(server);
if (server->ssocket) {
cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n", server->ssocket->state,
server->ssocket->flags);
Expand Down Expand Up @@ -296,7 +296,7 @@ cifs_abort_connection(struct TCP_Server_Info *server)
mid->mid_flags |= MID_DELETED;
}
spin_unlock(&GlobalMid_Lock);
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);

cifs_dbg(FYI, "%s: issuing mid callbacks\n", __func__);
list_for_each_entry_safe(mid, nmid, &retry_list, qhead) {
Expand All @@ -306,9 +306,9 @@ cifs_abort_connection(struct TCP_Server_Info *server)
}

if (cifs_rdma_enabled(server)) {
mutex_lock(&server->srv_mutex);
cifs_server_lock(server);
smbd_destroy(server);
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
}
}

Expand Down Expand Up @@ -359,7 +359,7 @@ static int __cifs_reconnect(struct TCP_Server_Info *server,

do {
try_to_freeze();
mutex_lock(&server->srv_mutex);
cifs_server_lock(server);

if (!cifs_swn_set_server_dstaddr(server)) {
/* resolve the hostname again to make sure that IP address is up-to-date */
Expand All @@ -372,7 +372,7 @@ static int __cifs_reconnect(struct TCP_Server_Info *server,
else
rc = generic_ip_connect(server);
if (rc) {
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
msleep(3000);
} else {
Expand All @@ -383,7 +383,7 @@ static int __cifs_reconnect(struct TCP_Server_Info *server,
server->tcpStatus = CifsNeedNegotiate;
spin_unlock(&cifs_tcp_ses_lock);
cifs_swn_reset_server_dstaddr(server);
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
}
} while (server->tcpStatus == CifsNeedReconnect);
Expand Down Expand Up @@ -488,12 +488,12 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)

do {
try_to_freeze();
mutex_lock(&server->srv_mutex);
cifs_server_lock(server);

rc = reconnect_target_unlocked(server, &tl, &target_hint);
if (rc) {
/* Failed to reconnect socket */
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
msleep(3000);
continue;
Expand All @@ -510,7 +510,7 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)
server->tcpStatus = CifsNeedNegotiate;
spin_unlock(&cifs_tcp_ses_lock);
cifs_swn_reset_server_dstaddr(server);
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
} while (server->tcpStatus == CifsNeedReconnect);

Expand Down Expand Up @@ -1565,7 +1565,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
init_waitqueue_head(&tcp_ses->response_q);
init_waitqueue_head(&tcp_ses->request_q);
INIT_LIST_HEAD(&tcp_ses->pending_mid_q);
mutex_init(&tcp_ses->srv_mutex);
mutex_init(&tcp_ses->_srv_mutex);
memcpy(tcp_ses->workstation_RFC1001_name,
ctx->source_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL);
memcpy(tcp_ses->server_RFC1001_name,
Expand Down
4 changes: 2 additions & 2 deletions fs/cifs/dfs_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -1327,9 +1327,9 @@ static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, c
cifs_dbg(VFS, "%s: failed to convert address \'%s\'. skip address matching.\n",
__func__, ip);
} else {
mutex_lock(&server->srv_mutex);
cifs_server_lock(server);
match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, &sa);
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
}

kfree(ip);
Expand Down
6 changes: 3 additions & 3 deletions fs/cifs/sess.c
Original file line number Diff line number Diff line change
Expand Up @@ -1120,14 +1120,14 @@ sess_establish_session(struct sess_data *sess_data)
struct cifs_ses *ses = sess_data->ses;
struct TCP_Server_Info *server = sess_data->server;

mutex_lock(&server->srv_mutex);
cifs_server_lock(server);
if (!server->session_estab) {
if (server->sign) {
server->session_key.response =
kmemdup(ses->auth_key.response,
ses->auth_key.len, GFP_KERNEL);
if (!server->session_key.response) {
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
return -ENOMEM;
}
server->session_key.len =
Expand All @@ -1136,7 +1136,7 @@ sess_establish_session(struct sess_data *sess_data)
server->sequence_number = 0x2;
server->session_estab = true;
}
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);

cifs_dbg(FYI, "CIFS session established successfully\n");
return 0;
Expand Down
6 changes: 3 additions & 3 deletions fs/cifs/smb1ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ send_nt_cancel(struct TCP_Server_Info *server, struct smb_rqst *rqst,
in_buf->WordCount = 0;
put_bcc(0, in_buf);

mutex_lock(&server->srv_mutex);
cifs_server_lock(server);
rc = cifs_sign_smb(in_buf, server, &mid->sequence_number);
if (rc) {
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
return rc;
}

Expand All @@ -55,7 +55,7 @@ send_nt_cancel(struct TCP_Server_Info *server, struct smb_rqst *rqst,
if (rc < 0)
server->sequence_number--;

mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);

cifs_dbg(FYI, "issued NT_CANCEL for mid %u, rc = %d\n",
get_mid(in_buf), rc);
Expand Down
6 changes: 3 additions & 3 deletions fs/cifs/smb2pdu.c
Original file line number Diff line number Diff line change
Expand Up @@ -1369,21 +1369,21 @@ SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
struct cifs_ses *ses = sess_data->ses;
struct TCP_Server_Info *server = sess_data->server;

mutex_lock(&server->srv_mutex);
cifs_server_lock(server);
if (server->ops->generate_signingkey) {
rc = server->ops->generate_signingkey(ses, server);
if (rc) {
cifs_dbg(FYI,
"SMB3 session key generation failed\n");
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
return rc;
}
}
if (!server->session_estab) {
server->sequence_number = 0x2;
server->session_estab = true;
}
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);

cifs_dbg(FYI, "SMB2/3 session established successfully\n");
return rc;
Expand Down
4 changes: 2 additions & 2 deletions fs/cifs/smbdirect.c
Original file line number Diff line number Diff line change
Expand Up @@ -1382,9 +1382,9 @@ void smbd_destroy(struct TCP_Server_Info *server)
log_rdma_event(INFO, "freeing mr list\n");
wake_up_interruptible_all(&info->wait_mr);
while (atomic_read(&info->mr_used_count)) {
mutex_unlock(&server->srv_mutex);
cifs_server_unlock(server);
msleep(1000);
mutex_lock(&server->srv_mutex);
cifs_server_lock(server);
}
destroy_mr_list(info);

Expand Down
Loading

0 comments on commit cc391b6

Please sign in to comment.