Commit e0b1f591 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'v6.14-rc-smb3-client-fixes-part' of git://git.samba.org/sfrench/cifs-2.6

Pull smb client updates from Steve French:

 - Fix oops in DebugData when link speed 0

 - Two reparse point fixes

 - Ten DFS (global namespace) fixes

 - Symlink error handling fix

 - Two SMB1 fixes

 - Four cleanup fixes

 - Improved debugging of status codes

 - Fix incorrect output of tracepoints for compounding, and add missing
   compounding tracepoint

* tag 'v6.14-rc-smb3-client-fixes-part' of git://git.samba.org/sfrench/cifs-2.6: (23 commits)
  smb: client: handle lack of EA support in smb2_query_path_info()
  smb: client: don't check for @leaf_fullpath in match_server()
  smb: client: get rid of TCP_Server_Info::refpath_lock
  cifs: Remove duplicate struct reparse_symlink_data and SYMLINK_FLAG_RELATIVE
  cifs: Do not attempt to call CIFSGetSrvInodeNumber() without CAP_INFOLEVEL_PASSTHRU
  cifs: Do not attempt to call CIFSSMBRenameOpenFile() without CAP_INFOLEVEL_PASSTHRU
  cifs: Remove declaration of dead CIFSSMBQuerySymLink function
  cifs: Fix printing Status code into dmesg
  cifs: Add missing NT_STATUS_* codes from nterr.h to nterr.c
  cifs: Fix endian types in struct rfc1002_session_packet
  cifs: Use cifs_autodisable_serverino() for disabling CIFS_MOUNT_SERVER_INUM in readdir.c
  smb3: add missing tracepoint for querying wsl EAs
  smb: client: fix order of arguments of tracepoints
  smb: client: fix oops due to unset link speed
  smb: client: correctly handle ErrorContextData as a flexible array
  smb: client: don't retry DFS targets on server shutdown
  smb: client: fix return value of parse_dfs_referrals()
  smb: client: optimize referral walk on failed link targets
  smb: client: provide dns_resolve_{unc,name} helpers
  smb: client: parse DNS domain name from domain= option
  ...
parents e814f3fd 3681c74d
Loading
Loading
Loading
Loading
+82 −80
Original line number Diff line number Diff line
@@ -315,59 +315,72 @@ build_avpair_blob(struct cifs_ses *ses, const struct nls_table *nls_cp)
	return 0;
}

/* Server has provided av pairs/target info in the type 2 challenge
 * packet and we have plucked it and stored within smb session.
 * We parse that blob here to find netbios domain name to be used
 * as part of ntlmv2 authentication (in Target String), if not already
 * specified on the command line.
 * If this function returns without any error but without fetching
 * domain name, authentication may fail against some server but
 * may not fail against other (those who are not very particular
 * about target string i.e. for some, just user name might suffice.
#define AV_TYPE(av)		(le16_to_cpu(av->type))
#define AV_LEN(av)		(le16_to_cpu(av->length))
#define AV_DATA_PTR(av)	((void *)av->data)

#define av_for_each_entry(ses, av) \
	for (av = NULL; (av = find_next_av(ses, av));)

static struct ntlmssp2_name *find_next_av(struct cifs_ses *ses,
					  struct ntlmssp2_name *av)
{
	u16 len;
	u8 *end;

	end = (u8 *)ses->auth_key.response + ses->auth_key.len;
	if (!av) {
		if (unlikely(!ses->auth_key.response || !ses->auth_key.len))
			return NULL;
		av = (void *)ses->auth_key.response;
	} else {
		av = (void *)((u8 *)av + sizeof(*av) + AV_LEN(av));
	}

	if ((u8 *)av + sizeof(*av) > end)
		return NULL;

	len = AV_LEN(av);
	if (AV_TYPE(av) == NTLMSSP_AV_EOL)
		return NULL;
	if (!len || (u8 *)av + sizeof(*av) + len > end)
		return NULL;
	return av;
}

/*
 * Check if server has provided av pair of @type in the NTLMSSP
 * CHALLENGE_MESSAGE blob.
 */
