]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
smb: client: fix DFS interlink failover
authorPaulo Alcantara <pc@manguebit.com>
Wed, 18 Sep 2024 05:03:55 +0000 (02:03 -0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 17 Jul 2025 16:35:18 +0000 (18:35 +0200)
[ Upstream commit 4f42a8b54b5c6e36519aef3cb1f6210e54abd451 ]

The DFS interlinks point to different DFS namespaces so make sure to
use the correct DFS root server to chase any DFS links under it by
storing the SMB session in dfs_ref_walk structure and then using it on
every referral walk.

Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Stable-dep-of: 74ebd02163fd ("cifs: all initializations for tcon should happen in tcon_info_alloc")
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/smb/client/cifsglob.h
fs/smb/client/cifsproto.h
fs/smb/client/connect.c
fs/smb/client/dfs.c
fs/smb/client/dfs.h
fs/smb/client/dfs_cache.c
fs/smb/client/fs_context.h
fs/smb/client/misc.c
fs/smb/client/namespace.c

index 5c856adf7be9ecd72e9c90a532be9446164a5aa1..c9b37f2ebde85306714809a809fb19462890560f 100644 (file)
@@ -830,6 +830,7 @@ struct TCP_Server_Info {
         * format: \\HOST\SHARE[\OPTIONAL PATH]
         */
        char *leaf_fullpath;
+       bool dfs_conn:1;
 };
 
 static inline bool is_smb1(struct TCP_Server_Info *server)
@@ -1065,6 +1066,7 @@ struct cifs_ses {
        struct list_head smb_ses_list;
        struct list_head rlist; /* reconnect list */
        struct list_head tcon_list;
+       struct list_head dlist; /* dfs list */
        struct cifs_tcon *tcon_ipc;
        spinlock_t ses_lock;  /* protect anything here that is not protected */
        struct mutex session_mutex;
@@ -1294,6 +1296,7 @@ struct cifs_tcon {
        /* BB add field for back pointer to sb struct(s)? */
 #ifdef CONFIG_CIFS_DFS_UPCALL
        struct delayed_work dfs_cache_work;
+       struct list_head dfs_ses_list;
 #endif
        struct delayed_work     query_interfaces; /* query interfaces workqueue job */
        char *origin_fullpath; /* canonical copy of smb3_fs_context::source */
index c6d325666b5cd8c9d46325d4f9e2a0479672b6bf..8edb6fe89a97c69add5c458881f2d382a06f7275 100644 (file)
@@ -737,15 +737,9 @@ static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options)
 
 int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);
 
-/* Put references of @ses and its children */
 static inline void cifs_put_smb_ses(struct cifs_ses *ses)
 {
-       struct cifs_ses *next;
-
-       do {
-               next = ses->dfs_root_ses;
-               __cifs_put_smb_ses(ses);
-       } while ((ses = next));
+       __cifs_put_smb_ses(ses);
 }
 
 /* Get an active reference of @ses and its children.
@@ -759,9 +753,7 @@ static inline void cifs_put_smb_ses(struct cifs_ses *ses)
 static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
 {
        lockdep_assert_held(&cifs_tcp_ses_lock);
-
-       for (; ses; ses = ses->dfs_root_ses)
-               ses->ses_count++;
+       ses->ses_count++;
 }
 
 static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)
index 8298d1745f9b9c056bf351e7d0f9c1f98d566cdc..14be8822d23a2af31dfd0ff4a4ebd3fba10bd3b5 100644 (file)
@@ -1551,6 +1551,9 @@ static int match_server(struct TCP_Server_Info *server,
        if (server->nosharesock)
                return 0;
 
+       if (!match_super && (ctx->dfs_conn || server->dfs_conn))
+               return 0;
+
        /* If multidialect negotiation see if existing sessions match one */
        if (strcmp(ctx->vals->version_string, SMB3ANY_VERSION_STRING) == 0) {
                if (server->vals->protocol_id < SMB30_PROT_ID)
@@ -1740,6 +1743,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
 
        if (ctx->nosharesock)
                tcp_ses->nosharesock = true;
+       tcp_ses->dfs_conn = ctx->dfs_conn;
 
        tcp_ses->ops = ctx->ops;
        tcp_ses->vals = ctx->vals;
@@ -1890,12 +1894,14 @@ out_err:
 }
 
 /* this function must be called with ses_lock and chan_lock held */
-static int match_session(struct cifs_ses *ses, struct smb3_fs_context *ctx)
+static int match_session(struct cifs_ses *ses,
+                        struct smb3_fs_context *ctx,
+                        bool match_super)
 {
        struct TCP_Server_Info *server = ses->server;
        enum securityEnum ctx_sec, ses_sec;
 
-       if (ctx->dfs_root_ses != ses->dfs_root_ses)
+       if (!match_super && ctx->dfs_root_ses != ses->dfs_root_ses)
                return 0;
 
        /*
@@ -2047,7 +2053,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
                        continue;
                }
                spin_lock(&ses->chan_lock);
-               if (match_session(ses, ctx)) {
+               if (match_session(ses, ctx, false)) {
                        spin_unlock(&ses->chan_lock);
                        spin_unlock(&ses->ses_lock);
                        ret = ses;
@@ -2450,8 +2456,6 @@ retry_new_session:
         * need to lock before changing something in the session.
         */
        spin_lock(&cifs_tcp_ses_lock);
-       if (ctx->dfs_root_ses)
-               cifs_smb_ses_inc_refcount(ctx->dfs_root_ses);
        ses->dfs_root_ses = ctx->dfs_root_ses;
        list_add(&ses->smb_ses_list, &server->smb_ses_list);
        spin_unlock(&cifs_tcp_ses_lock);
@@ -2528,6 +2532,7 @@ cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
 {
        unsigned int xid;
        struct cifs_ses *ses;
+       LIST_HEAD(ses_list);
 
        /*
         * IPC tcon share the lifetime of their session and are
@@ -2552,6 +2557,9 @@ cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
 
        list_del_init(&tcon->tcon_list);
        tcon->status = TID_EXITING;
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       list_replace_init(&tcon->dfs_ses_list, &ses_list);
+#endif
        spin_unlock(&tcon->tc_lock);
        spin_unlock(&cifs_tcp_ses_lock);
 
@@ -2579,6 +2587,9 @@ cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
        cifs_fscache_release_super_cookie(tcon);
        tconInfoFree(tcon, netfs_trace_tcon_ref_free);
        cifs_put_smb_ses(ses);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       dfs_put_root_smb_sessions(&ses_list);
+#endif
 }
 
 /**
@@ -2962,7 +2973,7 @@ cifs_match_super(struct super_block *sb, void *data)
        spin_lock(&ses->chan_lock);
        spin_lock(&tcon->tc_lock);
        if (!match_server(tcp_srv, ctx, true) ||
-           !match_session(ses, ctx) ||
+           !match_session(ses, ctx, true) ||
            !match_tcon(tcon, ctx) ||
            !match_prepath(sb, tcon, mnt_data)) {
                rc = 0;
@@ -3712,13 +3723,12 @@ out:
 int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
 {
        struct cifs_mount_ctx mnt_ctx = { .cifs_sb = cifs_sb, .fs_ctx = ctx, };
-       bool isdfs;
        int rc;
 
-       rc = dfs_mount_share(&mnt_ctx, &isdfs);
+       rc = dfs_mount_share(&mnt_ctx);
        if (rc)
                goto error;
-       if (!isdfs)
+       if (!ctx->dfs_conn)
                goto out;
 
        /*
@@ -4135,7 +4145,7 @@ cifs_set_vol_auth(struct smb3_fs_context *ctx, struct cifs_ses *ses)
 }
 
 static struct cifs_tcon *
-__cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
+cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
 {
        int rc;
        struct cifs_tcon *master_tcon = cifs_sb_master_tcon(cifs_sb);
@@ -4233,17 +4243,6 @@ out:
        return tcon;
 }
 
-static struct cifs_tcon *
-cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
-{
-       struct cifs_tcon *ret;
-
-       cifs_mount_lock();
-       ret = __cifs_construct_tcon(cifs_sb, fsuid);
-       cifs_mount_unlock();
-       return ret;
-}
-
 struct cifs_tcon *
 cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)
 {
index bd259b04cdede061c08ddaed9e246f87e476aeea..c35953843373ea8ce3fc540818d8a6645827f21d 100644 (file)
@@ -69,7 +69,7 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
  * Get an active reference of @ses so that next call to cifs_put_tcon() won't
  * release it as any new DFS referrals must go through its IPC tcon.
  */
-static void add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
+static void set_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
 {
        struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
        struct cifs_ses *ses = mnt_ctx->ses;
@@ -95,7 +95,7 @@ static inline int parse_dfs_target(struct smb3_fs_context *ctx,
        return rc;
 }
 
-static int set_ref_paths(struct cifs_mount_ctx *mnt_ctx,
+static int setup_dfs_ref(struct cifs_mount_ctx *mnt_ctx,
                         struct dfs_info3_param *tgt,
                         struct dfs_ref_walk *rw)
 {
@@ -120,6 +120,7 @@ static int set_ref_paths(struct cifs_mount_ctx *mnt_ctx,
        }
        ref_walk_path(rw) = ref_path;
        ref_walk_fpath(rw) = full_path;
+       ref_walk_ses(rw) = ctx->dfs_root_ses;
        return 0;
 }
 
@@ -128,11 +129,11 @@ static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
 {
        struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
        struct dfs_info3_param tgt = {};
-       bool is_refsrv;
        int rc = -ENOENT;
 
 again:
        do {
+               ctx->dfs_root_ses = ref_walk_ses(rw);
                if (ref_walk_empty(rw)) {
                        rc = dfs_get_referral(mnt_ctx, ref_walk_path(rw) + 1,
                                              NULL, ref_walk_tl(rw));
@@ -158,10 +159,7 @@ again:
                        if (rc)
                                continue;
 
-                       is_refsrv = tgt.server_type == DFS_TYPE_ROOT ||
-                               DFS_INTERLINK(tgt.flags);
                        ref_walk_set_tgt_hint(rw);
-
                        if (tgt.flags & DFSREF_STORAGE_SERVER) {
                                rc = cifs_mount_get_tcon(mnt_ctx);
                                if (!rc)
@@ -172,12 +170,10 @@ again:
                                        continue;
                        }
 
-                       if (is_refsrv)
-                               add_root_smb_session(mnt_ctx);
-
+                       set_root_smb_session(mnt_ctx);
                        rc = ref_walk_advance(rw);
                        if (!rc) {
-                               rc = set_ref_paths(mnt_ctx, &tgt, rw);
+                               rc = setup_dfs_ref(mnt_ctx, &tgt, rw);
                                if (!rc) {
                                        rc = -EREMOTE;
                                        goto again;
@@ -193,20 +189,22 @@ out:
        return rc;
 }
 
-static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx)
+static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
+                            struct dfs_ref_walk **rw)
 {
-       struct dfs_ref_walk *rw;
        int rc;
 
-       rw = ref_walk_alloc();
-       if (IS_ERR(rw))
-               return PTR_ERR(rw);
+       *rw = ref_walk_alloc();
+       if (IS_ERR(*rw)) {
+               rc = PTR_ERR(*rw);
+               *rw = NULL;
+               return rc;
+       }
 
-       ref_walk_init(rw);
-       rc = set_ref_paths(mnt_ctx, NULL, rw);
+       ref_walk_init(*rw);
+       rc = setup_dfs_ref(mnt_ctx, NULL, *rw);
        if (!rc)
-               rc = __dfs_referral_walk(mnt_ctx, rw);
-       ref_walk_free(rw);
+               rc = __dfs_referral_walk(mnt_ctx, *rw);
        return rc;
 }
 
@@ -214,16 +212,16 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
 {
        struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
        struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+       struct dfs_ref_walk *rw = NULL;
        struct cifs_tcon *tcon;
        char *origin_fullpath;
-       bool new_tcon = true;
        int rc;
 
        origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
        if (IS_ERR(origin_fullpath))
                return PTR_ERR(origin_fullpath);
 
-       rc = dfs_referral_walk(mnt_ctx);
+       rc = dfs_referral_walk(mnt_ctx, &rw);
        if (!rc) {
                /*
                 * Prevent superblock from being created with any missing
@@ -241,21 +239,16 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
 
        tcon = mnt_ctx->tcon;
        spin_lock(&tcon->tc_lock);
-       if (!tcon->origin_fullpath) {
-               tcon->origin_fullpath = origin_fullpath;
-               origin_fullpath = NULL;
-       } else {
-               new_tcon = false;
-       }
+       tcon->origin_fullpath = origin_fullpath;
+       origin_fullpath = NULL;
+       ref_walk_set_tcon(rw, tcon);
        spin_unlock(&tcon->tc_lock);
-
-       if (new_tcon) {
-               queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
-                                  dfs_cache_get_ttl() * HZ);
-       }
+       queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
+                          dfs_cache_get_ttl() * HZ);
 
 out:
        kfree(origin_fullpath);
+       ref_walk_free(rw);
        return rc;
 }
 
@@ -279,7 +272,7 @@ static int update_fs_context_dstaddr(struct smb3_fs_context *ctx)
        return rc;
 }
 
-int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
+int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
 {
        struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
        bool nodfs = ctx->nodfs;
@@ -289,7 +282,6 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
        if (rc)
                return rc;
 
-       *isdfs = false;
        rc = get_session(mnt_ctx, NULL);
        if (rc)
                return rc;
@@ -317,10 +309,15 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
                return rc;
        }
 
-       *isdfs = true;
-       add_root_smb_session(mnt_ctx);
-       rc = __dfs_mount_share(mnt_ctx);
-       dfs_put_root_smb_sessions(mnt_ctx);
+       if (!ctx->dfs_conn) {
+               ctx->dfs_conn = true;
+               cifs_mount_put_conns(mnt_ctx);
+               rc = get_session(mnt_ctx, NULL);
+       }
+       if (!rc) {
+               set_root_smb_session(mnt_ctx);
+               rc = __dfs_mount_share(mnt_ctx);
+       }
        return rc;
 }
 
index e5c4dcf837503aa2851f9b01680b6f5b7eb8874d..1aa2bc65b3bc2c43f953d01f753ff26a42f0511d 100644 (file)
@@ -19,6 +19,7 @@
 struct dfs_ref {
        char *path;
        char *full_path;
+       struct cifs_ses *ses;
        struct dfs_cache_tgt_list tl;
        struct dfs_cache_tgt_iterator *tit;
 };
@@ -38,6 +39,7 @@ struct dfs_ref_walk {
 #define ref_walk_path(w)       (ref_walk_cur(w)->path)
 #define ref_walk_fpath(w)      (ref_walk_cur(w)->full_path)
 #define ref_walk_tl(w)         (&ref_walk_cur(w)->tl)
+#define ref_walk_ses(w)        (ref_walk_cur(w)->ses)
 
 static inline struct dfs_ref_walk *ref_walk_alloc(void)
 {
@@ -60,14 +62,19 @@ static inline void __ref_walk_free(struct dfs_ref *ref)
        kfree(ref->path);
        kfree(ref->full_path);
        dfs_cache_free_tgts(&ref->tl);
+       if (ref->ses)
+               cifs_put_smb_ses(ref->ses);
        memset(ref, 0, sizeof(*ref));
 }
 
 static inline void ref_walk_free(struct dfs_ref_walk *rw)
 {
-       struct dfs_ref *ref = ref_walk_start(rw);
+       struct dfs_ref *ref;
 
-       for (; ref <= ref_walk_end(rw); ref++)
+       if (!rw)
+               return;
+
+       for (ref = ref_walk_start(rw); ref <= ref_walk_end(rw); ref++)
                __ref_walk_free(ref);
        kfree(rw);
 }
@@ -116,9 +123,22 @@ static inline void ref_walk_set_tgt_hint(struct dfs_ref_walk *rw)
                                       ref_walk_tit(rw));
 }
 
+static inline void ref_walk_set_tcon(struct dfs_ref_walk *rw,
+                                    struct cifs_tcon *tcon)
+{
+       struct dfs_ref *ref = ref_walk_start(rw);
+
+       for (; ref <= ref_walk_cur(rw); ref++) {
+               if (WARN_ON_ONCE(!ref->ses))
+                       continue;
+               list_add(&ref->ses->dlist, &tcon->dfs_ses_list);
+               ref->ses = NULL;
+       }
+}
+
 int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
                              struct smb3_fs_context *ctx);
-int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs);
+int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx);
 
 static inline char *dfs_get_path(struct cifs_sb_info *cifs_sb, const char *path)
 {
@@ -142,20 +162,14 @@ static inline int dfs_get_referral(struct cifs_mount_ctx *mnt_ctx, const char *p
  * references of all DFS root sessions that were used across the mount process
  * in dfs_mount_share().
  */
-static inline void dfs_put_root_smb_sessions(struct cifs_mount_ctx *mnt_ctx)
+static inline void dfs_put_root_smb_sessions(struct list_head *head)
 {
-       const struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
-       struct cifs_ses *ses = ctx->dfs_root_ses;
-       struct cifs_ses *cur;
-
-       if (!ses)
-               return;
+       struct cifs_ses *ses, *n;
 
-       for (cur = ses; cur; cur = cur->dfs_root_ses) {
-               if (cur->dfs_root_ses)
-                       cifs_put_smb_ses(cur->dfs_root_ses);
+       list_for_each_entry_safe(ses, n, head, dlist) {
+               list_del_init(&ses->dlist);
+               cifs_put_smb_ses(ses);
        }
-       cifs_put_smb_ses(ses);
 }
 
 #endif /* _CIFS_DFS_H */
index 3cf7c88489be456a073d54f5c8ffa8585bc49628..433f546055b977d8402862e67b7a19279b91272b 100644 (file)
@@ -1336,9 +1336,8 @@ void dfs_cache_refresh(struct work_struct *work)
        struct cifs_ses *ses;
 
        tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
-       ses = tcon->ses->dfs_root_ses;
 
-       for (; ses; ses = ses->dfs_root_ses)
+       list_for_each_entry(ses, &tcon->dfs_ses_list, dlist)
                refresh_ses_referral(ses);
        refresh_tcon_referral(tcon, false);
 
index d0a2043ea44682fdcc46ccaf01141fcc8af674c3..52ee72e562f5f6768d3f32ad87c4e0a990515745 100644 (file)
@@ -287,6 +287,7 @@ struct smb3_fs_context {
        struct cifs_ses *dfs_root_ses;
        bool dfs_automount:1; /* set for dfs automount only */
        enum cifs_reparse_type reparse_type;
+       bool dfs_conn:1; /* set for dfs mounts */
 };
 
 extern const struct fs_parameter_spec smb3_fs_parameters[];
index 9e8e0a01ae8eb0f930032e6734ff2e237b40913c..2e9a14e28e466c3013c0e880b3b34030bf5bbf73 100644 (file)
@@ -145,6 +145,9 @@ tcon_info_alloc(bool dir_leases_enabled, enum smb3_tcon_ref_trace trace)
        mutex_init(&ret_buf->fscache_lock);
 #endif
        trace_smb3_tcon_ref(ret_buf->debug_id, ret_buf->tc_count, trace);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       INIT_LIST_HEAD(&ret_buf->dfs_ses_list);
+#endif
 
        return ret_buf;
 }
index ec58c0e507244b8a0d6885f7e71245625a50f506..a6655807c0865ae5c5b7f4ee7d4dac4cade16f41 100644 (file)
@@ -260,7 +260,7 @@ static struct vfsmount *cifs_do_automount(struct path *path)
                ctx->source = NULL;
                goto out;
        }
-       ctx->dfs_automount = is_dfs_mount(mntpt);
+       ctx->dfs_automount = ctx->dfs_conn = is_dfs_mount(mntpt);
        cifs_dbg(FYI, "%s: ctx: source=%s UNC=%s prepath=%s dfs_automount=%d\n",
                 __func__, ctx->source, ctx->UNC, ctx->prepath, ctx->dfs_automount);