]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
smb: client: handle lack of IPC in dfs_cache_refresh()
authorPaulo Alcantara <pc@manguebit.org>
Thu, 23 Oct 2025 21:59:47 +0000 (18:59 -0300)
committerSteve French <stfrench@microsoft.com>
Thu, 30 Oct 2025 01:13:05 +0000 (20:13 -0500)
In very rare cases, DFS mounts could end up with SMB sessions without
any IPC connections.  These mounts are only possible when having
unexpired cached DFS referrals, hence not requiring any IPC
connections during the mount process.

Try to establish those missing IPC connections when refreshing DFS
referrals.  If the server is still rejecting it, then simply ignore
and leave expired cached DFS referral for any potential DFS failovers.

Reported-by: Jay Shin <jaeshin@redhat.com>
Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
Cc: David Howells <dhowells@redhat.com>
Cc: linux-cifs@vger.kernel.org
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsproto.h
fs/smb/client/connect.c
fs/smb/client/dfs_cache.c

index fb1813cbe0ebcb85d6fefa737324c21e84cc41f4..3528c365a452907d8463f2cc96a5fd47da008b86 100644 (file)
@@ -616,6 +616,8 @@ extern int E_md4hash(const unsigned char *passwd, unsigned char *p16,
 extern struct TCP_Server_Info *
 cifs_find_tcp_session(struct smb3_fs_context *ctx);
 
+struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal);
+
 void __cifs_put_smb_ses(struct cifs_ses *ses);
 
 extern struct cifs_ses *
index dd12f3eb61dcbd2ade548b1a120ef1fee5c806f8..d65ab7e4b1c269b3a2dc8c521555b6e30be2f4c9 100644 (file)
@@ -2015,39 +2015,31 @@ static int match_session(struct cifs_ses *ses,
 /**
  * cifs_setup_ipc - helper to setup the IPC tcon for the session
  * @ses: smb session to issue the request on
- * @ctx: the superblock configuration context to use for building the
- *       new tree connection for the IPC (interprocess communication RPC)
+ * @seal: if encryption is requested
  *
  * A new IPC connection is made and stored in the session
  * tcon_ipc. The IPC tcon has the same lifetime as the session.
  */
-static int
-cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx)
+struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal)
 {
        int rc = 0, xid;
        struct cifs_tcon *tcon;
        char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0};
-       bool seal = false;
        struct TCP_Server_Info *server = ses->server;
 
        /*
         * If the mount request that resulted in the creation of the
         * session requires encryption, force IPC to be encrypted too.
         */
-       if (ctx->seal) {
-               if (server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION)
-                       seal = true;
-               else {
-                       cifs_server_dbg(VFS,
-                                "IPC: server doesn't support encryption\n");
-                       return -EOPNOTSUPP;
-               }
+       if (seal && !(server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION)) {
+               cifs_server_dbg(VFS, "IPC: server doesn't support encryption\n");
+               return ERR_PTR(-EOPNOTSUPP);
        }
 
        /* no need to setup directory caching on IPC share, so pass in false */
        tcon = tcon_info_alloc(false, netfs_trace_tcon_ref_new_ipc);
        if (tcon == NULL)
-               return -ENOMEM;
+               return ERR_PTR(-ENOMEM);
 
        spin_lock(&server->srv_lock);
        scnprintf(unc, sizeof(unc), "\\\\%s\\IPC$", server->hostname);
@@ -2057,13 +2049,13 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx)
        tcon->ses = ses;
        tcon->ipc = true;
        tcon->seal = seal;
-       rc = server->ops->tree_connect(xid, ses, unc, tcon, ctx->local_nls);
+       rc = server->ops->tree_connect(xid, ses, unc, tcon, ses->local_nls);
        free_xid(xid);
 
        if (rc) {
-               cifs_server_dbg(VFS, "failed to connect to IPC (rc=%d)\n", rc);
+               cifs_server_dbg(VFS | ONCE, "failed to connect to IPC (rc=%d)\n", rc);
                tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc_fail);
-               goto out;
+               return ERR_PTR(rc);
        }
 
        cifs_dbg(FYI, "IPC tcon rc=%d ipc tid=0x%x\n", rc, tcon->tid);
@@ -2071,9 +2063,7 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx)
        spin_lock(&tcon->tc_lock);
        tcon->status = TID_GOOD;
        spin_unlock(&tcon->tc_lock);
-       ses->tcon_ipc = tcon;
-out:
-       return rc;
+       return tcon;
 }
 
 static struct cifs_ses *