static int
find_domain_name(struct cifs_ses *ses, const struct nls_table *nls_cp)
static int find_av_name(struct cifs_ses *ses, u16 type, char **name, u16 maxlen)
{
	unsigned int attrsize;
	unsigned int type;
	unsigned int onesize = sizeof(struct ntlmssp2_name);
	unsigned char *blobptr;
	unsigned char *blobend;
	struct ntlmssp2_name *attrptr;
	const struct nls_table *nlsc = ses->local_nls;
	struct ntlmssp2_name *av;
	u16 len, nlen;

	if (!ses->auth_key.len || !ses->auth_key.response)
	if (*name)
		return 0;

	blobptr = ses->auth_key.response;
	blobend = blobptr + ses->auth_key.len;

	while (blobptr + onesize < blobend) {
		attrptr = (struct ntlmssp2_name *) blobptr;
		type = le16_to_cpu(attrptr->type);
		if (type == NTLMSSP_AV_EOL)
			break;
		blobptr += 2; /* advance attr type */
		attrsize = le16_to_cpu(attrptr->length);
		blobptr += 2; /* advance attr size */
		if (blobptr + attrsize > blobend)
			break;
		if (type == NTLMSSP_AV_NB_DOMAIN_NAME) {
			if (!attrsize || attrsize >= CIFS_MAX_DOMAINNAME_LEN)
				break;
			if (!ses->domainName) {
				ses->domainName =
					kmalloc(attrsize + 1, GFP_KERNEL);
				if (!ses->domainName)
	av_for_each_entry(ses, av) {
		len = AV_LEN(av);
		if (AV_TYPE(av) != type)
			continue;
		if (!IS_ALIGNED(len, sizeof(__le16))) {
			cifs_dbg(VFS | ONCE, "%s: bad length(%u) for type %u\n",
				 __func__, len, type);
			continue;
		}
		nlen = len / sizeof(__le16);
		if (nlen <= maxlen) {
			++nlen;
			*name = kmalloc(nlen, GFP_KERNEL);
			if (!*name)
				return -ENOMEM;
				cifs_from_utf16(ses->domainName,
					(__le16 *)blobptr, attrsize, attrsize,
					nls_cp, NO_MAP_UNI_RSVD);
			cifs_from_utf16(*name, AV_DATA_PTR(av), nlen,
					len, nlsc, NO_MAP_UNI_RSVD);
			break;
		}
	}
		blobptr += attrsize; /* advance attr  value */
	}

	return 0;
}

@@ -377,40 +390,16 @@ find_domain_name(struct cifs_ses *ses, const struct nls_table *nls_cp)
 * as part of ntlmv2 authentication (or local current time as
 * default in case of failure)
 */
static __le64
find_timestamp(struct cifs_ses *ses)
static __le64 find_timestamp(struct cifs_ses *ses)
{
	unsigned int attrsize;
	unsigned int type;
	unsigned int onesize = sizeof(struct ntlmssp2_name);
	unsigned char *blobptr;
	unsigned char *blobend;
	struct ntlmssp2_name *attrptr;
	struct ntlmssp2_name *av;
	struct timespec64 ts;

	if (!ses->auth_key.len || !ses->auth_key.response)
		return 0;

	blobptr = ses->auth_key.response;
	blobend = blobptr + ses->auth_key.len;

	while (blobptr + onesize < blobend) {
		attrptr = (struct ntlmssp2_name *) blobptr;
		type = le16_to_cpu(attrptr->type);
		if (type == NTLMSSP_AV_EOL)
			break;
		blobptr += 2; /* advance attr type */
		attrsize = le16_to_cpu(attrptr->length);
		blobptr += 2; /* advance attr size */
		if (blobptr + attrsize > blobend)
			break;
		if (type == NTLMSSP_AV_TIMESTAMP) {
			if (attrsize == sizeof(u64))
				return *((__le64 *)blobptr);
		}
		blobptr += attrsize; /* advance attr value */
	av_for_each_entry(ses, av) {
		if (AV_TYPE(av) == NTLMSSP_AV_TIMESTAMP &&
		    AV_LEN(av) == sizeof(u64))
			return *((__le64 *)AV_DATA_PTR(av));
	}

	ktime_get_real_ts64(&ts);
	return cpu_to_le64(cifs_UnixTimeToNT(ts));
}
@@ -563,16 +552,29 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp)
	if (ses->server->negflavor == CIFS_NEGFLAVOR_EXTENDED) {
		if (!ses->domainName) {
			if (ses->domainAuto) {
				rc = find_domain_name(ses, nls_cp);
				if (rc) {
					cifs_dbg(VFS, "error %d finding domain name\n",
						 rc);
				/*
				 * Domain (workgroup) hasn't been specified in
				 * mount options, so try to find it in
				 * CHALLENGE_MESSAGE message and then use it as
				 * part of NTLMv2 authentication.
				 */
				rc = find_av_name(ses, NTLMSSP_AV_NB_DOMAIN_NAME,
						  &ses->domainName,
						  CIFS_MAX_DOMAINNAME_LEN);
				if (rc)
					goto setup_ntlmv2_rsp_ret;
				}
			} else {
				ses->domainName = kstrdup("", GFP_KERNEL);
				if (!ses->domainName) {
					rc = -ENOMEM;
					goto setup_ntlmv2_rsp_ret;
				}
			}
		}
		rc = find_av_name(ses, NTLMSSP_AV_DNS_DOMAIN_NAME,
				  &ses->dns_dom, CIFS_MAX_DOMAINNAME_LEN);
		if (rc)
			goto setup_ntlmv2_rsp_ret;
	} else {
		rc = build_avpair_blob(ses, nls_cp);
		if (rc) {
+24 −11
Original line number Diff line number Diff line
@@ -811,23 +811,15 @@ struct TCP_Server_Info {
	bool use_swn_dstaddr;
	struct sockaddr_storage swn_dstaddr;
#endif
	struct mutex refpath_lock; /* protects leaf_fullpath */
	/*
	 * 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.
	 *
	 * Protected by @refpath_lock and @srv_lock.  The @refpath_lock is
	 * mostly 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 comparisons against
	 * both fields as in mount(2) and cache refresh.
	 * Canonical DFS referral path used in cifs_reconnect() for failover as
	 * well as in DFS cache refresher.
	 *
	 * format: \\HOST\SHARE[\OPTIONAL PATH]
	 */
	char *leaf_fullpath;
	bool dfs_conn:1;
	char dns_dom[CIFS_MAX_DOMAINNAME_LEN + 1];
};

static inline bool is_smb1(struct TCP_Server_Info *server)
@@ -1154,6 +1146,7 @@ struct cifs_ses {
	/* ========= end: protected by chan_lock ======== */
	struct cifs_ses *dfs_root_ses;
	struct nls_table *local_nls;
	char *dns_dom; /* FQDN of the domain */
};

static inline bool
@@ -2311,4 +2304,24 @@ static inline bool cifs_ses_exiting(struct cifs_ses *ses)
	return ret;
}

static inline bool cifs_netbios_name(const char *name, size_t namelen)
{
	bool ret = false;
	size_t i;

	if (namelen >= 1 && namelen <= RFC1001_NAME_LEN) {
		for (i = 0; i < namelen; i++) {
			const unsigned char c = name[i];

			if (c == '\\' || c == '/' || c == ':' || c == '*' ||
			    c == '?' || c == '"' || c == '<' || c == '>' ||
			    c == '|' || c == '.')
				return false;
			if (!ret && isalpha(c))
				ret = true;
		}
	}
	return ret;
}

#endif	/* _CIFS_GLOB_H */
+1 −17
Original line number Diff line number Diff line
@@ -649,7 +649,7 @@ typedef union smb_com_session_setup_andx {
struct ntlmssp2_name {
	__le16 type;
	__le16 length;
/*	char   name[length]; */
	__u8 data[];
} __attribute__((packed));

struct ntlmv2_resp {
@@ -1484,22 +1484,6 @@ struct file_notify_information {
	__u8  FileName[];
} __attribute__((packed));

/* For IO_REPARSE_TAG_SYMLINK */
struct reparse_symlink_data {
	__le32	ReparseTag;
	__le16	ReparseDataLength;
	__u16	Reserved;
	__le16	SubstituteNameOffset;
	__le16	SubstituteNameLength;
	__le16	PrintNameOffset;
	__le16	PrintNameLength;
	__le32	Flags;
	char	PathBuffer[];
} __attribute__((packed));

/* Flag above */
#define SYMLINK_FLAG_RELATIVE 0x00000001

/* For IO_REPARSE_TAG_NFS */
#define NFS_SPECFILE_LNK	0x00000000014B4E4C
#define NFS_SPECFILE_CHR	0x0000000000524843
+1 −4
Original line number Diff line number Diff line
@@ -474,9 +474,6 @@ extern int cifs_query_reparse_point(const unsigned int xid,
				    const char *full_path,
				    u32 *tag, struct kvec *rsp,
				    int *rsp_buftype);
extern int CIFSSMBQuerySymLink(const unsigned int xid, struct cifs_tcon *tcon,
			       __u16 fid, char **symlinkinfo,
			       const struct nls_table *nls_codepage);
extern int CIFSSMB_set_compression(const unsigned int xid,
				   struct cifs_tcon *tcon, __u16 fid);
extern int CIFS_open(const unsigned int xid, struct cifs_open_parms *oparms,
@@ -680,7 +677,7 @@ static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses,
}

int match_target_ip(struct TCP_Server_Info *server,
		    const char *share, size_t share_len,
		    const char *host, size_t hostlen,
		    bool *result);
int cifs_inval_name_dfs_link_error(const unsigned int xid,
				   struct cifs_tcon *tcon,
+47 −86
Original line number Diff line number Diff line
@@ -72,10 +72,8 @@ static void cifs_prune_tlinks(struct work_struct *work);
 */
static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server)
{
	int rc;
	int len;
	char *unc;
	struct sockaddr_storage ss;
	int rc;

	if (!server->hostname)
		return -EINVAL;
@@ -84,32 +82,18 @@ static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server)
	if (server->hostname[0] == '\0')
		return 0;

	len = strlen(server->hostname) + 3;

	unc = kmalloc(len, GFP_KERNEL);
	if (!unc) {
		cifs_dbg(FYI, "%s: failed to create UNC path\n", __func__);
		return -ENOMEM;
	}
	scnprintf(unc, len, "\\\\%s", server->hostname);

	spin_lock(&server->srv_lock);
	ss = server->dstaddr;
	spin_unlock(&server->srv_lock);

	rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL);
	kfree(unc);

	if (rc < 0) {
		cifs_dbg(FYI, "%s: failed to resolve server part of %s to IP: %d\n",
			 __func__, server->hostname, rc);
	} else {
	rc = dns_resolve_name(server->dns_dom, server->hostname,
			      strlen(server->hostname),
			      (struct sockaddr *)&ss);
	if (!rc) {
		spin_lock(&server->srv_lock);
		memcpy(&server->dstaddr, &ss, sizeof(server->dstaddr));
		spin_unlock(&server->srv_lock);
		rc = 0;
	}

	return rc;
}

@@ -438,7 +422,8 @@ static int __cifs_reconnect(struct TCP_Server_Info *server,
}

#ifdef CONFIG_CIFS_DFS_UPCALL
static int __reconnect_target_unlocked(struct TCP_Server_Info *server, const char *target)
static int __reconnect_target_locked(struct TCP_Server_Info *server,
				     const char *target)
{
	int rc;
	char *hostname;
@@ -471,34 +456,43 @@ static int __reconnect_target_unlocked(struct TCP_Server_Info *server, const cha
	return rc;
}

static int reconnect_target_unlocked(struct TCP_Server_Info *server, struct dfs_cache_tgt_list *tl,
static int reconnect_target_locked(struct TCP_Server_Info *server,
				   struct dfs_cache_tgt_list *tl,
				   struct dfs_cache_tgt_iterator **target_hint)
{
	int rc;
	struct dfs_cache_tgt_iterator *tit;
	int rc;

	*target_hint = NULL;

	/* If dfs target list is empty, then reconnect to last server */
	tit = dfs_cache_get_tgt_iterator(tl);
	if (!tit)
		return __reconnect_target_unlocked(server, server->hostname);
		return __reconnect_target_locked(server, server->hostname);

	/* Otherwise, try every dfs target in @tl */
	for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
		rc = __reconnect_target_unlocked(server, dfs_cache_get_tgt_name(tit));
	do {
		const char *target = dfs_cache_get_tgt_name(tit);

		spin_lock(&server->srv_lock);
		if (server->tcpStatus != CifsNeedReconnect) {
			spin_unlock(&server->srv_lock);
			return -ECONNRESET;
		}
		spin_unlock(&server->srv_lock);
		rc = __reconnect_target_locked(server, target);
		if (!rc) {
			*target_hint = tit;
			break;
		}
	}
	} while ((tit = dfs_cache_get_next_tgt(tl, tit)));
	return rc;
}

static int reconnect_dfs_server(struct TCP_Server_Info *server)
{
	struct dfs_cache_tgt_iterator *target_hint = NULL;

	const char *ref_path = server->leaf_fullpath + 1;
	DFS_CACHE_TGT_LIST(tl);
	int num_targets = 0;
	int rc = 0;
@@ -511,10 +505,8 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)
	 * 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.
	 */
	mutex_lock(&server->refpath_lock);
	if (!dfs_cache_noreq_find(server->leaf_fullpath + 1, NULL, &tl))
	if (!dfs_cache_noreq_find(ref_path, NULL, &tl))
		num_targets = dfs_cache_get_nr_tgts(&tl);
	mutex_unlock(&server->refpath_lock);
	if (!num_targets)
		num_targets = 1;

@@ -534,7 +526,7 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)
		try_to_freeze();
		cifs_server_lock(server);

		rc = reconnect_target_unlocked(server, &tl, &target_hint);
		rc = reconnect_target_locked(server, &tl, &target_hint);
		if (rc) {
			/* Failed to reconnect socket */
			cifs_server_unlock(server);
@@ -558,9 +550,7 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)
		mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
	} while (server->tcpStatus == CifsNeedReconnect);

	mutex_lock(&server->refpath_lock);
	dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, target_hint);
	mutex_unlock(&server->refpath_lock);
	dfs_cache_noreq_update_tgthint(ref_path, target_hint);
	dfs_cache_free_tgts(&tl);

	/* Need to set up echo worker again once connection has been established */
@@ -575,13 +565,8 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)

int cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session)
{
	mutex_lock(&server->refpath_lock);
	if (!server->leaf_fullpath) {
		mutex_unlock(&server->refpath_lock);
	if (!server->leaf_fullpath)
		return __cifs_reconnect(server, mark_smb_session);
	}
	mutex_unlock(&server->refpath_lock);

	return reconnect_dfs_server(server);
}
#else
@@ -1541,42 +1526,10 @@ static int match_server(struct TCP_Server_Info *server,
	if (!cifs_match_ipaddr((struct sockaddr *)&ctx->srcaddr,
			       (struct sockaddr *)&server->srcaddr))
		return 0;
	/*
	 * When matching cifs.ko superblocks (@match_super == true), we can't
	 * really match either @server->leaf_fullpath or @server->dstaddr
	 * directly since this @server might belong to a completely different
	 * server -- in case of domain-based DFS referrals or DFS links -- as
	 * provided earlier by mount(2) through 'source' and 'ip' options.
	 *
	 * Otherwise, match the DFS referral in @server->leaf_fullpath or the
	 * destination address in @server->dstaddr.
	 *
	 * When using 'nodfs' mount option, we avoid sharing it with DFS
	 * connections as they might failover.
	 */
	if (!match_super) {
		if (!ctx->nodfs) {
			if (server->leaf_fullpath) {
				if (!ctx->leaf_fullpath ||
				    strcasecmp(server->leaf_fullpath,
					       ctx->leaf_fullpath))
					return 0;
			} else if (ctx->leaf_fullpath) {
				return 0;
			}
		} else if (server->leaf_fullpath) {
			return 0;
		}
	}

	/*
	 * Match for a regular connection (address/hostname/port) which has no
	 * DFS referrals set.
	 */
	if (!server->leaf_fullpath &&
	    (strcasecmp(server->hostname, ctx->server_hostname) ||
	if (strcasecmp(server->hostname, ctx->server_hostname) ||
	    !match_server_address(server, addr) ||
	     !match_port(server, addr)))
	    !match_port(server, addr))
		return 0;

	if (!match_security(server, ctx))
@@ -1710,6 +1663,8 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
			goto out_err;
		}
	}
	if (ctx->dns_dom)
		strscpy(tcp_ses->dns_dom, ctx->dns_dom);

	if (ctx->nosharesock)
		tcp_ses->nosharesock = true;
