From: Greg Kroah-Hartman Date: Sun, 7 May 2023 18:23:37 +0000 (+0200) Subject: 6.3-stable patches X-Git-Tag: v5.15.111~58 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7f00e948fe5a637d07debc6ce64d5aedf9862240;p=thirdparty%2Fkernel%2Fstable-queue.git 6.3-stable patches 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 --- 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 index 00000000000..55cd816f341 --- /dev/null +++ b/queue-6.3/cifs-avoid-potential-races-when-handling-multiple-dfs-tcons.patch @@ -0,0 +1,379 @@ +From 6be2ea33a4093402252724a00c4af8033725184c Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Thu, 27 Apr 2023 04:40:08 -0300 +Subject: cifs: avoid potential races when handling multiple dfs tcons + +From: Paulo Alcantara + +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) +Signed-off-by: Steve French +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #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 index 00000000000..a0e808dfb4a --- /dev/null +++ b/queue-6.3/cifs-fix-potential-race-when-tree-connecting-ipc.patch @@ -0,0 +1,109 @@ +From ee20d7c6100752eaf2409d783f4f1449c29ea33d Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Tue, 25 Apr 2023 02:42:56 -0300 +Subject: cifs: fix potential race when tree connecting ipc + +From: Paulo Alcantara + +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) +Signed-off-by: Steve French +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 00000000000..e5a8f8786d4 --- /dev/null +++ b/queue-6.3/cifs-fix-potential-use-after-free-bugs-in-tcp_server_info-hostname.patch @@ -0,0 +1,153 @@ +From 90c49fce1c43e1cc152695e20363ff5087897c09 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +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 + +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) +Signed-off-by: Steve French +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 00000000000..7c93af8ab3a --- /dev/null +++ b/queue-6.3/cifs-fix-sharing-of-dfs-connections.patch @@ -0,0 +1,501 @@ +From 8e3554150d6c80a84b3cb046615d1a0e943811dc Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Sun, 23 Apr 2023 23:26:51 -0300 +Subject: cifs: fix sharing of DFS connections + +From: Paulo Alcantara + +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 +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) +Signed-off-by: Steve French +Signed-off-by: Greg Kroah-Hartman +--- + 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 ++#include + #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 index 00000000000..db43879def7 --- /dev/null +++ b/queue-6.3/cifs-protect-access-of-tcp_server_info-origin-leaf-_fullpath.patch @@ -0,0 +1,209 @@ +From 3dc9c433c9dde15477d02b609ccb4328e2adb6dc Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Wed, 26 Apr 2023 13:43:53 -0300 +Subject: cifs: protect access of TCP_Server_Info::{origin,leaf}_fullpath + +From: Paulo Alcantara + +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) +Signed-off-by: Steve French +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 00000000000..b15f09d1cad --- /dev/null +++ b/queue-6.3/cifs-protect-session-status-check-in-smb2_reconnect.patch @@ -0,0 +1,50 @@ +From 5bff9f741af60b143a5ae73417a8ec47fd5ff2f4 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Thu, 27 Apr 2023 16:07:38 -0300 +Subject: cifs: protect session status check in smb2_reconnect() + +From: Paulo Alcantara + +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) +Signed-off-by: Steve French +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 00000000000..f4edd702d87 --- /dev/null +++ b/queue-6.3/io_uring-rsrc-check-for-nonconsecutive-pages.patch @@ -0,0 +1,41 @@ +From 776617db78c6d208780e7c69d4d68d1fa82913de Mon Sep 17 00:00:00 2001 +From: Tobias Holl +Date: Wed, 3 May 2023 08:59:50 -0600 +Subject: io_uring/rsrc: check for nonconsecutive pages + +From: Tobias Holl + +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 +[axboe: formatting] +Signed-off-by: Jens Axboe +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } diff --git a/queue-6.3/series b/queue-6.3/series index 5f24d02a569..0e9f4e51437 100644 --- a/queue-6.3/series +++ b/queue-6.3/series @@ -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