@@ -2347,6 +2337,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
 {
        struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr;
        struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
+       struct cifs_tcon *ipc;
        struct cifs_ses *ses;
        unsigned int xid;
        int retries = 0;
@@ -2525,7 +2516,12 @@ retry_new_session:
        list_add(&ses->smb_ses_list, &server->smb_ses_list);
        spin_unlock(&cifs_tcp_ses_lock);
 
-       cifs_setup_ipc(ses, ctx);
+       ipc = cifs_setup_ipc(ses, ctx->seal);
+       spin_lock(&cifs_tcp_ses_lock);
+       spin_lock(&ses->ses_lock);
+       ses->tcon_ipc = !IS_ERR(ipc) ? ipc : NULL;
+       spin_unlock(&ses->ses_lock);
+       spin_unlock(&cifs_tcp_ses_lock);
 
        free_xid(xid);
 
index 4dada26d56b5fe246e960b158244fbc934792c68..f2ad0ccd08a77124c024dbbfb23986e154b39a42 100644 (file)
@@ -1120,24 +1120,63 @@ static bool target_share_equal(struct cifs_tcon *tcon, const char *s1)
        return match;
 }
 
-static bool is_ses_good(struct cifs_ses *ses)
+static bool is_ses_good(struct cifs_tcon *tcon, struct cifs_ses *ses)
 {
        struct TCP_Server_Info *server = ses->server;
-       struct cifs_tcon *tcon = ses->tcon_ipc;
+       struct cifs_tcon *ipc = NULL;
        bool ret;
 
+       spin_lock(&cifs_tcp_ses_lock);
        spin_lock(&ses->ses_lock);
        spin_lock(&ses->chan_lock);
+
        ret = !cifs_chan_needs_reconnect(ses, server) &&
-               ses->ses_status == SES_GOOD &&
-               !tcon->need_reconnect;
+               ses->ses_status == SES_GOOD;
+
        spin_unlock(&ses->chan_lock);
+
+       if (!ret)
+               goto out;
+
+       if (likely(ses->tcon_ipc)) {
+               if (ses->tcon_ipc->need_reconnect) {
+                       ret = false;
+                       goto out;
+               }
+       } else {
+               spin_unlock(&ses->ses_lock);
+               spin_unlock(&cifs_tcp_ses_lock);
+
+               ipc = cifs_setup_ipc(ses, tcon->seal);
+
+               spin_lock(&cifs_tcp_ses_lock);
+               spin_lock(&ses->ses_lock);
+               if (!IS_ERR(ipc)) {
+                       if (!ses->tcon_ipc) {
+                               ses->tcon_ipc = ipc;
+                               ipc = NULL;
+                       }
+               } else {
+                       ret = false;
+                       ipc = NULL;
+               }
+       }
+
+out:
        spin_unlock(&ses->ses_lock);
+       spin_unlock(&cifs_tcp_ses_lock);
+       if (ipc && server->ops->tree_disconnect) {
+               unsigned int xid = get_xid();
+
+               (void)server->ops->tree_disconnect(xid, ipc);
+               _free_xid(xid);
+       }
+       tconInfoFree(ipc, netfs_trace_tcon_ref_free_ipc);
        return ret;
 }
 
 /* Refresh dfs referral of @ses */
-static void refresh_ses_referral(struct cifs_ses *ses)
+static void refresh_ses_referral(struct cifs_tcon *tcon, struct cifs_ses *ses)
 {
        struct cache_entry *ce;
        unsigned int xid;
@@ -1153,7 +1192,7 @@ static void refresh_ses_referral(struct cifs_ses *ses)
        }
 
        ses = CIFS_DFS_ROOT_SES(ses);
-       if (!is_ses_good(ses)) {
+       if (!is_ses_good(tcon, ses)) {
                cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n",
                         __func__);
                goto out;
@@ -1241,7 +1280,7 @@ static void refresh_tcon_referral(struct cifs_tcon *tcon, bool force_refresh)
        up_read(&htable_rw_lock);
 
        ses = CIFS_DFS_ROOT_SES(ses);
-       if (!is_ses_good(ses)) {
+       if (!is_ses_good(tcon, ses)) {
                cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n",
                         __func__);
                goto out;
@@ -1309,7 +1348,7 @@ void dfs_cache_refresh(struct work_struct *work)
        tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
 
        list_for_each_entry(ses, &tcon->dfs_ses_list, dlist)
-               refresh_ses_referral(ses);
+               refresh_ses_referral(tcon, ses);
        refresh_tcon_referral(tcon, false);
 
        queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,