@@ -1758,9 +1713,6 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
	INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
	INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
	mutex_init(&tcp_ses->reconnect_mutex);
#ifdef CONFIG_CIFS_DFS_UPCALL
	mutex_init(&tcp_ses->refpath_lock);
#endif
	memcpy(&tcp_ses->srcaddr, &ctx->srcaddr,
	       sizeof(tcp_ses->srcaddr));
	memcpy(&tcp_ses->dstaddr, &ctx->dstaddr,
@@ -2276,12 +2228,13 @@ cifs_set_cifscreds(struct smb3_fs_context *ctx __attribute__((unused)),
struct cifs_ses *
cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
{
	int rc = 0;
	int retries = 0;
	unsigned int xid;
	struct cifs_ses *ses;
	struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
	struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr;
	struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
	struct cifs_ses *ses;
	unsigned int xid;
	int retries = 0;
	size_t len;
	int rc = 0;

	xid = get_xid();

@@ -2371,6 +2324,14 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
		ses->domainName = kstrdup(ctx->domainname, GFP_KERNEL);
		if (!ses->domainName)
			goto get_ses_fail;

		len = strnlen(ctx->domainname, CIFS_MAX_DOMAINNAME_LEN);
		if (!cifs_netbios_name(ctx->domainname, len)) {
			ses->dns_dom = kstrndup(ctx->domainname,
						len, GFP_KERNEL);
			if (!ses->dns_dom)
				goto get_ses_fail;
		}
	}

	strscpy(ses->workstation_name, ctx->workstation_name, sizeof(ses->workstation_name));
Loading