--- /dev/null
+From ee20d7c6100752eaf2409d783f4f1449c29ea33d Mon Sep 17 00:00:00 2001
+From: Paulo Alcantara <pc@manguebit.com>
+Date: Tue, 25 Apr 2023 02:42:56 -0300
+Subject: cifs: fix potential race when tree connecting ipc
+
+From: Paulo Alcantara <pc@manguebit.com>
+
+commit ee20d7c6100752eaf2409d783f4f1449c29ea33d upstream.
+
+Protect access of TCP_Server_Info::hostname when building the ipc tree
+name as it might get freed in cifsd thread and thus causing an
+use-after-free bug in __tree_connect_dfs_target(). Also, while at it,
+update status of IPC tcon on success and then avoid any extra tree
+connects.
+
+Cc: stable@vger.kernel.org # v6.2+
+Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/cifs/dfs.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
+ 1 file changed, 50 insertions(+), 7 deletions(-)
+
+--- a/fs/cifs/dfs.c
++++ b/fs/cifs/dfs.c
+@@ -398,6 +398,54 @@ static int target_share_matches_server(s
+ return rc;
+ }
+
++static void __tree_connect_ipc(const unsigned int xid, char *tree,
++ struct cifs_sb_info *cifs_sb,
++ struct cifs_ses *ses)
++{
++ struct TCP_Server_Info *server = ses->server;
++ struct cifs_tcon *tcon = ses->tcon_ipc;
++ int rc;
++
++ spin_lock(&ses->ses_lock);
++ spin_lock(&ses->chan_lock);
++ if (cifs_chan_needs_reconnect(ses, server) ||
++ ses->ses_status != SES_GOOD) {
++ spin_unlock(&ses->chan_lock);
++ spin_unlock(&ses->ses_lock);
++ cifs_server_dbg(FYI, "%s: skipping ipc reconnect due to disconnected ses\n",
++ __func__);
++ return;
++ }
++ spin_unlock(&ses->chan_lock);
++ spin_unlock(&ses->ses_lock);
++
++ cifs_server_lock(server);
++ scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
++ cifs_server_unlock(server);
++
++ rc = server->ops->tree_connect(xid, ses, tree, tcon,
++ cifs_sb->local_nls);
++ cifs_server_dbg(FYI, "%s: tree_reconnect %s: %d\n", __func__, tree, rc);
++ spin_lock(&tcon->tc_lock);
++ if (rc) {
++ tcon->status = TID_NEED_TCON;
++ } else {
++ tcon->status = TID_GOOD;
++ tcon->need_reconnect = false;
++ }
++ spin_unlock(&tcon->tc_lock);
++}
++
++static void tree_connect_ipc(const unsigned int xid, char *tree,
++ struct cifs_sb_info *cifs_sb,
++ struct cifs_tcon *tcon)
++{
++ struct cifs_ses *ses = tcon->ses;
++
++ __tree_connect_ipc(xid, tree, cifs_sb, ses);
++ __tree_connect_ipc(xid, tree, cifs_sb, CIFS_DFS_ROOT_SES(ses));
++}
++
+ static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
+ struct cifs_sb_info *cifs_sb, char *tree, bool islink,
+ struct dfs_cache_tgt_list *tl)
+@@ -406,7 +454,6 @@ static int __tree_connect_dfs_target(con
+ struct TCP_Server_Info *server = tcon->ses->server;
+ const struct smb_version_operations *ops = server->ops;
+ struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
+- struct cifs_tcon *ipc = root_ses->tcon_ipc;
+ char *share = NULL, *prefix = NULL;
+ struct dfs_cache_tgt_iterator *tit;
+ bool target_match;
+@@ -442,18 +489,14 @@ static int __tree_connect_dfs_target(con
+ }
+
+ dfs_cache_noreq_update_tgthint(server->current_fullpath + 1, tit);
+-
+- if (ipc->need_reconnect) {
+- scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
+- rc = ops->tree_connect(xid, ipc->ses, tree, ipc, cifs_sb->local_nls);
+- cifs_dbg(FYI, "%s: reconnect ipc: %d\n", __func__, rc);
+- }
++ tree_connect_ipc(xid, tree, cifs_sb, tcon);
+
+ scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
+ if (!islink) {
+ rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
+ break;
+ }
++
+ /*
+ * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
+ * to it. Otherwise, cache the dfs referral and then mark current tcp ses for
--- /dev/null
+From 90c49fce1c43e1cc152695e20363ff5087897c09 Mon Sep 17 00:00:00 2001
+From: Paulo Alcantara <pc@manguebit.com>
+Date: Fri, 21 Apr 2023 15:52:32 -0300
+Subject: cifs: fix potential use-after-free bugs in TCP_Server_Info::hostname
+
+From: Paulo Alcantara <pc@manguebit.com>
+
+commit 90c49fce1c43e1cc152695e20363ff5087897c09 upstream.
+
+TCP_Server_Info::hostname may be updated once or many times during
+reconnect, so protect its access outside reconnect path as well and
+then prevent any potential use-after-free bugs.
+
+Cc: stable@vger.kernel.org
+Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/cifs/cifs_debug.c | 7 ++++++-
+ fs/cifs/cifs_debug.h | 12 ++++++------
+ fs/cifs/connect.c | 10 +++++++---
+ fs/cifs/sess.c | 7 ++++---
+ 4 files changed, 23 insertions(+), 13 deletions(-)
+
+--- a/fs/cifs/cifs_debug.c
++++ b/fs/cifs/cifs_debug.c
+@@ -279,8 +279,10 @@ static int cifs_debug_data_proc_show(str
+ seq_printf(m, "\n%d) ConnectionId: 0x%llx ",
+ c, server->conn_id);
+
++ spin_lock(&server->srv_lock);
+ if (server->hostname)
+ seq_printf(m, "Hostname: %s ", server->hostname);
++ spin_unlock(&server->srv_lock);
+ #ifdef CONFIG_CIFS_SMB_DIRECT
+ if (!server->rdma)
+ goto skip_rdma;
+@@ -620,10 +622,13 @@ static int cifs_stats_proc_show(struct s
+ server->fastest_cmd[j],
+ server->slowest_cmd[j]);
+ for (j = 0; j < NUMBER_OF_SMB2_COMMANDS; j++)
+- if (atomic_read(&server->smb2slowcmd[j]))
++ if (atomic_read(&server->smb2slowcmd[j])) {
++ spin_lock(&server->srv_lock);
+ seq_printf(m, " %d slow responses from %s for command %d\n",
+ atomic_read(&server->smb2slowcmd[j]),
+ server->hostname, j);
++ spin_unlock(&server->srv_lock);
++ }
+ #endif /* STATS2 */
+ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+ list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+--- a/fs/cifs/cifs_debug.h
++++ b/fs/cifs/cifs_debug.h
+@@ -81,19 +81,19 @@ do { \
+
+ #define cifs_server_dbg_func(ratefunc, type, fmt, ...) \
+ do { \
+- const char *sn = ""; \
+- if (server && server->hostname) \
+- sn = server->hostname; \
++ spin_lock(&server->srv_lock); \
+ if ((type) & FYI && cifsFYI & CIFS_INFO) { \
+ pr_debug_ ## ratefunc("%s: \\\\%s " fmt, \
+- __FILE__, sn, ##__VA_ARGS__); \
++ __FILE__, server->hostname, \
++ ##__VA_ARGS__); \
+ } else if ((type) & VFS) { \
+ pr_err_ ## ratefunc("VFS: \\\\%s " fmt, \
+- sn, ##__VA_ARGS__); \
++ server->hostname, ##__VA_ARGS__); \
+ } else if ((type) & NOISY && (NOISY != 0)) { \
+ pr_debug_ ## ratefunc("\\\\%s " fmt, \
+- sn, ##__VA_ARGS__); \
++ server->hostname, ##__VA_ARGS__); \
+ } \
++ spin_unlock(&server->srv_lock); \
+ } while (0)
+
+ #define cifs_server_dbg(type, fmt, ...) \
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -435,8 +435,10 @@ static int __reconnect_target_unlocked(s
+ if (server->hostname != target) {
+ hostname = extract_hostname(target);
+ if (!IS_ERR(hostname)) {
++ spin_lock(&server->srv_lock);
+ kfree(server->hostname);
+ server->hostname = hostname;
++ spin_unlock(&server->srv_lock);
+ } else {
+ cifs_dbg(FYI, "%s: couldn't extract hostname or address from dfs target: %ld\n",
+ __func__, PTR_ERR(hostname));
+@@ -593,9 +595,7 @@ cifs_echo_request(struct work_struct *wo
+ goto requeue_echo;
+
+ rc = server->ops->echo ? server->ops->echo(server) : -ENOSYS;
+- if (rc)
+- cifs_dbg(FYI, "Unable to send echo request to server: %s\n",
+- server->hostname);
++ cifs_server_dbg(FYI, "send echo request: rc = %d\n", rc);
+
+ /* Check witness registrations */
+ cifs_swn_check();
+@@ -1445,6 +1445,8 @@ static int match_server(struct TCP_Serve
+ {
+ struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;
+
++ lockdep_assert_held(&server->srv_lock);
++
+ if (ctx->nosharesock)
+ return 0;
+
+@@ -1859,7 +1861,9 @@ cifs_setup_ipc(struct cifs_ses *ses, str
+ if (tcon == NULL)
+ return -ENOMEM;
+
++ spin_lock(&server->srv_lock);
+ scnprintf(unc, sizeof(unc), "\\\\%s\\IPC$", server->hostname);
++ spin_unlock(&server->srv_lock);
+
+ xid = get_xid();
+ tcon->ses = ses;
+--- a/fs/cifs/sess.c
++++ b/fs/cifs/sess.c
+@@ -159,6 +159,7 @@ cifs_chan_is_iface_active(struct cifs_se
+ /* returns number of channels added */
+ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
+ {
++ struct TCP_Server_Info *server = ses->server;
+ int old_chan_count, new_chan_count;
+ int left;
+ int rc = 0;
+@@ -178,16 +179,16 @@ int cifs_try_adding_channels(struct cifs
+ return 0;
+ }
+
+- if (ses->server->dialect < SMB30_PROT_ID) {
++ if (server->dialect < SMB30_PROT_ID) {
+ spin_unlock(&ses->chan_lock);
+ cifs_dbg(VFS, "multichannel is not supported on this protocol version, use 3.0 or above\n");
+ return 0;
+ }
+
+- if (!(ses->server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
++ if (!(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
+ ses->chan_max = 1;
+ spin_unlock(&ses->chan_lock);
+- cifs_dbg(VFS, "server %s does not support multichannel\n", ses->server->hostname);
++ cifs_server_dbg(VFS, "no multichannel support\n");
+ return 0;
+ }
+ spin_unlock(&ses->chan_lock);
--- /dev/null
+From 8e3554150d6c80a84b3cb046615d1a0e943811dc Mon Sep 17 00:00:00 2001
+From: Paulo Alcantara <pc@manguebit.com>
+Date: Sun, 23 Apr 2023 23:26:51 -0300
+Subject: cifs: fix sharing of DFS connections
+
+From: Paulo Alcantara <pc@manguebit.com>
+
+commit 8e3554150d6c80a84b3cb046615d1a0e943811dc upstream.
+
+When matching DFS connections, we can't rely on the values set in
+cifs_sb_info::prepath and cifs_tcon::tree_name as they might change
+during DFS failover. The DFS referrals related to a specific DFS tcon
+are already matched earlier in match_server(), therefore we can safely
+skip those checks altogether as the connection is guaranteed to be
+unique for the DFS tcon.
+
+Besides, when creating or finding an SMB session, make sure to also
+refcount any DFS root session related to it (cifs_ses::dfs_root_ses),
+so if a new DFS mount ends up reusing the connection from the old
+mount while there was an umount(2) still in progress (e.g. umount(2)
+-> cifs_umount() -> reconnect -> cifs_put_tcon()), the connection
+could potentially be put right after the umount(2) finished.
+
+Patch has minor update to include fix for unused variable issue
+noted by the kernel test robot
+
+Reported-by: kernel test robot <lkp@intel.com>
+Link: https://lore.kernel.org/oe-kbuild-all/202305041040.j7W2xQSy-lkp@intel.com/
+Cc: stable@vger.kernel.org # v6.2+
+Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/cifs/cifsglob.h | 1
+ fs/cifs/cifsproto.h | 44 +++++++++++++++++++
+ fs/cifs/connect.c | 116 ++++++++++++++++++++++++++--------------------------
+ fs/cifs/dfs.c | 62 +++++++++++++++++++--------
+ fs/cifs/ioctl.c | 2
+ fs/cifs/smb2pdu.c | 4 -
+ 6 files changed, 148 insertions(+), 81 deletions(-)
+
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -1768,7 +1768,6 @@ struct cifs_mount_ctx {
+ struct TCP_Server_Info *server;
+ struct cifs_ses *ses;
+ struct cifs_tcon *tcon;
+- char *origin_fullpath, *leaf_fullpath;
+ struct list_head dfs_ses_list;
+ };
+
+--- a/fs/cifs/cifsproto.h
++++ b/fs/cifs/cifsproto.h
+@@ -8,6 +8,7 @@
+ #ifndef _CIFSPROTO_H
+ #define _CIFSPROTO_H
+ #include <linux/nls.h>
++#include <linux/ctype.h>
+ #include "trace.h"
+ #ifdef CONFIG_CIFS_DFS_UPCALL
+ #include "dfs_cache.h"
+@@ -569,7 +570,7 @@ extern int E_md4hash(const unsigned char
+ extern struct TCP_Server_Info *
+ cifs_find_tcp_session(struct smb3_fs_context *ctx);
+
+-extern void cifs_put_smb_ses(struct cifs_ses *ses);
++void __cifs_put_smb_ses(struct cifs_ses *ses);
+
+ extern struct cifs_ses *
+ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx);
+@@ -699,4 +700,45 @@ struct super_block *cifs_get_tcon_super(
+ void cifs_put_tcon_super(struct super_block *sb);
+ int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);
+
++/* Put references of @ses and @ses->dfs_root_ses */
++static inline void cifs_put_smb_ses(struct cifs_ses *ses)
++{
++ struct cifs_ses *rses = ses->dfs_root_ses;
++
++ __cifs_put_smb_ses(ses);
++ if (rses)
++ __cifs_put_smb_ses(rses);
++}
++
++/* Get an active reference of @ses and @ses->dfs_root_ses.
++ *
++ * NOTE: make sure to call this function when incrementing reference count of
++ * @ses to ensure that any DFS root session attached to it (@ses->dfs_root_ses)
++ * will also get its reference count incremented.
++ *
++ * cifs_put_smb_ses() will put both references, so call it when you're done.
++ */
++static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
++{
++ lockdep_assert_held(&cifs_tcp_ses_lock);
++
++ ses->ses_count++;
++ if (ses->dfs_root_ses)
++ ses->dfs_root_ses->ses_count++;
++}
++
++static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)
++{
++ if (strlen(s1) != strlen(s2))
++ return false;
++ for (; *s1; s1++, s2++) {
++ if (*s1 == '/' || *s1 == '\\') {
++ if (*s2 != '/' && *s2 != '\\')
++ return false;
++ } else if (tolower(*s1) != tolower(*s2))
++ return false;
++ }
++ return true;
++}
++
+ #endif /* _CIFSPROTO_H */
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -1011,10 +1011,8 @@ static void clean_demultiplex_info(struc
+ */
+ }
+
+-#ifdef CONFIG_CIFS_DFS_UPCALL
+ kfree(server->origin_fullpath);
+ kfree(server->leaf_fullpath);
+-#endif
+ kfree(server);
+
+ length = atomic_dec_return(&tcpSesAllocCount);
+@@ -1425,23 +1423,8 @@ match_security(struct TCP_Server_Info *s
+ return true;
+ }
+
+-static bool dfs_src_pathname_equal(const char *s1, const char *s2)
+-{
+- if (strlen(s1) != strlen(s2))
+- return false;
+- for (; *s1; s1++, s2++) {
+- if (*s1 == '/' || *s1 == '\\') {
+- if (*s2 != '/' && *s2 != '\\')
+- return false;
+- } else if (tolower(*s1) != tolower(*s2))
+- return false;
+- }
+- return true;
+-}
+-
+ /* this function must be called with srv_lock held */
+-static int match_server(struct TCP_Server_Info *server, struct smb3_fs_context *ctx,
+- bool dfs_super_cmp)
++static int match_server(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
+ {
+ struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;
+
+@@ -1472,27 +1455,41 @@ static int match_server(struct TCP_Serve
+ (struct sockaddr *)&server->srcaddr))
+ return 0;
+ /*
+- * When matching DFS superblocks, we only check for original source pathname as the
+- * currently connected target might be different than the one parsed earlier in i.e.
+- * mount.cifs(8).
+- */
+- if (dfs_super_cmp) {
+- if (!ctx->source || !server->origin_fullpath ||
+- !dfs_src_pathname_equal(server->origin_fullpath, ctx->source))
+- return 0;
+- } else {
+- /* Skip addr, hostname and port matching for DFS connections */
+- if (server->leaf_fullpath) {
++ * - Match for an DFS tcon (@server->origin_fullpath).
++ * - Match for an DFS root server connection (@server->leaf_fullpath).
++ * - If none of the above and @ctx->leaf_fullpath is set, then
++ * it is a new DFS connection.
++ * - If 'nodfs' mount option was passed, then match only connections
++ * that have no DFS referrals set
++ * (e.g. can't failover to other targets).
++ */
++ if (!ctx->nodfs) {
++ if (ctx->source && server->origin_fullpath) {
++ if (!dfs_src_pathname_equal(ctx->source,
++ server->origin_fullpath))
++ return 0;
++ } else if (server->leaf_fullpath) {
+ if (!ctx->leaf_fullpath ||
+- strcasecmp(server->leaf_fullpath, ctx->leaf_fullpath))
++ strcasecmp(server->leaf_fullpath,
++ ctx->leaf_fullpath))
+ return 0;
+- } else if (strcasecmp(server->hostname, ctx->server_hostname) ||
+- !match_server_address(server, addr) ||
+- !match_port(server, addr)) {
++ } else if (ctx->leaf_fullpath) {
+ return 0;
+ }
++ } else if (server->origin_fullpath || server->leaf_fullpath) {
++ return 0;
+ }
+
++ /*
++ * Match for a regular connection (address/hostname/port) which has no
++ * DFS referrals set.
++ */
++ if (!server->origin_fullpath && !server->leaf_fullpath &&
++ (strcasecmp(server->hostname, ctx->server_hostname) ||
++ !match_server_address(server, addr) ||
++ !match_port(server, addr)))
++ return 0;
++
+ if (!match_security(server, ctx))
+ return 0;
+
+@@ -1523,7 +1520,7 @@ cifs_find_tcp_session(struct smb3_fs_con
+ * Skip ses channels since they're only handled in lower layers
+ * (e.g. cifs_send_recv).
+ */
+- if (CIFS_SERVER_IS_CHAN(server) || !match_server(server, ctx, false)) {
++ if (CIFS_SERVER_IS_CHAN(server) || !match_server(server, ctx)) {
+ spin_unlock(&server->srv_lock);
+ continue;
+ }
+@@ -1916,7 +1913,7 @@ cifs_free_ipc(struct cifs_ses *ses)
+ static struct cifs_ses *
+ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
+ {
+- struct cifs_ses *ses;
++ struct cifs_ses *ses, *ret = NULL;
+
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+@@ -1926,23 +1923,22 @@ cifs_find_smb_ses(struct TCP_Server_Info
+ continue;
+ }
+ spin_lock(&ses->chan_lock);
+- if (!match_session(ses, ctx)) {
++ if (match_session(ses, ctx)) {
+ spin_unlock(&ses->chan_lock);
+ spin_unlock(&ses->ses_lock);
+- continue;
++ ret = ses;
++ break;
+ }
+ spin_unlock(&ses->chan_lock);
+ spin_unlock(&ses->ses_lock);
+-
+- ++ses->ses_count;
+- spin_unlock(&cifs_tcp_ses_lock);
+- return ses;
+ }
++ if (ret)
++ cifs_smb_ses_inc_refcount(ret);
+ spin_unlock(&cifs_tcp_ses_lock);
+- return NULL;
++ return ret;
+ }
+
+-void cifs_put_smb_ses(struct cifs_ses *ses)
++void __cifs_put_smb_ses(struct cifs_ses *ses)
+ {
+ unsigned int rc, xid;
+ unsigned int chan_count;
+@@ -2293,6 +2289,8 @@ cifs_get_smb_ses(struct TCP_Server_Info
+ */
+ spin_lock(&cifs_tcp_ses_lock);
+ ses->dfs_root_ses = ctx->dfs_root_ses;
++ if (ses->dfs_root_ses)
++ ses->dfs_root_ses->ses_count++;
+ list_add(&ses->smb_ses_list, &server->smb_ses_list);
+ spin_unlock(&cifs_tcp_ses_lock);
+
+@@ -2309,12 +2307,15 @@ get_ses_fail:
+ }
+
+ /* this function must be called with tc_lock held */
+-static int match_tcon(struct cifs_tcon *tcon, struct smb3_fs_context *ctx, bool dfs_super_cmp)
++static int match_tcon(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
+ {
++ struct TCP_Server_Info *server = tcon->ses->server;
++
+ if (tcon->status == TID_EXITING)
+ return 0;
+- /* Skip UNC validation when matching DFS superblocks */
+- if (!dfs_super_cmp && strncmp(tcon->tree_name, ctx->UNC, MAX_TREE_SIZE))
++ /* Skip UNC validation when matching DFS connections or superblocks */
++ if (!server->origin_fullpath && !server->leaf_fullpath &&
++ strncmp(tcon->tree_name, ctx->UNC, MAX_TREE_SIZE))
+ return 0;
+ if (tcon->seal != ctx->seal)
+ return 0;
+@@ -2337,7 +2338,7 @@ cifs_find_tcon(struct cifs_ses *ses, str
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+ spin_lock(&tcon->tc_lock);
+- if (!match_tcon(tcon, ctx, false)) {
++ if (!match_tcon(tcon, ctx)) {
+ spin_unlock(&tcon->tc_lock);
+ continue;
+ }
+@@ -2708,9 +2709,11 @@ compare_mount_options(struct super_block
+ return 1;
+ }
+
+-static int
+-match_prepath(struct super_block *sb, struct cifs_mnt_data *mnt_data)
++static int match_prepath(struct super_block *sb,
++ struct TCP_Server_Info *server,
++ struct cifs_mnt_data *mnt_data)
+ {
++ struct smb3_fs_context *ctx = mnt_data->ctx;
+ struct cifs_sb_info *old = CIFS_SB(sb);
+ struct cifs_sb_info *new = mnt_data->cifs_sb;
+ bool old_set = (old->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) &&
+@@ -2718,6 +2721,10 @@ match_prepath(struct super_block *sb, st
+ bool new_set = (new->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) &&
+ new->prepath;
+
++ if (server->origin_fullpath &&
++ dfs_src_pathname_equal(server->origin_fullpath, ctx->source))
++ return 1;
++
+ if (old_set && new_set && !strcmp(new->prepath, old->prepath))
+ return 1;
+ else if (!old_set && !new_set)
+@@ -2736,7 +2743,6 @@ cifs_match_super(struct super_block *sb,
+ struct cifs_ses *ses;
+ struct cifs_tcon *tcon;
+ struct tcon_link *tlink;
+- bool dfs_super_cmp;
+ int rc = 0;
+
+ spin_lock(&cifs_tcp_ses_lock);
+@@ -2751,18 +2757,16 @@ cifs_match_super(struct super_block *sb,
+ ses = tcon->ses;
+ tcp_srv = ses->server;
+
+- dfs_super_cmp = IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) && tcp_srv->origin_fullpath;
+-
+ ctx = mnt_data->ctx;
+
+ spin_lock(&tcp_srv->srv_lock);
+ spin_lock(&ses->ses_lock);
+ spin_lock(&ses->chan_lock);
+ spin_lock(&tcon->tc_lock);
+- if (!match_server(tcp_srv, ctx, dfs_super_cmp) ||
++ if (!match_server(tcp_srv, ctx) ||
+ !match_session(ses, ctx) ||
+- !match_tcon(tcon, ctx, dfs_super_cmp) ||
+- !match_prepath(sb, mnt_data)) {
++ !match_tcon(tcon, ctx) ||
++ !match_prepath(sb, tcp_srv, mnt_data)) {
+ rc = 0;
+ goto out;
+ }
+@@ -3507,8 +3511,6 @@ out:
+
+ error:
+ dfs_put_root_smb_sessions(&mnt_ctx.dfs_ses_list);
+- kfree(mnt_ctx.origin_fullpath);
+- kfree(mnt_ctx.leaf_fullpath);
+ cifs_mount_put_conns(&mnt_ctx);
+ return rc;
+ }
+--- a/fs/cifs/dfs.c
++++ b/fs/cifs/dfs.c
+@@ -99,7 +99,7 @@ static int get_session(struct cifs_mount
+ return rc;
+ }
+
+-static int get_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
++static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
+ {
+ struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+ struct dfs_root_ses *root_ses;
+@@ -127,7 +127,7 @@ static int get_dfs_conn(struct cifs_moun
+ {
+ struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+ struct dfs_info3_param ref = {};
+- bool is_refsrv = false;
++ bool is_refsrv;
+ int rc, rc2;
+
+ rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
+@@ -158,7 +158,7 @@ static int get_dfs_conn(struct cifs_moun
+ }
+
+ if (rc == -EREMOTE && is_refsrv) {
+- rc2 = get_root_smb_session(mnt_ctx);
++ rc2 = add_root_smb_session(mnt_ctx);
+ if (rc2)
+ rc = rc2;
+ }
+@@ -272,15 +272,21 @@ out:
+
+ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
+ {
+- struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+ struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
++ struct cifs_ses *ses;
++ char *source = ctx->source;
++ bool nodfs = ctx->nodfs;
+ int rc;
+
+ *isdfs = false;
+-
++ /* Temporarily set @ctx->source to NULL as we're not matching DFS
++ * superblocks yet. See cifs_match_super() and match_server().
++ */
++ ctx->source = NULL;
+ rc = get_session(mnt_ctx, NULL);
+ if (rc)
+- return rc;
++ goto out;
++
+ ctx->dfs_root_ses = mnt_ctx->ses;
+ /*
+ * If called with 'nodfs' mount option, then skip DFS resolving. Otherwise unconditionally
+@@ -289,23 +295,41 @@ int dfs_mount_share(struct cifs_mount_ct
+ * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
+ * to respond with PATH_NOT_COVERED to requests that include the prefix.
+ */
+- if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) ||
+- dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL)) {
++ if (!nodfs) {
++ rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL);
++ if (rc) {
++ if (rc != -ENOENT && rc != -EOPNOTSUPP)
++ goto out;
++ nodfs = true;
++ }
++ }
++ if (nodfs) {
+ rc = cifs_mount_get_tcon(mnt_ctx);
+- if (rc)
+- return rc;
+-
+- rc = cifs_is_path_remote(mnt_ctx);
+- if (!rc || rc != -EREMOTE)
+- return rc;
++ if (!rc)
++ rc = cifs_is_path_remote(mnt_ctx);
++ goto out;
+ }
+
+ *isdfs = true;
+- rc = get_root_smb_session(mnt_ctx);
+- if (rc)
+- return rc;
+-
+- return __dfs_mount_share(mnt_ctx);
++ /*
++ * Prevent DFS root session of being put in the first call to
++ * cifs_mount_put_conns(). If another DFS root server was not found
++ * while chasing the referrals (@ctx->dfs_root_ses == @ses), then we
++ * can safely put extra refcount of @ses.
++ */
++ ses = mnt_ctx->ses;
++ mnt_ctx->ses = NULL;
++ mnt_ctx->server = NULL;
++ rc = __dfs_mount_share(mnt_ctx);
++ if (ses == ctx->dfs_root_ses)
++ cifs_put_smb_ses(ses);
++out:
++ /*
++ * Restore previous value of @ctx->source so DFS superblock can be
++ * matched in cifs_match_super().
++ */
++ ctx->source = source;
++ return rc;
+ }
+
+ /* Update dfs referral path of superblock */
+--- a/fs/cifs/ioctl.c
++++ b/fs/cifs/ioctl.c
+@@ -239,7 +239,7 @@ static int cifs_dump_full_key(struct cif
+ * section, we need to make sure it won't be released
+ * so increment its refcount
+ */
+- ses->ses_count++;
++ cifs_smb_ses_inc_refcount(ses);
+ found = true;
+ goto search_end;
+ }
+--- a/fs/cifs/smb2pdu.c
++++ b/fs/cifs/smb2pdu.c
+@@ -3858,7 +3858,7 @@ void smb2_reconnect_server(struct work_s
+ if (ses->tcon_ipc && ses->tcon_ipc->need_reconnect) {
+ list_add_tail(&ses->tcon_ipc->rlist, &tmp_list);
+ tcon_selected = tcon_exist = true;
+- ses->ses_count++;
++ cifs_smb_ses_inc_refcount(ses);
+ }
+ /*
+ * handle the case where channel needs to reconnect
+@@ -3869,7 +3869,7 @@ void smb2_reconnect_server(struct work_s
+ if (!tcon_selected && cifs_chan_needs_reconnect(ses, server)) {
+ list_add_tail(&ses->rlist, &tmp_ses_list);
+ ses_exist = true;
+- ses->ses_count++;
++ cifs_smb_ses_inc_refcount(ses);
+ }
+ spin_unlock(&ses->chan_lock);
+ }
--- /dev/null
+From 3dc9c433c9dde15477d02b609ccb4328e2adb6dc Mon Sep 17 00:00:00 2001
+From: Paulo Alcantara <pc@manguebit.com>
+Date: Wed, 26 Apr 2023 13:43:53 -0300
+Subject: cifs: protect access of TCP_Server_Info::{origin,leaf}_fullpath
+
+From: Paulo Alcantara <pc@manguebit.com>
+
+commit 3dc9c433c9dde15477d02b609ccb4328e2adb6dc upstream.
+
+Protect access of TCP_Server_Info::{origin,leaf}_fullpath when
+matching DFS connections, and get rid of
+TCP_Server_Info::current_fullpath while we're at it.
+
+Cc: stable@vger.kernel.org # v6.2+
+Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/cifs/cifsglob.h | 20 +++++++++++++-------
+ fs/cifs/connect.c | 10 ++++++----
+ fs/cifs/dfs.c | 14 ++++++++------
+ fs/cifs/dfs.h | 13 +++++++++++--
+ fs/cifs/dfs_cache.c | 6 +++++-
+ 5 files changed, 43 insertions(+), 20 deletions(-)
+
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -742,17 +742,23 @@ struct TCP_Server_Info {
+ #endif
+ struct mutex refpath_lock; /* protects leaf_fullpath */
+ /*
+- * Canonical DFS full paths that were used to chase referrals in mount and reconnect.
++ * origin_fullpath: Canonical copy of smb3_fs_context::source.
++ * It is used for matching existing DFS tcons.
+ *
+- * origin_fullpath: first or original referral path
+- * leaf_fullpath: last referral path (might be changed due to nested links in reconnect)
++ * leaf_fullpath: Canonical DFS referral path related to this
++ * connection.
++ * It is used in DFS cache refresher, reconnect and may
++ * change due to nested DFS links.
+ *
+- * current_fullpath: pointer to either origin_fullpath or leaf_fullpath
+- * NOTE: cannot be accessed outside cifs_reconnect() and smb2_reconnect()
++ * Both protected by @refpath_lock and @srv_lock. The @refpath_lock is
++ * mosly used for not requiring a copy of @leaf_fullpath when getting
++ * cached or new DFS referrals (which might also sleep during I/O).
++ * While @srv_lock is held for making string and NULL comparions against
++ * both fields as in mount(2) and cache refresh.
+ *
+- * format: \\HOST\SHARE\[OPTIONAL PATH]
++ * format: \\HOST\SHARE[\OPTIONAL PATH]
+ */
+- char *origin_fullpath, *leaf_fullpath, *current_fullpath;
++ char *origin_fullpath, *leaf_fullpath;
+ };
+
+ static inline bool is_smb1(struct TCP_Server_Info *server)
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -486,7 +486,6 @@ static int reconnect_target_unlocked(str
+ static int reconnect_dfs_server(struct TCP_Server_Info *server)
+ {
+ int rc = 0;
+- const char *refpath = server->current_fullpath + 1;
+ struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
+ struct dfs_cache_tgt_iterator *target_hint = NULL;
+ int num_targets = 0;
+@@ -499,8 +498,10 @@ static int reconnect_dfs_server(struct T
+ * through /proc/fs/cifs/dfscache or the target list is empty due to server settings after
+ * refreshing the referral, so, in this case, default it to 1.
+ */
+- if (!dfs_cache_noreq_find(refpath, NULL, &tl))
++ mutex_lock(&server->refpath_lock);
++ if (!dfs_cache_noreq_find(server->leaf_fullpath + 1, NULL, &tl))
+ num_targets = dfs_cache_get_nr_tgts(&tl);
++ mutex_unlock(&server->refpath_lock);
+ if (!num_targets)
+ num_targets = 1;
+
+@@ -544,7 +545,9 @@ static int reconnect_dfs_server(struct T
+ mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
+ } while (server->tcpStatus == CifsNeedReconnect);
+
+- dfs_cache_noreq_update_tgthint(refpath, target_hint);
++ mutex_lock(&server->refpath_lock);
++ dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, target_hint);
++ mutex_unlock(&server->refpath_lock);
+ dfs_cache_free_tgts(&tl);
+
+ /* Need to set up echo worker again once connection has been established */
+@@ -1621,7 +1624,6 @@ cifs_get_tcp_session(struct smb3_fs_cont
+ rc = -ENOMEM;
+ goto out_err;
+ }
+- tcp_ses->current_fullpath = tcp_ses->leaf_fullpath;
+ }
+
+ if (ctx->nosharesock)
+--- a/fs/cifs/dfs.c
++++ b/fs/cifs/dfs.c
+@@ -248,11 +248,12 @@ static int __dfs_mount_share(struct cifs
+ tcon = mnt_ctx->tcon;
+
+ mutex_lock(&server->refpath_lock);
++ spin_lock(&server->srv_lock);
+ if (!server->origin_fullpath) {
+ server->origin_fullpath = origin_fullpath;
+- server->current_fullpath = server->leaf_fullpath;
+ origin_fullpath = NULL;
+ }
++ spin_unlock(&server->srv_lock);
+ mutex_unlock(&server->refpath_lock);
+
+ if (list_empty(&tcon->dfs_ses_list)) {
+@@ -366,10 +367,11 @@ static int update_server_fullpath(struct
+ rc = PTR_ERR(npath);
+ } else {
+ mutex_lock(&server->refpath_lock);
++ spin_lock(&server->srv_lock);
+ kfree(server->leaf_fullpath);
+ server->leaf_fullpath = npath;
++ spin_unlock(&server->srv_lock);
+ mutex_unlock(&server->refpath_lock);
+- server->current_fullpath = server->leaf_fullpath;
+ }
+ return rc;
+ }
+@@ -474,7 +476,7 @@ static int __tree_connect_dfs_target(con
+ share = prefix = NULL;
+
+ /* Check if share matches with tcp ses */
+- rc = dfs_cache_get_tgt_share(server->current_fullpath + 1, tit, &share, &prefix);
++ rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
+ if (rc) {
+ cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
+ break;
+@@ -488,7 +490,7 @@ static int __tree_connect_dfs_target(con
+ continue;
+ }
+
+- dfs_cache_noreq_update_tgthint(server->current_fullpath + 1, tit);
++ dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
+ tree_connect_ipc(xid, tree, cifs_sb, tcon);
+
+ scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
+@@ -602,8 +604,8 @@ int cifs_tree_connect(const unsigned int
+ cifs_sb = CIFS_SB(sb);
+
+ /* If it is not dfs or there was no cached dfs referral, then reconnect to same share */
+- if (!server->current_fullpath ||
+- dfs_cache_noreq_find(server->current_fullpath + 1, &ref, &tl)) {
++ if (!server->leaf_fullpath ||
++ dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
+ rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, cifs_sb->local_nls);
+ goto out;
+ }
+--- a/fs/cifs/dfs.h
++++ b/fs/cifs/dfs.h
+@@ -43,8 +43,12 @@ static inline char *dfs_get_automount_de
+ size_t len;
+ char *s;
+
+- if (unlikely(!server->origin_fullpath))
++ spin_lock(&server->srv_lock);
++ if (unlikely(!server->origin_fullpath)) {
++ spin_unlock(&server->srv_lock);
+ return ERR_PTR(-EREMOTE);
++ }
++ spin_unlock(&server->srv_lock);
+
+ s = dentry_path_raw(dentry, page, PATH_MAX);
+ if (IS_ERR(s))
+@@ -53,13 +57,18 @@ static inline char *dfs_get_automount_de
+ if (!s[1])
+ s++;
+
++ spin_lock(&server->srv_lock);
+ len = strlen(server->origin_fullpath);
+- if (s < (char *)page + len)
++ if (s < (char *)page + len) {
++ spin_unlock(&server->srv_lock);
+ return ERR_PTR(-ENAMETOOLONG);
++ }
+
+ s -= len;
+ memcpy(s, server->origin_fullpath, len);
++ spin_unlock(&server->srv_lock);
+ convert_delimiter(s, '/');
++
+ return s;
+ }
+
+--- a/fs/cifs/dfs_cache.c
++++ b/fs/cifs/dfs_cache.c
+@@ -1278,8 +1278,12 @@ static void refresh_cache_worker(struct
+
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+- if (!server->leaf_fullpath)
++ spin_lock(&server->srv_lock);
++ if (!server->leaf_fullpath) {
++ spin_unlock(&server->srv_lock);
+ continue;
++ }
++ spin_unlock(&server->srv_lock);
+
+ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+ if (ses->tcon_ipc) {
--- /dev/null
+From 5bff9f741af60b143a5ae73417a8ec47fd5ff2f4 Mon Sep 17 00:00:00 2001
+From: Paulo Alcantara <pc@manguebit.com>
+Date: Thu, 27 Apr 2023 16:07:38 -0300
+Subject: cifs: protect session status check in smb2_reconnect()
+
+From: Paulo Alcantara <pc@manguebit.com>
+
+commit 5bff9f741af60b143a5ae73417a8ec47fd5ff2f4 upstream.
+
+Use @ses->ses_lock to protect access of @ses->ses_status.
+
+Cc: stable@vger.kernel.org
+Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/cifs/smb2pdu.c | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+--- a/fs/cifs/smb2pdu.c
++++ b/fs/cifs/smb2pdu.c
+@@ -175,8 +175,17 @@ smb2_reconnect(__le16 smb2_command, stru
+ }
+ }
+ spin_unlock(&tcon->tc_lock);
+- if ((!tcon->ses) || (tcon->ses->ses_status == SES_EXITING) ||
+- (!tcon->ses->server) || !server)
++
++ ses = tcon->ses;
++ if (!ses)
++ return -EIO;
++ spin_lock(&ses->ses_lock);
++ if (ses->ses_status == SES_EXITING) {
++ spin_unlock(&ses->ses_lock);
++ return -EIO;
++ }
++ spin_unlock(&ses->ses_lock);
++ if (!ses->server || !server)
+ return -EIO;
+
+ spin_lock(&server->srv_lock);
+@@ -204,8 +213,6 @@ again:
+ if (rc)
+ return rc;
+
+- ses = tcon->ses;
+-
+ spin_lock(&ses->chan_lock);
+ if (!cifs_chan_needs_reconnect(ses, server) && !tcon->need_reconnect) {
+ spin_unlock(&ses->chan_lock);
dm-flakey-fix-a-crash-with-invalid-table-line.patch
dm-ioctl-fix-nested-locking-in-table_clear-to-remove-deadlock-concern.patch
dm-don-t-lock-fs-when-the-map-is-null-in-process-of-resume.patch
+cifs-fix-potential-use-after-free-bugs-in-tcp_server_info-hostname.patch
+cifs-protect-session-status-check-in-smb2_reconnect.patch
+cifs-fix-sharing-of-dfs-connections.patch
+cifs-fix-potential-race-when-tree-connecting-ipc.patch
+cifs-protect-access-of-tcp_server_info-origin-leaf-_fullpath.patch