Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

smb: client: handle lack of IPC in dfs_cache_refresh()

In very rare cases, DFS mounts could end up with SMB sessions without
any IPC connections. These mounts are only possible when having
unexpired cached DFS referrals, hence not requiring any IPC
connections during the mount process.

Try to establish those missing IPC connections when refreshing DFS
referrals. If the server is still rejecting it, then simply ignore
and leave expired cached DFS referral for any potential DFS failovers.

Reported-by: Jay Shin <jaeshin@redhat.com>
Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
Cc: David Howells <dhowells@redhat.com>
Cc: linux-cifs@vger.kernel.org
Signed-off-by: Steve French <stfrench@microsoft.com>

authored by

Paulo Alcantara and committed by
Steve French
fac56c46 5c76f996

+66 -29
+2
fs/smb/client/cifsproto.h
··· 616 616 extern struct TCP_Server_Info * 617 617 cifs_find_tcp_session(struct smb3_fs_context *ctx); 618 618 619 + struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal); 620 + 619 621 void __cifs_put_smb_ses(struct cifs_ses *ses); 620 622 621 623 extern struct cifs_ses *
+17 -21
fs/smb/client/connect.c
··· 2015 2015 /** 2016 2016 * cifs_setup_ipc - helper to setup the IPC tcon for the session 2017 2017 * @ses: smb session to issue the request on 2018 - * @ctx: the superblock configuration context to use for building the 2019 - * new tree connection for the IPC (interprocess communication RPC) 2018 + * @seal: if encryption is requested 2020 2019 * 2021 2020 * A new IPC connection is made and stored in the session 2022 2021 * tcon_ipc. The IPC tcon has the same lifetime as the session. 2023 2022 */ 2024 - static int 2025 - cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx) 2023 + struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal) 2026 2024 { 2027 2025 int rc = 0, xid; 2028 2026 struct cifs_tcon *tcon; 2029 2027 char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0}; 2030 - bool seal = false; 2031 2028 struct TCP_Server_Info *server = ses->server; 2032 2029 2033 2030 /* 2034 2031 * If the mount request that resulted in the creation of the 2035 2032 * session requires encryption, force IPC to be encrypted too. 2036 2033 */ 2037 - if (ctx->seal) { 2038 - if (server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) 2039 - seal = true; 2040 - else { 2041 - cifs_server_dbg(VFS, 2042 - "IPC: server doesn't support encryption\n"); 2043 - return -EOPNOTSUPP; 2044 - } 2034 + if (seal && !(server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION)) { 2035 + cifs_server_dbg(VFS, "IPC: server doesn't support encryption\n"); 2036 + return ERR_PTR(-EOPNOTSUPP); 2045 2037 } 2046 2038 2047 2039 /* no need to setup directory caching on IPC share, so pass in false */ 2048 2040 tcon = tcon_info_alloc(false, netfs_trace_tcon_ref_new_ipc); 2049 2041 if (tcon == NULL) 2050 - return -ENOMEM; 2042 + return ERR_PTR(-ENOMEM); 2051 2043 2052 2044 spin_lock(&server->srv_lock); 2053 2045 scnprintf(unc, sizeof(unc), "\\\\%s\\IPC$", server->hostname); ··· 2049 2057 tcon->ses = ses; 2050 2058 tcon->ipc = true; 2051 2059 tcon->seal = seal; 2052 - rc = server->ops->tree_connect(xid, ses, unc, tcon, ctx->local_nls); 2060 + rc = server->ops->tree_connect(xid, ses, unc, tcon, ses->local_nls); 2053 2061 free_xid(xid); 2054 2062 2055 2063 if (rc) { 2056 - cifs_server_dbg(VFS, "failed to connect to IPC (rc=%d)\n", rc); 2064 + cifs_server_dbg(VFS | ONCE, "failed to connect to IPC (rc=%d)\n", rc); 2057 2065 tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc_fail); 2058 - goto out; 2066 + return ERR_PTR(rc); 2059 2067 } 2060 2068 2061 2069 cifs_dbg(FYI, "IPC tcon rc=%d ipc tid=0x%x\n", rc, tcon->tid); ··· 2063 2071 spin_lock(&tcon->tc_lock); 2064 2072 tcon->status = TID_GOOD; 2065 2073 spin_unlock(&tcon->tc_lock); 2066 - ses->tcon_ipc = tcon; 2067 - out: 2068 - return rc; 2074 + return tcon; 2069 2075 } 2070 2076 2071 2077 static struct cifs_ses * ··· 2337 2347 { 2338 2348 struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr; 2339 2349 struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr; 2350 + struct cifs_tcon *ipc; 2340 2351 struct cifs_ses *ses; 2341 2352 unsigned int xid; 2342 2353 int retries = 0; ··· 2516 2525 list_add(&ses->smb_ses_list, &server->smb_ses_list); 2517 2526 spin_unlock(&cifs_tcp_ses_lock); 2518 2527 2519 - cifs_setup_ipc(ses, ctx); 2528 + ipc = cifs_setup_ipc(ses, ctx->seal); 2529 + spin_lock(&cifs_tcp_ses_lock); 2530 + spin_lock(&ses->ses_lock); 2531 + ses->tcon_ipc = !IS_ERR(ipc) ? ipc : NULL; 2532 + spin_unlock(&ses->ses_lock); 2533 + spin_unlock(&cifs_tcp_ses_lock); 2520 2534 2521 2535 free_xid(xid); 2522 2536
+47 -8
fs/smb/client/dfs_cache.c
··· 1120 1120 return match; 1121 1121 } 1122 1122 1123 - static bool is_ses_good(struct cifs_ses *ses) 1123 + static bool is_ses_good(struct cifs_tcon *tcon, struct cifs_ses *ses) 1124 1124 { 1125 1125 struct TCP_Server_Info *server = ses->server; 1126 - struct cifs_tcon *tcon = ses->tcon_ipc; 1126 + struct cifs_tcon *ipc = NULL; 1127 1127 bool ret; 1128 1128 1129 + spin_lock(&cifs_tcp_ses_lock); 1129 1130 spin_lock(&ses->ses_lock); 1130 1131 spin_lock(&ses->chan_lock); 1132 + 1131 1133 ret = !cifs_chan_needs_reconnect(ses, server) && 1132 - ses->ses_status == SES_GOOD && 1133 - !tcon->need_reconnect; 1134 + ses->ses_status == SES_GOOD; 1135 + 1134 1136 spin_unlock(&ses->chan_lock); 1137 + 1138 + if (!ret) 1139 + goto out; 1140 + 1141 + if (likely(ses->tcon_ipc)) { 1142 + if (ses->tcon_ipc->need_reconnect) { 1143 + ret = false; 1144 + goto out; 1145 + } 1146 + } else { 1147 + spin_unlock(&ses->ses_lock); 1148 + spin_unlock(&cifs_tcp_ses_lock); 1149 + 1150 + ipc = cifs_setup_ipc(ses, tcon->seal); 1151 + 1152 + spin_lock(&cifs_tcp_ses_lock); 1153 + spin_lock(&ses->ses_lock); 1154 + if (!IS_ERR(ipc)) { 1155 + if (!ses->tcon_ipc) { 1156 + ses->tcon_ipc = ipc; 1157 + ipc = NULL; 1158 + } 1159 + } else { 1160 + ret = false; 1161 + ipc = NULL; 1162 + } 1163 + } 1164 + 1165 + out: 1135 1166 spin_unlock(&ses->ses_lock); 1167 + spin_unlock(&cifs_tcp_ses_lock); 1168 + if (ipc && server->ops->tree_disconnect) { 1169 + unsigned int xid = get_xid(); 1170 + 1171 + (void)server->ops->tree_disconnect(xid, ipc); 1172 + _free_xid(xid); 1173 + } 1174 + tconInfoFree(ipc, netfs_trace_tcon_ref_free_ipc); 1136 1175 return ret; 1137 1176 } 1138 1177 1139 1178 /* Refresh dfs referral of @ses */ 1140 - static void refresh_ses_referral(struct cifs_ses *ses) 1179 + static void refresh_ses_referral(struct cifs_tcon *tcon, struct cifs_ses *ses) 1141 1180 { 1142 1181 struct cache_entry *ce; 1143 1182 unsigned int xid; ··· 1192 1153 } 1193 1154 1194 1155 ses = CIFS_DFS_ROOT_SES(ses); 1195 - if (!is_ses_good(ses)) { 1156 + if (!is_ses_good(tcon, ses)) { 1196 1157 cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n", 1197 1158 __func__); 1198 1159 goto out; ··· 1280 1241 up_read(&htable_rw_lock); 1281 1242 1282 1243 ses = CIFS_DFS_ROOT_SES(ses); 1283 - if (!is_ses_good(ses)) { 1244 + if (!is_ses_good(tcon, ses)) { 1284 1245 cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n", 1285 1246 __func__); 1286 1247 goto out; ··· 1348 1309 tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work); 1349 1310 1350 1311 list_for_each_entry(ses, &tcon->dfs_ses_list, dlist) 1351 - refresh_ses_referral(ses); 1312 + refresh_ses_referral(tcon, ses); 1352 1313 refresh_tcon_referral(tcon, false); 1353 1314 1354 1315 queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,