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

smb: client: fix DFS mount against old servers with NTLMSSP

Old Windows servers will return not fully qualified DFS targets by
default as specified in

MS-DFSC 3.2.5.5 Receiving a Root Referral Request or Link Referral
Request

| Servers SHOULD<30> return fully qualified DNS host names of
| targets in responses to root referral requests and link referral
| requests.
| ...
| <30> Section 3.2.5.5: By default, Windows Server 2003, Windows
| Server 2008, Windows Server 2008 R2, Windows Server 2012, and
| Windows Server 2012 R2 return DNS host names that are not fully
| qualified for targets.

Fix this by converting all NetBIOS host names from DFS targets to
FQDNs and try resolving them first if DNS domain name was provided in
NTLMSSP CHALLENGE_MESSAGE message from previous SMB2_SESSION_SETUP.
This also prevents the client from translating the DFS target
hostnames to another domain depending on the network domain search
order.

Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>

authored by

Paulo Alcantara and committed by
Steve French
ad46faff 0e8ae9b9

+105 -50
+21
fs/smb/client/cifsglob.h
··· 828 828 */ 829 829 char *leaf_fullpath; 830 830 bool dfs_conn:1; 831 + char dns_dom[CIFS_MAX_DOMAINNAME_LEN + 1]; 831 832 }; 832 833 833 834 static inline bool is_smb1(struct TCP_Server_Info *server) ··· 2310 2309 spin_lock(&ses->ses_lock); 2311 2310 ret = ses->ses_status == SES_EXITING; 2312 2311 spin_unlock(&ses->ses_lock); 2312 + return ret; 2313 + } 2314 + 2315 + static inline bool cifs_netbios_name(const char *name, size_t namelen) 2316 + { 2317 + bool ret = false; 2318 + size_t i; 2319 + 2320 + if (namelen >= 1 && namelen <= RFC1001_NAME_LEN) { 2321 + for (i = 0; i < namelen; i++) { 2322 + const unsigned char c = name[i]; 2323 + 2324 + if (c == '\\' || c == '/' || c == ':' || c == '*' || 2325 + c == '?' || c == '"' || c == '<' || c == '>' || 2326 + c == '|' || c == '.') 2327 + return false; 2328 + if (!ret && isalpha(c)) 2329 + ret = true; 2330 + } 2331 + } 2313 2332 return ret; 2314 2333 } 2315 2334
+4 -1
fs/smb/client/connect.c
··· 97 97 ss = server->dstaddr; 98 98 spin_unlock(&server->srv_lock); 99 99 100 - rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL); 100 + rc = dns_resolve_server_name_to_ip(server->dns_dom, unc, 101 + (struct sockaddr *)&ss, NULL); 101 102 kfree(unc); 102 103 103 104 if (rc < 0) { ··· 1711 1710 goto out_err; 1712 1711 } 1713 1712 } 1713 + if (ctx->dns_dom) 1714 + strscpy(tcp_ses->dns_dom, ctx->dns_dom); 1714 1715 1715 1716 if (ctx->nosharesock) 1716 1717 tcp_ses->nosharesock = true;
+9 -4
fs/smb/client/dfs.c
··· 9 9 #include "fs_context.h" 10 10 #include "dfs.h" 11 11 12 + #define DFS_DOM(ctx) (ctx->dfs_root_ses ? ctx->dfs_root_ses->dns_dom : NULL) 13 + 12 14 /** 13 15 * dfs_parse_target_referral - set fs context for dfs target referral 14 16 * ··· 48 46 if (rc) 49 47 goto out; 50 48 51 - rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL); 52 - 49 + rc = dns_resolve_server_name_to_ip(DFS_DOM(ctx), path, 50 + (struct sockaddr *)&ctx->dstaddr, 51 + NULL); 53 52 out: 54 53 kfree(path); 55 54 return rc; ··· 62 59 int rc; 63 60 64 61 ctx->leaf_fullpath = (char *)full_path; 62 + ctx->dns_dom = DFS_DOM(ctx); 65 63 rc = cifs_mount_get_session(mnt_ctx); 66 - ctx->leaf_fullpath = NULL; 64 + ctx->leaf_fullpath = ctx->dns_dom = NULL; 67 65 68 66 return rc; 69 67 } ··· 268 264 int rc = 0; 269 265 270 266 if (!ctx->nodfs && ctx->dfs_automount) { 271 - rc = dns_resolve_server_name_to_ip(ctx->source, addr, NULL); 267 + rc = dns_resolve_server_name_to_ip(NULL, ctx->source, 268 + addr, NULL); 272 269 if (!rc) 273 270 cifs_set_port(addr, ctx->port); 274 271 ctx->dfs_automount = false;
+2 -1
fs/smb/client/dfs_cache.c
··· 1114 1114 extract_unc_hostname(s1, &host, &hostlen); 1115 1115 scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host); 1116 1116 1117 - rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL); 1117 + rc = dns_resolve_server_name_to_ip(server->dns_dom, unc, 1118 + (struct sockaddr *)&ss, NULL); 1118 1119 if (rc < 0) { 1119 1120 cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n", 1120 1121 __func__, (int)hostlen, host);
+60 -42
fs/smb/client/dns_resolve.c
··· 20 20 #include "cifsproto.h" 21 21 #include "cifs_debug.h" 22 22 23 + static int resolve_name(const char *name, size_t namelen, 24 + struct sockaddr *addr, time64_t *expiry) 25 + { 26 + char *ip; 27 + int rc; 28 + 29 + rc = dns_query(current->nsproxy->net_ns, NULL, name, 30 + namelen, NULL, &ip, expiry, false); 31 + if (rc < 0) { 32 + cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n", 33 + __func__, (int)namelen, (int)namelen, name); 34 + } else { 35 + cifs_dbg(FYI, "%s: resolved: %*.*s to %s expiry %llu\n", 36 + __func__, (int)namelen, (int)namelen, name, ip, 37 + expiry ? (*expiry) : 0); 38 + 39 + rc = cifs_convert_address(addr, ip, strlen(ip)); 40 + kfree(ip); 41 + if (!rc) { 42 + cifs_dbg(FYI, "%s: unable to determine ip address\n", 43 + __func__); 44 + rc = -EHOSTUNREACH; 45 + } else { 46 + rc = 0; 47 + } 48 + } 49 + return rc; 50 + } 51 + 23 52 /** 24 53 * dns_resolve_server_name_to_ip - Resolve UNC server name to ip address. 54 + * @dom: optional DNS domain name 25 55 * @unc: UNC path specifying the server (with '/' as delimiter) 26 56 * @ip_addr: Where to return the IP address. 27 57 * @expiry: Where to return the expiry time for the dns record. 28 58 * 29 59 * Returns zero success, -ve on error. 30 60 */ 31 - int 32 - dns_resolve_server_name_to_ip(const char *unc, struct sockaddr *ip_addr, time64_t *expiry) 61 + int dns_resolve_server_name_to_ip(const char *dom, const char *unc, 62 + struct sockaddr *ip_addr, time64_t *expiry) 33 63 { 34 - const char *hostname, *sep; 35 - char *ip; 36 - int len, rc; 64 + const char *name; 65 + size_t namelen, len; 66 + char *s; 67 + int rc; 37 68 38 69 if (!ip_addr || !unc) 39 70 return -EINVAL; 40 71 41 - len = strlen(unc); 42 - if (len < 3) { 43 - cifs_dbg(FYI, "%s: unc is too short: %s\n", __func__, unc); 72 + cifs_dbg(FYI, "%s: dom=%s unc=%s\n", __func__, dom, unc); 73 + if (strlen(unc) < 3) 44 74 return -EINVAL; 45 - } 46 75 47 - /* Discount leading slashes for cifs */ 48 - len -= 2; 49 - hostname = unc + 2; 76 + extract_unc_hostname(unc, &name, &namelen); 77 + if (!namelen) 78 + return -EINVAL; 50 79 51 - /* Search for server name delimiter */ 52 - sep = memchr(hostname, '/', len); 53 - if (sep) 54 - len = sep - hostname; 55 - else 56 - cifs_dbg(FYI, "%s: probably server name is whole unc: %s\n", 57 - __func__, unc); 58 - 80 + cifs_dbg(FYI, "%s: hostname=%.*s\n", __func__, (int)namelen, name); 59 81 /* Try to interpret hostname as an IPv4 or IPv6 address */ 60 - rc = cifs_convert_address(ip_addr, hostname, len); 82 + rc = cifs_convert_address(ip_addr, name, namelen); 61 83 if (rc > 0) { 62 - cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %*.*s\n", __func__, len, len, 63 - hostname); 84 + cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %*.*s\n", 85 + __func__, (int)namelen, (int)namelen, name); 64 86 return 0; 65 87 } 66 88 67 - /* Perform the upcall */ 68 - rc = dns_query(current->nsproxy->net_ns, NULL, hostname, len, 69 - NULL, &ip, expiry, false); 70 - if (rc < 0) { 71 - cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n", 72 - __func__, len, len, hostname); 73 - } else { 74 - cifs_dbg(FYI, "%s: resolved: %*.*s to %s expiry %llu\n", 75 - __func__, len, len, hostname, ip, 76 - expiry ? (*expiry) : 0); 89 + /* 90 + * If @name contains a NetBIOS name and @dom has been specified, then 91 + * convert @name to an FQDN and try resolving it first. 92 + */ 93 + if (dom && *dom && cifs_netbios_name(name, namelen)) { 94 + len = strnlen(dom, CIFS_MAX_DOMAINNAME_LEN) + namelen + 2; 95 + s = kmalloc(len, GFP_KERNEL); 96 + if (!s) 97 + return -ENOMEM; 77 98 78 - rc = cifs_convert_address(ip_addr, ip, strlen(ip)); 79 - kfree(ip); 80 - 81 - if (!rc) { 82 - cifs_dbg(FYI, "%s: unable to determine ip address\n", __func__); 83 - rc = -EHOSTUNREACH; 84 - } else 85 - rc = 0; 99 + scnprintf(s, len, "%.*s.%s", (int)namelen, name, dom); 100 + rc = resolve_name(s, len - 1, ip_addr, expiry); 101 + kfree(s); 102 + if (!rc) 103 + return 0; 86 104 } 87 - return rc; 105 + return resolve_name(name, namelen, ip_addr, expiry); 88 106 }
+2 -1
fs/smb/client/dns_resolve.h
··· 14 14 #include <linux/net.h> 15 15 16 16 #ifdef __KERNEL__ 17 - int dns_resolve_server_name_to_ip(const char *unc, struct sockaddr *ip_addr, time64_t *expiry); 17 + int dns_resolve_server_name_to_ip(const char *dom, const char *unc, 18 + struct sockaddr *ip_addr, time64_t *expiry); 18 19 #endif /* KERNEL */ 19 20 20 21 #endif /* _DNS_RESOLVE_H */
+4
fs/smb/client/fs_context.c
··· 385 385 new_ctx->source = NULL; 386 386 new_ctx->iocharset = NULL; 387 387 new_ctx->leaf_fullpath = NULL; 388 + new_ctx->dns_dom = NULL; 388 389 /* 389 390 * Make sure to stay in sync with smb3_cleanup_fs_context_contents() 390 391 */ ··· 400 399 DUP_CTX_STR(nodename); 401 400 DUP_CTX_STR(iocharset); 402 401 DUP_CTX_STR(leaf_fullpath); 402 + DUP_CTX_STR(dns_dom); 403 403 404 404 return 0; 405 405 } ··· 1865 1863 ctx->prepath = NULL; 1866 1864 kfree(ctx->leaf_fullpath); 1867 1865 ctx->leaf_fullpath = NULL; 1866 + kfree(ctx->dns_dom); 1867 + ctx->dns_dom = NULL; 1868 1868 } 1869 1869 1870 1870 void
+1
fs/smb/client/fs_context.h
··· 295 295 bool dfs_automount:1; /* set for dfs automount only */ 296 296 enum cifs_reparse_type reparse_type; 297 297 bool dfs_conn:1; /* set for dfs mounts */ 298 + char *dns_dom; 298 299 }; 299 300 300 301 extern const struct fs_parameter_spec smb3_fs_parameters[];
+2 -1
fs/smb/client/misc.c
··· 1189 1189 1190 1190 cifs_dbg(FYI, "%s: target name: %s\n", __func__, target + 2); 1191 1191 1192 - rc = dns_resolve_server_name_to_ip(target, (struct sockaddr *)&ss, NULL); 1192 + rc = dns_resolve_server_name_to_ip(server->dns_dom, target, 1193 + (struct sockaddr *)&ss, NULL); 1193 1194 kfree(target); 1194 1195 1195 1196 if (rc < 0)