CIFS: Fix a possible memory corruption during reconnect
[firefly-linux-kernel-4.4.55.git] / fs / cifs / connect.c
index 812a8cb07c632b855c7e4deee62d3b1eaf626e3f..5d59f25521ce12a25bd02c062756412bf234f7ab 100644 (file)
@@ -52,6 +52,9 @@
 #include "nterr.h"
 #include "rfc1002pdu.h"
 #include "fscache.h"
+#ifdef CONFIG_CIFS_SMB2
+#include "smb2proto.h"
+#endif
 
 #define CIFS_PORT 445
 #define RFC1001_PORT 139
@@ -2113,8 +2116,8 @@ cifs_find_tcp_session(struct smb_vol *vol)
        return NULL;
 }
 
-static void
-cifs_put_tcp_session(struct TCP_Server_Info *server)
+void
+cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
 {
        struct task_struct *task;
 
@@ -2131,6 +2134,19 @@ cifs_put_tcp_session(struct TCP_Server_Info *server)
 
        cancel_delayed_work_sync(&server->echo);
 
+#ifdef CONFIG_CIFS_SMB2
+       if (from_reconnect)
+               /*
+                * Avoid deadlock here: reconnect work calls
+                * cifs_put_tcp_session() at its end. Need to be sure
+                * that reconnect work does nothing with server pointer after
+                * that step.
+                */
+               cancel_delayed_work(&server->reconnect);
+       else
+               cancel_delayed_work_sync(&server->reconnect);
+#endif
+
        spin_lock(&GlobalMid_Lock);
        server->tcpStatus = CifsExiting;
        spin_unlock(&GlobalMid_Lock);
@@ -2195,6 +2211,10 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
        INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
        INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
        INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
+#ifdef CONFIG_CIFS_SMB2
+       INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
+       mutex_init(&tcp_ses->reconnect_mutex);
+#endif
        memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr,
               sizeof(tcp_ses->srcaddr));
        memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr,
@@ -2347,7 +2367,7 @@ cifs_put_smb_ses(struct cifs_ses *ses)
        spin_unlock(&cifs_tcp_ses_lock);
 
        sesInfoFree(ses);
-       cifs_put_tcp_session(server);
+       cifs_put_tcp_session(server, 0);
 }
 
 #ifdef CONFIG_KEYS
@@ -2521,7 +2541,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
                mutex_unlock(&ses->session_mutex);
 
                /* existing SMB ses has a server reference already */
-               cifs_put_tcp_session(server);
+               cifs_put_tcp_session(server, 0);
                free_xid(xid);
                return ses;
        }
@@ -2611,7 +2631,7 @@ cifs_find_tcon(struct cifs_ses *ses, const char *unc)
        return NULL;
 }
 
-static void
+void
 cifs_put_tcon(struct cifs_tcon *tcon)
 {
        unsigned int xid;
@@ -3767,7 +3787,7 @@ mount_fail_check:
                else if (ses)
                        cifs_put_smb_ses(ses);
                else
-                       cifs_put_tcp_session(server);
+                       cifs_put_tcp_session(server, 0);
                bdi_destroy(&cifs_sb->bdi);
        }
 
@@ -4078,7 +4098,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
        ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
        if (IS_ERR(ses)) {
                tcon = (struct cifs_tcon *)ses;
-               cifs_put_tcp_session(master_tcon->ses->server);
+               cifs_put_tcp_session(master_tcon->ses->server, 0);
                goto out;
        }