cifs: prevent data race in smb2_reconnect()
authorPaulo Alcantara <pc@cjr.nz>
Mon, 30 Jan 2023 23:33:29 +0000 (20:33 -0300)
committerSteve French <stfrench@microsoft.com>
Mon, 20 Feb 2023 17:48:48 +0000 (11:48 -0600)
Make sure to get an up-to-date TCP_Server_Info::nr_targets value prior
to waiting the server to be reconnected in smb2_reconnect().  It is
set in cifs_tcp_ses_needs_reconnect() and protected by
TCP_Server_Info::srv_lock.

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/smb2pdu.c

index 2c9ffa9..2d5c3df 100644 (file)
@@ -139,6 +139,66 @@ out:
        return;
 }
 
+static int wait_for_server_reconnect(struct TCP_Server_Info *server,
+                                    __le16 smb2_command, bool retry)
+{
+       int timeout = 10;
+       int rc;
+
+       spin_lock(&server->srv_lock);
+       if (server->tcpStatus != CifsNeedReconnect) {
+               spin_unlock(&server->srv_lock);
+               return 0;
+       }
+       timeout *= server->nr_targets;
+       spin_unlock(&server->srv_lock);
+
+       /*
+        * Return to caller for TREE_DISCONNECT and LOGOFF and CLOSE
+        * here since they are implicitly done when session drops.
+        */
+       switch (smb2_command) {
+       /*
+        * BB Should we keep oplock break and add flush to exceptions?
+        */
+       case SMB2_TREE_DISCONNECT:
+       case SMB2_CANCEL:
+       case SMB2_CLOSE:
+       case SMB2_OPLOCK_BREAK:
+               return -EAGAIN;
+       }
+
+       /*
+        * Give demultiplex thread up to 10 seconds to each target available for
+        * reconnect -- should be greater than cifs socket timeout which is 7
+        * seconds.
+        *
+        * On "soft" mounts we wait once. Hard mounts keep retrying until
+        * process is killed or server comes back on-line.
+        */
+       do {
+               rc = wait_event_interruptible_timeout(server->response_q,
+                                                     (server->tcpStatus != CifsNeedReconnect),
+                                                     timeout * HZ);
+               if (rc < 0) {
+                       cifs_dbg(FYI, "%s: aborting reconnect due to received signal\n",
+                                __func__);
+                       return -ERESTARTSYS;
+               }
+
+               /* are we still trying to reconnect? */
+               spin_lock(&server->srv_lock);
+               if (server->tcpStatus != CifsNeedReconnect) {
+                       spin_unlock(&server->srv_lock);
+                       return 0;
+               }
+               spin_unlock(&server->srv_lock);
+       } while (retry);
+
+       cifs_dbg(FYI, "%s: gave up waiting on reconnect\n", __func__);
+       return -EHOSTDOWN;
+}
+
 static int
 smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
               struct TCP_Server_Info *server)
@@ -146,7 +206,6 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
        int rc = 0;
        struct nls_table *nls_codepage;
        struct cifs_ses *ses;
-       int retries;
 
        /*
         * SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
@@ -184,61 +243,11 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
            (!tcon->ses->server) || !server)
                return -EIO;
 
-       ses = tcon->ses;
-       retries = server->nr_targets;
-
-       /*
-        * Give demultiplex thread up to 10 seconds to each target available for
-        * reconnect -- should be greater than cifs socket timeout which is 7
-        * seconds.
-        */
-       while (server->tcpStatus == CifsNeedReconnect) {
-               /*
-                * Return to caller for TREE_DISCONNECT and LOGOFF and CLOSE
-                * here since they are implicitly done when session drops.
-                */
-               switch (smb2_command) {
-               /*
-                * BB Should we keep oplock break and add flush to exceptions?
-                */
-               case SMB2_TREE_DISCONNECT:
-               case SMB2_CANCEL:
-               case SMB2_CLOSE:
-               case SMB2_OPLOCK_BREAK:
-                       return -EAGAIN;
-               }
-
-               rc = wait_event_interruptible_timeout(server->response_q,
-                                                     (server->tcpStatus != CifsNeedReconnect),
-                                                     10 * HZ);
-               if (rc < 0) {
-                       cifs_dbg(FYI, "%s: aborting reconnect due to a received signal by the process\n",
-                                __func__);
-                       return -ERESTARTSYS;
-               }
-
-               /* are we still trying to reconnect? */
-               spin_lock(&server->srv_lock);
-               if (server->tcpStatus != CifsNeedReconnect) {
-                       spin_unlock(&server->srv_lock);
-                       break;
-               }
-               spin_unlock(&server->srv_lock);
-
-               if (retries && --retries)
-                       continue;
+       rc = wait_for_server_reconnect(server, smb2_command, tcon->retry);
+       if (rc)
+               return rc;
 
-               /*
-                * on "soft" mounts we wait once. Hard mounts keep
-                * retrying until process is killed or server comes
-                * back on-line
-                */
-               if (!tcon->retry) {
-                       cifs_dbg(FYI, "gave up waiting on reconnect in smb_init\n");
-                       return -EHOSTDOWN;
-               }
-               retries = server->nr_targets;
-       }
+       ses = tcon->ses;
 
        spin_lock(&ses->chan_lock);
        if (!cifs_chan_needs_reconnect(ses, server) && !tcon->need_reconnect) {