From cc15461c73d7d044d56c47e869a215e49bd429c8 Mon Sep 17 00:00:00 2001 From: Paul Aurich Date: Fri, 26 Jun 2020 12:58:05 -0700 Subject: SMB3: Honor 'seal' flag for multiuser mounts Ensure multiuser SMB3 mounts use encryption for all users' tcons if the mount options are configured to require encryption. Without this, only the primary tcon and IPC tcons are guaranteed to be encrypted. Per-user tcons would only be encrypted if the server was configured to require encryption. Signed-off-by: Paul Aurich CC: Stable Signed-off-by: Steve French Reviewed-by: Aurelien Aptel --- fs/cifs/connect.c | 1 + 1 file changed, 1 insertion(+) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 5fac34f192af..804509f7f3a1 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5309,6 +5309,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) vol_info->no_linux_ext = !master_tcon->unix_ext; vol_info->sectype = master_tcon->ses->sectype; vol_info->sign = master_tcon->ses->sign; + vol_info->seal = master_tcon->seal; rc = cifs_set_vol_auth(vol_info, master_tcon->ses); if (rc) { -- cgit From 00dfbc2f9c61185a2e662f27c45a0bb29b2a134f Mon Sep 17 00:00:00 2001 From: Paul Aurich Date: Fri, 26 Jun 2020 12:58:06 -0700 Subject: SMB3: Honor persistent/resilient handle flags for multiuser mounts Without this: - persistent handles will only be enabled for per-user tcons if the server advertises the 'Continuous Availabity' capability - resilient handles would never be enabled for per-user tcons Signed-off-by: Paul Aurich CC: Stable Signed-off-by: Steve French Reviewed-by: Aurelien Aptel --- fs/cifs/connect.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 804509f7f3a1..dc7f875d2caf 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5306,6 +5306,8 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) vol_info->nocase = master_tcon->nocase; vol_info->nohandlecache = master_tcon->nohandlecache; vol_info->local_lease = master_tcon->local_lease; + vol_info->resilient = master_tcon->use_resilient; + vol_info->persistent = master_tcon->use_persistent; vol_info->no_linux_ext = !master_tcon->unix_ext; vol_info->sectype = master_tcon->ses->sectype; vol_info->sign = master_tcon->ses->sign; -- cgit From ad35f169db6cd5a4c5c0a5a42fb0cad3efeccb83 Mon Sep 17 00:00:00 2001 From: Paul Aurich Date: Fri, 26 Jun 2020 12:58:07 -0700 Subject: SMB3: Honor lease disabling for multiuser mounts Fixes: 3e7a02d47872 ("smb3: allow disabling requesting leases") Signed-off-by: Paul Aurich CC: Stable Signed-off-by: Steve French Reviewed-by: Aurelien Aptel --- fs/cifs/connect.c | 1 + 1 file changed, 1 insertion(+) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index dc7f875d2caf..6e71a1578e34 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5306,6 +5306,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) vol_info->nocase = master_tcon->nocase; vol_info->nohandlecache = master_tcon->nohandlecache; vol_info->local_lease = master_tcon->local_lease; + vol_info->no_lease = master_tcon->no_lease; vol_info->resilient = master_tcon->use_resilient; vol_info->persistent = master_tcon->use_persistent; vol_info->no_linux_ext = !master_tcon->unix_ext; -- cgit From 6b356f6cf941d5054d7fab072cae4a5f8658e3db Mon Sep 17 00:00:00 2001 From: Paul Aurich Date: Fri, 26 Jun 2020 12:58:08 -0700 Subject: SMB3: Honor 'handletimeout' flag for multiuser mounts Fixes: ca567eb2b3f0 ("SMB3: Allow persistent handle timeout to be configurable on mount") Signed-off-by: Paul Aurich CC: Stable Signed-off-by: Steve French Reviewed-by: Aurelien Aptel --- fs/cifs/connect.c | 1 + 1 file changed, 1 insertion(+) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 6e71a1578e34..dada6d51e034 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5309,6 +5309,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) vol_info->no_lease = master_tcon->no_lease; vol_info->resilient = master_tcon->use_resilient; vol_info->persistent = master_tcon->use_persistent; + vol_info->handle_timeout = master_tcon->handle_timeout; vol_info->no_linux_ext = !master_tcon->unix_ext; vol_info->sectype = master_tcon->ses->sectype; vol_info->sign = master_tcon->ses->sign; -- cgit From 5391b8e1b7b7e5cfa2dd4ffdc4b8c6b64dfd1866 Mon Sep 17 00:00:00 2001 From: Paul Aurich Date: Fri, 26 Jun 2020 12:58:09 -0700 Subject: SMB3: Honor 'posix' flag for multiuser mounts The flag from the primary tcon needs to be copied into the volume info so that cifs_get_tcon will try to enable extensions on the per-user tcon. At that point, since posix extensions must have already been enabled on the superblock, don't try to needlessly adjust the mount flags. Fixes: ce558b0e17f8 ("smb3: Add posix create context for smb3.11 posix mounts") Fixes: b326614ea215 ("smb3: allow "posix" mount option to enable new SMB311 protocol extensions") Signed-off-by: Paul Aurich Signed-off-by: Steve French Reviewed-by: Aurelien Aptel --- fs/cifs/connect.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index dada6d51e034..a61abde09ffe 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5311,6 +5311,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) vol_info->persistent = master_tcon->use_persistent; vol_info->handle_timeout = master_tcon->handle_timeout; vol_info->no_linux_ext = !master_tcon->unix_ext; + vol_info->linux_ext = master_tcon->posix_extensions; vol_info->sectype = master_tcon->ses->sectype; vol_info->sign = master_tcon->ses->sign; vol_info->seal = master_tcon->seal; @@ -5339,10 +5340,6 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) goto out; } - /* if new SMB3.11 POSIX extensions are supported do not remap / and \ */ - if (tcon->posix_extensions) - cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS; - if (cap_unix(ses)) reset_cifs_unix_caps(0, tcon, NULL, vol_info); -- cgit From 1a0e7f7c3c573a79bcd787d8a05e80651041b815 Mon Sep 17 00:00:00 2001 From: Qinglang Miao Date: Sat, 25 Jul 2020 16:56:01 +0800 Subject: cifs: convert to use be32_add_cpu() Convert cpu_to_be32(be32_to_cpu(E1) + E2) to use be32_add_cpu(). Signed-off-by: Qinglang Miao Signed-off-by: Steve French --- fs/cifs/connect.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index a61abde09ffe..fe4b55977dd8 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5114,8 +5114,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, bcc_ptr += strlen("?????"); bcc_ptr += 1; count = bcc_ptr - &pSMB->Password[0]; - pSMB->hdr.smb_buf_length = cpu_to_be32(be32_to_cpu( - pSMB->hdr.smb_buf_length) + count); + be32_add_cpu(&pSMB->hdr.smb_buf_length, count); pSMB->ByteCount = cpu_to_le16(count); rc = SendReceive(xid, ses, smb_buffer, smb_buffer_response, &length, -- cgit From 565674d613d7f45d87284ba9f0be730ce4b335d0 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 21 Jul 2020 09:36:38 -0300 Subject: cifs: merge __{cifs,smb2}_reconnect[_tcon]() into cifs_tree_connect() They were identical execpt to CIFSTCon() vs. SMB2_tcon(). These are also available via ops->tree_connect(). Signed-off-by: Stefan Metzmacher Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index fe4b55977dd8..727d676c7fb9 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5532,3 +5532,103 @@ cifs_prune_tlinks(struct work_struct *work) queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks, TLINK_IDLE_EXPIRE); } + +#ifdef CONFIG_CIFS_DFS_UPCALL +int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc) +{ + int rc; + struct TCP_Server_Info *server = tcon->ses->server; + const struct smb_version_operations *ops = server->ops; + struct dfs_cache_tgt_list tl; + struct dfs_cache_tgt_iterator *it = NULL; + char *tree; + const char *tcp_host; + size_t tcp_host_len; + const char *dfs_host; + size_t dfs_host_len; + + tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL); + if (!tree) + return -ENOMEM; + + if (!tcon->dfs_path) { + if (tcon->ipc) { + scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname); + rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); + } else { + rc = ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc); + } + goto out; + } + + rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl); + if (rc) + goto out; + + extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); + + for (it = dfs_cache_get_tgt_iterator(&tl); it; it = dfs_cache_get_next_tgt(&tl, it)) { + const char *share, *prefix; + size_t share_len, prefix_len; + bool target_match; + + rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix, &prefix_len); + if (rc) { + cifs_dbg(VFS, "%s: failed to parse target share %d\n", + __func__, rc); + continue; + } + + extract_unc_hostname(share, &dfs_host, &dfs_host_len); + + if (dfs_host_len != tcp_host_len + || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) { + cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", __func__, (int)dfs_host_len, + dfs_host, (int)tcp_host_len, tcp_host); + + rc = match_target_ip(server, dfs_host, dfs_host_len, &target_match); + if (rc) { + cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc); + break; + } + + if (!target_match) { + cifs_dbg(FYI, "%s: skipping target\n", __func__); + continue; + } + } + + if (tcon->ipc) { + scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$", (int)share_len, share); + rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); + } else { + scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, share); + rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); + if (!rc) { + rc = update_super_prepath(tcon, prefix, prefix_len); + break; + } + } + if (rc == -EREMOTE) + break; + } + + if (!rc) { + if (it) + rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1, it); + else + rc = -ENOENT; + } + dfs_cache_free_tgts(&tl); +out: + kfree(tree); + return rc; +} +#else +int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc) +{ + const struct smb_version_operations *ops = tcon->ses->server->ops; + + return ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc); +} +#endif -- cgit From 7d397a034d5c45528c7bdf7fc3752c4793029cce Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:40 -0300 Subject: cifs: rename reconn_inval_dfs_target() This function has nothing to do with *invalidation* but setting up the next target server from a cached referral. Rename it to reconn_set_next_dfs_target(). While at it, get rid of some meaningless checks. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 727d676c7fb9..21a4685894fa 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -393,15 +393,14 @@ static inline int reconn_set_ipaddr(struct TCP_Server_Info *server) #ifdef CONFIG_CIFS_DFS_UPCALL /* These functions must be called with server->srv_mutex held */ -static void reconn_inval_dfs_target(struct TCP_Server_Info *server, - struct cifs_sb_info *cifs_sb, - struct dfs_cache_tgt_list *tgt_list, - struct dfs_cache_tgt_iterator **tgt_it) +static void reconn_set_next_dfs_target(struct TCP_Server_Info *server, + struct cifs_sb_info *cifs_sb, + struct dfs_cache_tgt_list *tgt_list, + struct dfs_cache_tgt_iterator **tgt_it) { const char *name; - if (!cifs_sb || !cifs_sb->origin_fullpath || !tgt_list || - !server->nr_targets) + if (!cifs_sb || !cifs_sb->origin_fullpath) return; if (!*tgt_it) { @@ -578,7 +577,7 @@ cifs_reconnect(struct TCP_Server_Info *server) * feature is disabled, then we will retry last server we * connected to before. */ - reconn_inval_dfs_target(server, cifs_sb, &tgt_list, &tgt_it); + reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it); #endif rc = reconn_set_ipaddr(server); if (rc) { -- cgit From a52930353eaf443489a350a135c5525a4acbbf56 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:41 -0300 Subject: cifs: handle empty list of targets in cifs_reconnect() In case there were no cached DFS referrals in reconn_setup_dfs_targets(), set cifs_sb to NULL prior to calling reconn_set_next_dfs_target() so it would not try to access an empty tgt_list. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 21a4685894fa..78e6460d1e49 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -470,11 +470,13 @@ cifs_reconnect(struct TCP_Server_Info *server) sb = NULL; } else { cifs_sb = CIFS_SB(sb); - rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list); - if (rc && (rc != -EOPNOTSUPP)) { - cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n", - __func__); + if (rc) { + cifs_sb = NULL; + if (rc != -EOPNOTSUPP) { + cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n", + __func__); + } } else { server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list); } -- cgit From 7548e1da8d2d345debb9c0f141c47e4077d6085b Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:42 -0300 Subject: cifs: handle RESP_GET_DFS_REFERRAL.PathConsumed in reconnect Use PathConsumed field when parsing prefixes of referral paths that either match a cache entry or are a complete prefix path of an existing entry. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 78e6460d1e49..48b5133580d5 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5547,6 +5547,7 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru size_t tcp_host_len; const char *dfs_host; size_t dfs_host_len; + char *share = NULL, *prefix = NULL; tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL); if (!tree) @@ -5569,11 +5570,12 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); for (it = dfs_cache_get_tgt_iterator(&tl); it; it = dfs_cache_get_next_tgt(&tl, it)) { - const char *share, *prefix; - size_t share_len, prefix_len; bool target_match; - rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix, &prefix_len); + kfree(share); + kfree(prefix); + + rc = dfs_cache_get_tgt_share(tcon->dfs_path + 1, it, &share, &prefix); if (rc) { cifs_dbg(VFS, "%s: failed to parse target share %d\n", __func__, rc); @@ -5600,13 +5602,13 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru } if (tcon->ipc) { - scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$", (int)share_len, share); + scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", share); rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); } else { - scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, share); + scnprintf(tree, MAX_TREE_SIZE, "\\%s", share); rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); if (!rc) { - rc = update_super_prepath(tcon, prefix, prefix_len); + rc = update_super_prepath(tcon, prefix); break; } } @@ -5614,6 +5616,9 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru break; } + kfree(share); + kfree(prefix); + if (!rc) { if (it) rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1, it); -- cgit From c6a80e1ff4106755b8e72a88e767deb7c9d45050 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 31 Jul 2020 18:13:42 +0100 Subject: cifs: fix double free error on share and prefix Currently if the call dfs_cache_get_tgt_share fails we cannot fully guarantee that share and prefix are set to NULL and the next iteration of the loop can end up potentially double freeing these pointers. Since the semantics of dfs_cache_get_tgt_share are ambiguous for failure cases with the setting of share and prefix (currently now and the possibly the future), it seems prudent to set the pointers to NULL when the objects are free'd to avoid any double frees. Addresses-Coverity: ("Double free") Fixes: 96296c946a2a ("cifs: handle RESP_GET_DFS_REFERRAL.PathConsumed in reconnect") Signed-off-by: Colin Ian King Signed-off-by: Steve French Reviewed-by: Paulo Alcantara (SUSE) --- fs/cifs/connect.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 48b5133580d5..aae90953e07f 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5574,6 +5574,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru kfree(share); kfree(prefix); + share = NULL; + prefix = NULL; rc = dfs_cache_get_tgt_share(tcon->dfs_path + 1, it, &share, &prefix); if (rc) { -- cgit From 11375a59a99ca05086a1837c27d79e92dd8d4aed Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:43 -0300 Subject: cifs: only update prefix path of DFS links in cifs_tree_connect() For DFS root mounts that contain a prefix path, do not change them after failover. E.g., if the user mounts //srvA/root/dir1 and then lost connection to srvA, it will reconnect to //srvB/root/dir1 In case of DFS links, which may resolve to different prefix paths depending on their list of targets, the following must be supported: - mount //srvA/root/link/bar - connect to //srvA/share - set prefix path to "bar" - lost connection to srvA - reconnect to next target: //srvB/share/foo - set new prefix path to "foo/bar" In cifs_tree_connect(), check the server_type field of the cached DFS referral to determine whether or not prefix path should be updated. Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index aae90953e07f..637ba0066636 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -5548,6 +5548,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru const char *dfs_host; size_t dfs_host_len; char *share = NULL, *prefix = NULL; + struct dfs_info3_param ref = {0}; + bool isroot; tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL); if (!tree) @@ -5563,9 +5565,11 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru goto out; } - rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl); + rc = dfs_cache_noreq_find(tcon->dfs_path + 1, &ref, &tl); if (rc) goto out; + isroot = ref.server_type == DFS_TYPE_ROOT; + free_dfs_info_param(&ref); extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len); @@ -5609,7 +5613,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru } else { scnprintf(tree, MAX_TREE_SIZE, "\\%s", share); rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); - if (!rc) { + /* Only handle prefix paths of DFS link targets */ + if (!rc && !isroot) { rc = update_super_prepath(tcon, prefix); break; } -- cgit From 7efd081582619e7c270d1c0a422385dcaa99fa9f Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 21 Jul 2020 09:36:44 -0300 Subject: cifs: document and cleanup dfs mount cifs_mount() for DFS mounts is for a long time way too complex to follow, mostly because it lacks some documentation, does a lot of operations like resolving DFS roots and links, checking for path components, perform failover, crap code, etc. Besides adding some documentation to it, do some cleanup and ensure that the following is implemented and supported: * non-DFS mounts * DFS failover * DFS root mounts - tcon and cifs_sb must contain DFS path (NOT including prefix) - if prefix path, then save it in cifs_sb and it must not be changed * DFS link mounts - tcon and cifs_sb must contain DFS path (including prefix) - if prefix path, then save it in cifs_sb and it may be changed * prevent recursion on broken link referrals (MAX_NESTED_LINKS) * check every path component of the currently resolved target (including prefix), and chase them accordingly * make sure that DFS referrals go through newly resolved root servers Signed-off-by: Paulo Alcantara (SUSE) Reviewed-by: Aurelien Aptel Signed-off-by: Steve French --- fs/cifs/connect.c | 370 +++++++++++++++++++++++++++--------------------------- 1 file changed, 182 insertions(+), 188 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 637ba0066636..7e3e5e2098eb 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -4423,11 +4423,11 @@ build_unc_path_to_root(const struct smb_vol *vol, static int expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, struct smb_vol *volume_info, struct cifs_sb_info *cifs_sb, - int check_prefix) + char *ref_path) { int rc; struct dfs_info3_param referral = {0}; - char *full_path = NULL, *ref_path = NULL, *mdata = NULL; + char *full_path = NULL, *mdata = NULL; if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) return -EREMOTE; @@ -4436,9 +4436,6 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, if (IS_ERR(full_path)) return PTR_ERR(full_path); - /* For DFS paths, skip the first '\' of the UNC */ - ref_path = check_prefix ? full_path + 1 : volume_info->UNC + 1; - rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), ref_path, &referral, NULL); if (!rc) { @@ -4501,13 +4498,10 @@ static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it, return 0; } -static int setup_dfs_tgt_conn(const char *path, +static int setup_dfs_tgt_conn(const char *path, const char *full_path, const struct dfs_cache_tgt_iterator *tgt_it, - struct cifs_sb_info *cifs_sb, - struct smb_vol *vol, - unsigned int *xid, - struct TCP_Server_Info **server, - struct cifs_ses **ses, + struct cifs_sb_info *cifs_sb, struct smb_vol *vol, unsigned int *xid, + struct TCP_Server_Info **server, struct cifs_ses **ses, struct cifs_tcon **tcon) { int rc; @@ -4521,8 +4515,7 @@ static int setup_dfs_tgt_conn(const char *path, if (rc) return rc; - mdata = cifs_compose_mount_options(cifs_sb->mountdata, path, &ref, - &fake_devname); + mdata = cifs_compose_mount_options(cifs_sb->mountdata, full_path + 1, &ref, &fake_devname); free_dfs_info_param(&ref); if (IS_ERR(mdata)) { @@ -4545,7 +4538,7 @@ static int setup_dfs_tgt_conn(const char *path, mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon); rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses, tcon); - if (!rc) { + if (!rc || (*server && *ses)) { /* * We were able to connect to new target server. * Update current volume info with new target server. @@ -4557,14 +4550,10 @@ static int setup_dfs_tgt_conn(const char *path, return rc; } -static int mount_do_dfs_failover(const char *path, - struct cifs_sb_info *cifs_sb, - struct smb_vol *vol, - struct cifs_ses *root_ses, - unsigned int *xid, - struct TCP_Server_Info **server, - struct cifs_ses **ses, - struct cifs_tcon **tcon) +static int do_dfs_failover(const char *path, const char *full_path, struct cifs_sb_info *cifs_sb, + struct smb_vol *vol, struct cifs_ses *root_ses, unsigned int *xid, + struct TCP_Server_Info **server, struct cifs_ses **ses, + struct cifs_tcon **tcon) { int rc; struct dfs_cache_tgt_list tgt_list; @@ -4583,9 +4572,9 @@ static int mount_do_dfs_failover(const char *path, if (rc) break; /* Connect to next DFS target */ - rc = setup_dfs_tgt_conn(path, tgt_it, cifs_sb, vol, xid, server, - ses, tcon); - if (!rc || rc == -EACCES || rc == -EOPNOTSUPP) + rc = setup_dfs_tgt_conn(path, full_path, tgt_it, cifs_sb, vol, xid, server, ses, + tcon); + if (!rc || (*server && *ses)) break; } if (!rc) { @@ -4755,207 +4744,210 @@ static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb_vol *vol, } #ifdef CONFIG_CIFS_DFS_UPCALL -static inline void set_root_tcon(struct cifs_sb_info *cifs_sb, - struct cifs_tcon *tcon, - struct cifs_tcon **root) +static void set_root_ses(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, + struct cifs_ses **root_ses) { - spin_lock(&cifs_tcp_ses_lock); - tcon->tc_count++; - tcon->remap = cifs_remap(cifs_sb); - spin_unlock(&cifs_tcp_ses_lock); - *root = tcon; + if (ses) { + spin_lock(&cifs_tcp_ses_lock); + ses->ses_count++; + ses->tcon_ipc->remap = cifs_remap(cifs_sb); + spin_unlock(&cifs_tcp_ses_lock); + } + *root_ses = ses; +} + +static void put_root_ses(struct cifs_ses *ses) +{ + if (ses) + cifs_put_smb_ses(ses); +} + +/* Check if a path component is remote and then update @dfs_path accordingly */ +static int check_dfs_prepath(struct cifs_sb_info *cifs_sb, struct smb_vol *vol, + const unsigned int xid, struct TCP_Server_Info *server, + struct cifs_tcon *tcon, char **dfs_path) +{ + char *path, *s; + char sep = CIFS_DIR_SEP(cifs_sb), tmp; + char *npath; + int rc = 0; + int added_treename = tcon->Flags & SMB_SHARE_IS_IN_DFS; + int skip = added_treename; + + path = cifs_build_path_to_root(vol, cifs_sb, tcon, added_treename); + if (!path) + return -ENOMEM; + + /* + * Walk through the path components in @path and check if they're accessible. In case any of + * the components is -EREMOTE, then update @dfs_path with the next DFS referral request path + * (NOT including the remaining components). + */ + s = path; + do { + /* skip separators */ + while (*s && *s == sep) + s++; + if (!*s) + break; + /* next separator */ + while (*s && *s != sep) + s++; + /* + * if the treename is added, we then have to skip the first + * part within the separators + */ + if (skip) { + skip = 0; + continue; + } + tmp = *s; + *s = 0; + rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, path); + if (rc && rc == -EREMOTE) { + struct smb_vol v = {NULL}; + /* if @path contains a tree name, skip it in the prefix path */ + if (added_treename) { + rc = cifs_parse_devname(path, &v); + if (rc) + break; + rc = -EREMOTE; + npath = build_unc_path_to_root(&v, cifs_sb, true); + cifs_cleanup_volume_info_contents(&v); + } else { + v.UNC = vol->UNC; + v.prepath = path + 1; + npath = build_unc_path_to_root(&v, cifs_sb, true); + } + if (IS_ERR(npath)) { + rc = PTR_ERR(npath); + break; + } + kfree(*dfs_path); + *dfs_path = npath; + } + *s = tmp; + } while (rc == 0); + + kfree(path); + return rc; } int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) { int rc = 0; unsigned int xid; - struct cifs_ses *ses; - struct cifs_tcon *root_tcon = NULL; + struct TCP_Server_Info *server = NULL; + struct cifs_ses *ses = NULL, *root_ses = NULL; struct cifs_tcon *tcon = NULL; - struct TCP_Server_Info *server; - char *root_path = NULL, *full_path = NULL; - char *old_mountdata, *origin_mountdata = NULL; - int count; + int count = 0; + char *ref_path = NULL, *full_path = NULL; + char *oldmnt = NULL; + char *mntdata = NULL; rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); - if (!rc && tcon) { - /* If not a standalone DFS root, then check if path is remote */ - rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, - cifs_remap(cifs_sb), vol->UNC + 1, NULL, - NULL); - if (rc) { - rc = is_path_remote(cifs_sb, vol, xid, server, tcon); - if (!rc) - goto out; - if (rc != -EREMOTE) - goto error; - } - } /* - * If first DFS target server went offline and we failed to connect it, - * server and ses pointers are NULL at this point, though we still have - * chance to get a cached DFS referral in expand_dfs_referral() and - * retry next target available in it. + * Unconditionally try to get an DFS referral (even cached) to determine whether it is an + * DFS mount. * - * If a NULL ses ptr is passed to dfs_cache_find(), a lookup will be - * performed against DFS path and *no* requests will be sent to server - * for any new DFS referrals. Hence it's safe to skip checking whether - * server or ses ptr is NULL. - */ - if (rc == -EACCES || rc == -EOPNOTSUPP) - goto error; - - root_path = build_unc_path_to_root(vol, cifs_sb, false); - if (IS_ERR(root_path)) { - rc = PTR_ERR(root_path); - root_path = NULL; - goto error; - } - - full_path = build_unc_path_to_root(vol, cifs_sb, true); - if (IS_ERR(full_path)) { - rc = PTR_ERR(full_path); - full_path = NULL; - goto error; - } - /* - * Perform an unconditional check for whether there are DFS - * referrals for this path without prefix, 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. - * Chase the referral if found, otherwise continue normally. + * 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. */ - old_mountdata = cifs_sb->mountdata; - (void)expand_dfs_referral(xid, ses, vol, cifs_sb, false); - - if (cifs_sb->mountdata == NULL) { - rc = -ENOENT; - goto error; + if (dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), vol->UNC + 1, NULL, + NULL)) { + /* No DFS referral was returned. Looks like a regular share. */ + if (rc) + goto error; + /* Check if it is fully accessible and then mount it */ + rc = is_path_remote(cifs_sb, vol, xid, server, tcon); + if (!rc) + goto out; + if (rc != -EREMOTE) + goto error; } - - /* Save DFS root volume information for DFS refresh worker */ - origin_mountdata = kstrndup(cifs_sb->mountdata, - strlen(cifs_sb->mountdata), GFP_KERNEL); - if (!origin_mountdata) { + /* Save mount options */ + mntdata = kstrndup(cifs_sb->mountdata, strlen(cifs_sb->mountdata), GFP_KERNEL); + if (!mntdata) { rc = -ENOMEM; goto error; } - - if (cifs_sb->mountdata != old_mountdata) { - /* If we were redirected, reconnect to new target server */ - mount_put_conns(cifs_sb, xid, server, ses, tcon); - rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); - } - if (rc) { - if (rc == -EACCES || rc == -EOPNOTSUPP) - goto error; - /* Perform DFS failover to any other DFS targets */ - rc = mount_do_dfs_failover(root_path + 1, cifs_sb, vol, NULL, - &xid, &server, &ses, &tcon); - if (rc) - goto error; - } - - kfree(root_path); - root_path = build_unc_path_to_root(vol, cifs_sb, false); - if (IS_ERR(root_path)) { - rc = PTR_ERR(root_path); - root_path = NULL; + /* Get path of DFS root */ + ref_path = build_unc_path_to_root(vol, cifs_sb, false); + if (IS_ERR(ref_path)) { + rc = PTR_ERR(ref_path); + ref_path = NULL; goto error; } - /* Cache out resolved root server */ - (void)dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), - root_path + 1, NULL, NULL); - kfree(root_path); - root_path = NULL; - - set_root_tcon(cifs_sb, tcon, &root_tcon); - - for (count = 1; ;) { - if (!rc && tcon) { - rc = is_path_remote(cifs_sb, vol, xid, server, tcon); - if (!rc || rc != -EREMOTE) - break; - } - /* - * BB: when we implement proper loop detection, - * we will remove this check. But now we need it - * to prevent an indefinite loop if 'DFS tree' is - * misconfigured (i.e. has loops). - */ - if (count++ > MAX_NESTED_LINKS) { - rc = -ELOOP; - break; - } + set_root_ses(cifs_sb, ses, &root_ses); + do { + /* Save full path of last DFS path we used to resolve final target server */ kfree(full_path); - full_path = build_unc_path_to_root(vol, cifs_sb, true); + full_path = build_unc_path_to_root(vol, cifs_sb, !!count); if (IS_ERR(full_path)) { rc = PTR_ERR(full_path); - full_path = NULL; break; } - - old_mountdata = cifs_sb->mountdata; - rc = expand_dfs_referral(xid, root_tcon->ses, vol, cifs_sb, - true); + /* Chase referral */ + oldmnt = cifs_sb->mountdata; + rc = expand_dfs_referral(xid, root_ses, vol, cifs_sb, ref_path + 1); if (rc) break; - - if (cifs_sb->mountdata != old_mountdata) { + /* Connect to new DFS target only if we were redirected */ + if (oldmnt != cifs_sb->mountdata) { mount_put_conns(cifs_sb, xid, server, ses, tcon); - rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, - &tcon); - /* - * Ensure that DFS referrals go through new root server. - */ - if (!rc && tcon && - (tcon->share_flags & (SHI1005_FLAGS_DFS | - SHI1005_FLAGS_DFS_ROOT))) { - cifs_put_tcon(root_tcon); - set_root_tcon(cifs_sb, tcon, &root_tcon); - } + rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); } - if (rc) { - if (rc == -EACCES || rc == -EOPNOTSUPP) - break; - /* Perform DFS failover to any other DFS targets */ - rc = mount_do_dfs_failover(full_path + 1, cifs_sb, vol, - root_tcon->ses, &xid, - &server, &ses, &tcon); - if (rc == -EACCES || rc == -EOPNOTSUPP || !server || - !ses) - goto error; + if (rc && !server && !ses) { + /* Failed to connect. Try to connect to other targets in the referral. */ + rc = do_dfs_failover(ref_path + 1, full_path, cifs_sb, vol, root_ses, &xid, + &server, &ses, &tcon); } - } - cifs_put_tcon(root_tcon); + if (rc == -EACCES || rc == -EOPNOTSUPP || !server || !ses) + break; + if (!tcon) + continue; + /* Make sure that requests go through new root servers */ + if (tcon->share_flags & (SHI1005_FLAGS_DFS | SHI1005_FLAGS_DFS_ROOT)) { + put_root_ses(root_ses); + set_root_ses(cifs_sb, ses, &root_ses); + } + /* Check for remaining path components and then continue chasing them (-EREMOTE) */ + rc = check_dfs_prepath(cifs_sb, vol, xid, server, tcon, &ref_path); + /* Prevent recursion on broken link referrals */ + if (rc == -EREMOTE && ++count > MAX_NESTED_LINKS) + rc = -ELOOP; + } while (rc == -EREMOTE); if (rc) goto error; - - spin_lock(&cifs_tcp_ses_lock); - if (!tcon->dfs_path) { - /* Save full path in new tcon to do failover when reconnecting tcons */ - tcon->dfs_path = full_path; - full_path = NULL; - tcon->remap = cifs_remap(cifs_sb); - } - cifs_sb->origin_fullpath = kstrndup(tcon->dfs_path, - strlen(tcon->dfs_path), - GFP_ATOMIC); + put_root_ses(root_ses); + root_ses = NULL; + kfree(ref_path); + ref_path = NULL; + /* + * Store DFS full path in both superblock and tree connect structures. + * + * For DFS root mounts, the prefix path (cifs_sb->prepath) is preserved during reconnect so + * only the root path is set in cifs_sb->origin_fullpath and tcon->dfs_path. And for DFS + * links, the prefix path is included in both and may be changed during reconnect. See + * cifs_tree_connect(). + */ + cifs_sb->origin_fullpath = kstrndup(full_path, strlen(full_path), GFP_KERNEL); if (!cifs_sb->origin_fullpath) { - spin_unlock(&cifs_tcp_ses_lock); rc = -ENOMEM; goto error; } + spin_lock(&cifs_tcp_ses_lock); + tcon->dfs_path = full_path; + full_path = NULL; + tcon->remap = cifs_remap(cifs_sb); spin_unlock(&cifs_tcp_ses_lock); - rc = dfs_cache_add_vol(origin_mountdata, vol, cifs_sb->origin_fullpath); - if (rc) { - kfree(cifs_sb->origin_fullpath); + /* Add original volume information for DFS cache to be used when refreshing referrals */ + rc = dfs_cache_add_vol(mntdata, vol, cifs_sb->origin_fullpath); + if (rc) goto error; - } /* * After reconnecting to a different server, unique ids won't * match anymore, so we disable serverino. This prevents @@ -4977,9 +4969,11 @@ out: return mount_setup_tlink(cifs_sb, ses, tcon); error: + kfree(ref_path); kfree(full_path); - kfree(root_path); - kfree(origin_mountdata); + kfree(mntdata); + kfree(cifs_sb->origin_fullpath); + put_root_ses(root_ses); mount_put_conns(cifs_sb, xid, server, ses, tcon); return rc; } -- cgit From 20b135e48cdda7a7dfccef7286def66e5c9686c3 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 5 Aug 2020 12:52:07 +0300 Subject: cifs: Fix an error pointer dereference in cifs_mount() The error handling calls kfree(full_path) so we can't let it be a NULL pointer. There used to be a NULL assignment here but we accidentally deleted it. Add it back. Fixes: 7efd08158261 ("cifs: document and cleanup dfs mount") Signed-off-by: Dan Carpenter Signed-off-by: Steve French Reviewed-by: Paulo Alcantara (SUSE) --- fs/cifs/connect.c | 1 + 1 file changed, 1 insertion(+) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 7e3e5e2098eb..2fe6b1ab75c3 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -4886,6 +4886,7 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) full_path = build_unc_path_to_root(vol, cifs_sb, !!count); if (IS_ERR(full_path)) { rc = PTR_ERR(full_path); + full_path = NULL; break; } /* Chase referral */ -- cgit From 453431a54934d917153c65211b2dabf45562ca88 Mon Sep 17 00:00:00 2001 From: Waiman Long Date: Thu, 6 Aug 2020 23:18:13 -0700 Subject: mm, treewide: rename kzfree() to kfree_sensitive() As said by Linus: A symmetric naming is only helpful if it implies symmetries in use. Otherwise it's actively misleading. In "kzalloc()", the z is meaningful and an important part of what the caller wants. In "kzfree()", the z is actively detrimental, because maybe in the future we really _might_ want to use that "memfill(0xdeadbeef)" or something. The "zero" part of the interface isn't even _relevant_. The main reason that kzfree() exists is to clear sensitive information that should not be leaked to other future users of the same memory objects. Rename kzfree() to kfree_sensitive() to follow the example of the recently added kvfree_sensitive() and make the intention of the API more explicit. In addition, memzero_explicit() is used to clear the memory to make sure that it won't get optimized away by the compiler. The renaming is done by using the command sequence: git grep -w --name-only kzfree |\ xargs sed -i 's/kzfree/kfree_sensitive/' followed by some editing of the kfree_sensitive() kerneldoc and adding a kzfree backward compatibility macro in slab.h. [akpm@linux-foundation.org: fs/crypto/inline_crypt.c needs linux/slab.h] [akpm@linux-foundation.org: fix fs/crypto/inline_crypt.c some more] Suggested-by: Joe Perches Signed-off-by: Waiman Long Signed-off-by: Andrew Morton Acked-by: David Howells Acked-by: Michal Hocko Acked-by: Johannes Weiner Cc: Jarkko Sakkinen Cc: James Morris Cc: "Serge E. Hallyn" Cc: Joe Perches Cc: Matthew Wilcox Cc: David Rientjes Cc: Dan Carpenter Cc: "Jason A . Donenfeld" Link: http://lkml.kernel.org/r/20200616154311.12314-3-longman@redhat.com Signed-off-by: Linus Torvalds --- fs/cifs/connect.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index a61abde09ffe..889fee586d94 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2182,7 +2182,7 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, tmp_end++; if (!(tmp_end < end && tmp_end[1] == delim)) { /* No it is not. Set the password to NULL */ - kzfree(vol->password); + kfree_sensitive(vol->password); vol->password = NULL; break; } @@ -2220,7 +2220,7 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, options = end; } - kzfree(vol->password); + kfree_sensitive(vol->password); /* Now build new password string */ temp_len = strlen(value); vol->password = kzalloc(temp_len+1, GFP_KERNEL); @@ -3198,7 +3198,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses) rc = -ENOMEM; kfree(vol->username); vol->username = NULL; - kzfree(vol->password); + kfree_sensitive(vol->password); vol->password = NULL; goto out_key_put; } @@ -4219,7 +4219,7 @@ void cifs_cleanup_volume_info_contents(struct smb_vol *volume_info) { kfree(volume_info->username); - kzfree(volume_info->password); + kfree_sensitive(volume_info->password); kfree(volume_info->UNC); kfree(volume_info->domainname); kfree(volume_info->iocharset); @@ -5345,7 +5345,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) out: kfree(vol_info->username); - kzfree(vol_info->password); + kfree_sensitive(vol_info->password); kfree(vol_info); return tcon; -- cgit