]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.3-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 7 May 2023 18:23:37 +0000 (20:23 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 7 May 2023 18:23:37 +0000 (20:23 +0200)
added patches:
cifs-avoid-potential-races-when-handling-multiple-dfs-tcons.patch
cifs-fix-potential-race-when-tree-connecting-ipc.patch
cifs-fix-potential-use-after-free-bugs-in-tcp_server_info-hostname.patch
cifs-fix-sharing-of-dfs-connections.patch
cifs-protect-access-of-tcp_server_info-origin-leaf-_fullpath.patch
cifs-protect-session-status-check-in-smb2_reconnect.patch
io_uring-rsrc-check-for-nonconsecutive-pages.patch

queue-6.3/cifs-avoid-potential-races-when-handling-multiple-dfs-tcons.patch [new file with mode: 0644]
queue-6.3/cifs-fix-potential-race-when-tree-connecting-ipc.patch [new file with mode: 0644]
queue-6.3/cifs-fix-potential-use-after-free-bugs-in-tcp_server_info-hostname.patch [new file with mode: 0644]
queue-6.3/cifs-fix-sharing-of-dfs-connections.patch [new file with mode: 0644]
queue-6.3/cifs-protect-access-of-tcp_server_info-origin-leaf-_fullpath.patch [new file with mode: 0644]
queue-6.3/cifs-protect-session-status-check-in-smb2_reconnect.patch [new file with mode: 0644]
queue-6.3/io_uring-rsrc-check-for-nonconsecutive-pages.patch [new file with mode: 0644]
queue-6.3/series

diff --git a/queue-6.3/cifs-avoid-potential-races-when-handling-multiple-dfs-tcons.patch b/queue-6.3/cifs-avoid-potential-races-when-handling-multiple-dfs-tcons.patch
new file mode 100644 (file)
index 0000000..55cd816
--- /dev/null
@@ -0,0 +1,379 @@
+From 6be2ea33a4093402252724a00c4af8033725184c Mon Sep 17 00:00:00 2001
+From: Paulo Alcantara <pc@manguebit.com>
+Date: Thu, 27 Apr 2023 04:40:08 -0300
+Subject: cifs: avoid potential races when handling multiple dfs tcons
+
+From: Paulo Alcantara <pc@manguebit.com>
+
+commit 6be2ea33a4093402252724a00c4af8033725184c upstream.
+
+Now that a DFS tcon manages its own list of DFS referrals and
+sessions, there is no point in having a single worker to refresh
+referrals of all DFS tcons.  Make it faster and less prone to race
+conditions when having several mounts by queueing a worker per DFS
+tcon that will take care of refreshing only the DFS referrals related
+to 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  |    2 
+ fs/cifs/connect.c   |    7 ++
+ fs/cifs/dfs.c       |    4 +
+ fs/cifs/dfs_cache.c |  137 ++++++++++++++++++++++------------------------------
+ fs/cifs/dfs_cache.h |    9 +++
+ 5 files changed, 80 insertions(+), 79 deletions(-)
+
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -1238,8 +1238,8 @@ struct cifs_tcon {
+       struct cached_fids *cfids;
+       /* BB add field for back pointer to sb struct(s)? */
+ #ifdef CONFIG_CIFS_DFS_UPCALL
+-      struct list_head ulist; /* cache update list */
+       struct list_head dfs_ses_list;
++      struct delayed_work dfs_cache_work;
+ #endif
+       struct delayed_work     query_interfaces; /* query interfaces workqueue job */
+ };
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -2337,6 +2337,9 @@ cifs_put_tcon(struct cifs_tcon *tcon)
+       /* cancel polling of interfaces */
+       cancel_delayed_work_sync(&tcon->query_interfaces);
++#ifdef CONFIG_CIFS_DFS_UPCALL
++      cancel_delayed_work_sync(&tcon->dfs_cache_work);
++#endif
+       if (tcon->use_witness) {
+               int rc;
+@@ -2584,7 +2587,9 @@ cifs_get_tcon(struct cifs_ses *ses, stru
+               queue_delayed_work(cifsiod_wq, &tcon->query_interfaces,
+                                  (SMB_INTERFACE_POLL_INTERVAL * HZ));
+       }
+-
++#ifdef CONFIG_CIFS_DFS_UPCALL
++      INIT_DELAYED_WORK(&tcon->dfs_cache_work, dfs_cache_refresh);
++#endif
+       spin_lock(&cifs_tcp_ses_lock);
+       list_add(&tcon->tcon_list, &ses->tcon_list);
+       spin_unlock(&cifs_tcp_ses_lock);
+--- a/fs/cifs/dfs.c
++++ b/fs/cifs/dfs.c
+@@ -157,6 +157,8 @@ static int get_dfs_conn(struct cifs_moun
+               rc = cifs_is_path_remote(mnt_ctx);
+       }
++      dfs_cache_noreq_update_tgthint(ref_path + 1, tit);
++
+       if (rc == -EREMOTE && is_refsrv) {
+               rc2 = add_root_smb_session(mnt_ctx);
+               if (rc2)
+@@ -259,6 +261,8 @@ static int __dfs_mount_share(struct cifs
+               if (list_empty(&tcon->dfs_ses_list)) {
+                       list_replace_init(&mnt_ctx->dfs_ses_list,
+                                         &tcon->dfs_ses_list);
++                      queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
++                                         dfs_cache_get_ttl() * HZ);
+               } else {
+                       dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
+               }
+--- a/fs/cifs/dfs_cache.c
++++ b/fs/cifs/dfs_cache.c
+@@ -20,12 +20,14 @@
+ #include "cifs_unicode.h"
+ #include "smb2glob.h"
+ #include "dns_resolve.h"
++#include "dfs.h"
+ #include "dfs_cache.h"
+-#define CACHE_HTABLE_SIZE 32
+-#define CACHE_MAX_ENTRIES 64
+-#define CACHE_MIN_TTL 120 /* 2 minutes */
++#define CACHE_HTABLE_SIZE     32
++#define CACHE_MAX_ENTRIES     64
++#define CACHE_MIN_TTL         120 /* 2 minutes */
++#define CACHE_DEFAULT_TTL     300 /* 5 minutes */
+ #define IS_DFS_INTERLINK(v) (((v) & DFSREF_REFERRAL_SERVER) && !((v) & DFSREF_STORAGE_SERVER))
+@@ -50,10 +52,9 @@ struct cache_entry {
+ };
+ static struct kmem_cache *cache_slab __read_mostly;
+-static struct workqueue_struct *dfscache_wq __read_mostly;
++struct workqueue_struct *dfscache_wq;
+-static int cache_ttl;
+-static DEFINE_SPINLOCK(cache_ttl_lock);
++atomic_t dfs_cache_ttl;
+ static struct nls_table *cache_cp;
+@@ -65,10 +66,6 @@ static atomic_t cache_count;
+ static struct hlist_head cache_htable[CACHE_HTABLE_SIZE];
+ static DECLARE_RWSEM(htable_rw_lock);
+-static void refresh_cache_worker(struct work_struct *work);
+-
+-static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker);
+-
+ /**
+  * dfs_cache_canonical_path - get a canonical DFS path
+  *
+@@ -290,7 +287,9 @@ int dfs_cache_init(void)
+       int rc;
+       int i;
+-      dfscache_wq = alloc_workqueue("cifs-dfscache", WQ_FREEZABLE | WQ_UNBOUND, 1);
++      dfscache_wq = alloc_workqueue("cifs-dfscache",
++                                    WQ_UNBOUND|WQ_FREEZABLE|WQ_MEM_RECLAIM,
++                                    0);
+       if (!dfscache_wq)
+               return -ENOMEM;
+@@ -306,6 +305,7 @@ int dfs_cache_init(void)
+               INIT_HLIST_HEAD(&cache_htable[i]);
+       atomic_set(&cache_count, 0);
++      atomic_set(&dfs_cache_ttl, CACHE_DEFAULT_TTL);
+       cache_cp = load_nls("utf8");
+       if (!cache_cp)
+               cache_cp = load_nls_default();
+@@ -480,6 +480,7 @@ static struct cache_entry *add_cache_ent
+       int rc;
+       struct cache_entry *ce;
+       unsigned int hash;
++      int ttl;
+       WARN_ON(!rwsem_is_locked(&htable_rw_lock));
+@@ -496,15 +497,8 @@ static struct cache_entry *add_cache_ent
+       if (IS_ERR(ce))
+               return ce;
+-      spin_lock(&cache_ttl_lock);
+-      if (!cache_ttl) {
+-              cache_ttl = ce->ttl;
+-              queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
+-      } else {
+-              cache_ttl = min_t(int, cache_ttl, ce->ttl);
+-              mod_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
+-      }
+-      spin_unlock(&cache_ttl_lock);
++      ttl = min_t(int, atomic_read(&dfs_cache_ttl), ce->ttl);
++      atomic_set(&dfs_cache_ttl, ttl);
+       hlist_add_head(&ce->hlist, &cache_htable[hash]);
+       dump_ce(ce);
+@@ -616,7 +610,6 @@ static struct cache_entry *lookup_cache_
+  */
+ void dfs_cache_destroy(void)
+ {
+-      cancel_delayed_work_sync(&refresh_task);
+       unload_nls(cache_cp);
+       flush_cache_ents();
+       kmem_cache_destroy(cache_slab);
+@@ -1142,6 +1135,7 @@ static bool target_share_equal(struct TC
+  * target shares in @refs.
+  */
+ static void mark_for_reconnect_if_needed(struct TCP_Server_Info *server,
++                                       const char *path,
+                                        struct dfs_cache_tgt_list *old_tl,
+                                        struct dfs_cache_tgt_list *new_tl)
+ {
+@@ -1153,8 +1147,10 @@ static void mark_for_reconnect_if_needed
+                    nit = dfs_cache_get_next_tgt(new_tl, nit)) {
+                       if (target_share_equal(server,
+                                              dfs_cache_get_tgt_name(oit),
+-                                             dfs_cache_get_tgt_name(nit)))
++                                             dfs_cache_get_tgt_name(nit))) {
++                              dfs_cache_noreq_update_tgthint(path, nit);
+                               return;
++                      }
+               }
+       }
+@@ -1162,13 +1158,28 @@ static void mark_for_reconnect_if_needed
+       cifs_signal_cifsd_for_reconnect(server, true);
+ }
++static bool is_ses_good(struct cifs_ses *ses)
++{
++      struct TCP_Server_Info *server = ses->server;
++      struct cifs_tcon *tcon = ses->tcon_ipc;
++      bool ret;
++
++      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;
++      spin_unlock(&ses->chan_lock);
++      spin_unlock(&ses->ses_lock);
++      return ret;
++}
++
+ /* Refresh dfs referral of tcon and mark it for reconnect if needed */
+-static int __refresh_tcon(const char *path, struct cifs_tcon *tcon, bool force_refresh)
++static int __refresh_tcon(const char *path, struct cifs_ses *ses, bool force_refresh)
+ {
+       struct dfs_cache_tgt_list old_tl = DFS_CACHE_TGT_LIST_INIT(old_tl);
+       struct dfs_cache_tgt_list new_tl = DFS_CACHE_TGT_LIST_INIT(new_tl);
+-      struct cifs_ses *ses = CIFS_DFS_ROOT_SES(tcon->ses);
+-      struct cifs_tcon *ipc = ses->tcon_ipc;
++      struct TCP_Server_Info *server = ses->server;
+       bool needs_refresh = false;
+       struct cache_entry *ce;
+       unsigned int xid;
+@@ -1190,20 +1201,19 @@ static int __refresh_tcon(const char *pa
+               goto out;
+       }
+-      spin_lock(&ipc->tc_lock);
+-      if (ipc->status != TID_GOOD) {
+-              spin_unlock(&ipc->tc_lock);
+-              cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n", __func__);
++      ses = CIFS_DFS_ROOT_SES(ses);
++      if (!is_ses_good(ses)) {
++              cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n",
++                       __func__);
+               goto out;
+       }
+-      spin_unlock(&ipc->tc_lock);
+       ce = cache_refresh_path(xid, ses, path, true);
+       if (!IS_ERR(ce)) {
+               rc = get_targets(ce, &new_tl);
+               up_read(&htable_rw_lock);
+               cifs_dbg(FYI, "%s: get_targets: %d\n", __func__, rc);
+-              mark_for_reconnect_if_needed(tcon->ses->server, &old_tl, &new_tl);
++              mark_for_reconnect_if_needed(server, path, &old_tl, &new_tl);
+       }
+ out:
+@@ -1216,10 +1226,11 @@ out:
+ static int refresh_tcon(struct cifs_tcon *tcon, bool force_refresh)
+ {
+       struct TCP_Server_Info *server = tcon->ses->server;
++      struct cifs_ses *ses = tcon->ses;
+       mutex_lock(&server->refpath_lock);
+       if (server->leaf_fullpath)
+-              __refresh_tcon(server->leaf_fullpath + 1, tcon, force_refresh);
++              __refresh_tcon(server->leaf_fullpath + 1, ses, force_refresh);
+       mutex_unlock(&server->refpath_lock);
+       return 0;
+ }
+@@ -1263,60 +1274,32 @@ int dfs_cache_remount_fs(struct cifs_sb_
+       return refresh_tcon(tcon, true);
+ }
+-/*
+- * Worker that will refresh DFS cache from all active mounts based on lowest TTL value
+- * from a DFS referral.
+- */
+-static void refresh_cache_worker(struct work_struct *work)
++/* Refresh all DFS referrals related to DFS tcon */
++void dfs_cache_refresh(struct work_struct *work)
+ {
+       struct TCP_Server_Info *server;
+-      struct cifs_tcon *tcon, *ntcon;
+-      struct list_head tcons;
++      struct dfs_root_ses *rses;
++      struct cifs_tcon *tcon;
+       struct cifs_ses *ses;
+-      INIT_LIST_HEAD(&tcons);
++      tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
++      ses = tcon->ses;
++      server = ses->server;
+-      spin_lock(&cifs_tcp_ses_lock);
+-      list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+-              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) {
+-                              ses->ses_count++;
+-                              list_add_tail(&ses->tcon_ipc->ulist, &tcons);
+-                      }
+-                      list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+-                              if (!tcon->ipc) {
+-                                      tcon->tc_count++;
+-                                      list_add_tail(&tcon->ulist, &tcons);
+-                              }
+-                      }
+-              }
+-      }
+-      spin_unlock(&cifs_tcp_ses_lock);
+-
+-      list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
+-              struct TCP_Server_Info *server = tcon->ses->server;
+-
+-              list_del_init(&tcon->ulist);
++      mutex_lock(&server->refpath_lock);
++      if (server->leaf_fullpath)
++              __refresh_tcon(server->leaf_fullpath + 1, ses, false);
++      mutex_unlock(&server->refpath_lock);
++      list_for_each_entry(rses, &tcon->dfs_ses_list, list) {
++              ses = rses->ses;
++              server = ses->server;
+               mutex_lock(&server->refpath_lock);
+               if (server->leaf_fullpath)
+-                      __refresh_tcon(server->leaf_fullpath + 1, tcon, false);
++                      __refresh_tcon(server->leaf_fullpath + 1, ses, false);
+               mutex_unlock(&server->refpath_lock);
+-
+-              if (tcon->ipc)
+-                      cifs_put_smb_ses(tcon->ses);
+-              else
+-                      cifs_put_tcon(tcon);
+       }
+-      spin_lock(&cache_ttl_lock);
+-      queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
+-      spin_unlock(&cache_ttl_lock);
++      queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
++                         atomic_read(&dfs_cache_ttl) * HZ);
+ }
+--- a/fs/cifs/dfs_cache.h
++++ b/fs/cifs/dfs_cache.h
+@@ -13,6 +13,9 @@
+ #include <linux/uuid.h>
+ #include "cifsglob.h"
++extern struct workqueue_struct *dfscache_wq;
++extern atomic_t dfs_cache_ttl;
++
+ #define DFS_CACHE_TGT_LIST_INIT(var) { .tl_numtgts = 0, .tl_list = LIST_HEAD_INIT((var).tl_list), }
+ struct dfs_cache_tgt_list {
+@@ -42,6 +45,7 @@ int dfs_cache_get_tgt_share(char *path,
+                           char **prefix);
+ char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap);
+ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb);
++void dfs_cache_refresh(struct work_struct *work);
+ static inline struct dfs_cache_tgt_iterator *
+ dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl,
+@@ -89,4 +93,9 @@ dfs_cache_get_nr_tgts(const struct dfs_c
+       return tl ? tl->tl_numtgts : 0;
+ }
++static inline int dfs_cache_get_ttl(void)
++{
++      return atomic_read(&dfs_cache_ttl);
++}
++
+ #endif /* _CIFS_DFS_CACHE_H */
diff --git a/queue-6.3/cifs-fix-potential-race-when-tree-connecting-ipc.patch b/queue-6.3/cifs-fix-potential-race-when-tree-connecting-ipc.patch
new file mode 100644 (file)
index 0000000..a0e808d
--- /dev/null
@@ -0,0 +1,109 @@
+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
diff --git a/queue-6.3/cifs-fix-potential-use-after-free-bugs-in-tcp_server_info-hostname.patch b/queue-6.3/cifs-fix-potential-use-after-free-bugs-in-tcp_server_info-hostname.patch
new file mode 100644 (file)
index 0000000..e5a8f87
--- /dev/null
@@ -0,0 +1,153 @@
+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
+@@ -280,8 +280,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;
+@@ -623,10 +625,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
+@@ -403,8 +403,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));
+@@ -561,9 +563,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();
+@@ -1404,6 +1404,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;
+@@ -1810,7 +1812,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);
diff --git a/queue-6.3/cifs-fix-sharing-of-dfs-connections.patch b/queue-6.3/cifs-fix-sharing-of-dfs-connections.patch
new file mode 100644 (file)
index 0000000..7c93af8
--- /dev/null
@@ -0,0 +1,501 @@
+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
+@@ -1750,7 +1750,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"
+@@ -572,7 +573,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);
+@@ -696,4 +697,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
+@@ -993,10 +993,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);
+@@ -1384,23 +1382,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;
+@@ -1431,27 +1414,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;
+@@ -1482,7 +1479,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;
+               }
+@@ -1867,7 +1864,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) {
+@@ -1877,23 +1874,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;
+@@ -2244,6 +2240,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);
+@@ -2260,12 +2258,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;
+@@ -2288,7 +2289,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;
+               }
+@@ -2659,9 +2660,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) &&
+@@ -2669,6 +2672,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)
+@@ -2687,7 +2694,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);
+@@ -2702,18 +2708,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;
+       }
+@@ -3458,8 +3462,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
+@@ -3856,7 +3856,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
+@@ -3867,7 +3867,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);
+       }
diff --git a/queue-6.3/cifs-protect-access-of-tcp_server_info-origin-leaf-_fullpath.patch b/queue-6.3/cifs-protect-access-of-tcp_server_info-origin-leaf-_fullpath.patch
new file mode 100644 (file)
index 0000000..db43879
--- /dev/null
@@ -0,0 +1,209 @@
+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
+@@ -736,17 +736,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
+@@ -454,7 +454,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;
+@@ -467,8 +466,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;
+@@ -512,7 +513,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 */
+@@ -1579,7 +1582,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);
+@@ -606,8 +608,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) {
diff --git a/queue-6.3/cifs-protect-session-status-check-in-smb2_reconnect.patch b/queue-6.3/cifs-protect-session-status-check-in-smb2_reconnect.patch
new file mode 100644 (file)
index 0000000..b15f09d
--- /dev/null
@@ -0,0 +1,50 @@
+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);
diff --git a/queue-6.3/io_uring-rsrc-check-for-nonconsecutive-pages.patch b/queue-6.3/io_uring-rsrc-check-for-nonconsecutive-pages.patch
new file mode 100644 (file)
index 0000000..f4edd70
--- /dev/null
@@ -0,0 +1,41 @@
+From 776617db78c6d208780e7c69d4d68d1fa82913de Mon Sep 17 00:00:00 2001
+From: Tobias Holl <tobias@tholl.xyz>
+Date: Wed, 3 May 2023 08:59:50 -0600
+Subject: io_uring/rsrc: check for nonconsecutive pages
+
+From: Tobias Holl <tobias@tholl.xyz>
+
+commit 776617db78c6d208780e7c69d4d68d1fa82913de upstream.
+
+Pages that are from the same folio do not necessarily need to be
+consecutive. In that case, we cannot consolidate them into a single bvec
+entry. Before applying the huge page optimization from commit 57bebf807e2a
+("io_uring/rsrc: optimise registered huge pages"), check that the memory
+is actually consecutive.
+
+Cc: stable@vger.kernel.org
+Fixes: 57bebf807e2a ("io_uring/rsrc: optimise registered huge pages")
+Signed-off-by: Tobias Holl <tobias@tholl.xyz>
+[axboe: formatting]
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ io_uring/rsrc.c |    7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+--- a/io_uring/rsrc.c
++++ b/io_uring/rsrc.c
+@@ -1230,7 +1230,12 @@ static int io_sqe_buffer_register(struct
+       if (nr_pages > 1) {
+               folio = page_folio(pages[0]);
+               for (i = 1; i < nr_pages; i++) {
+-                      if (page_folio(pages[i]) != folio) {
++                      /*
++                       * Pages must be consecutive and on the same folio for
++                       * this to work
++                       */
++                      if (page_folio(pages[i]) != folio ||
++                          pages[i] != pages[i - 1] + 1) {
+                               folio = NULL;
+                               break;
+                       }
index 5f24d02a5696aa8ddd3d164588bad00ed70072b4..0e9f4e51437a6b2ec5cb04253e94f76aca2d23a6 100644 (file)
@@ -690,3 +690,10 @@ dm-integrity-call-kmem_cache_destroy-in-dm_integrity_init-error-path.patch
 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
+io_uring-rsrc-check-for-nonconsecutive-pages.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
+cifs-avoid-potential-races-when-handling-multiple-dfs-tcons.patch