Commits

Gordon Ross committed 2d7fbeb Draft

1930 smb_match functions incorrectly handle multibyte characters
1931 smb_convert_wildcards incorrectly expands < at the end of the pattern
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
Reviewed by: Eric Schrock <eric.schrock@delphix.com>
Approved by: Richard Lowe <richlowe@richlowe.net>

  • Participants
  • Parent commits eed531d

Comments (0)

Files changed (8)

usr/src/common/smbsrv/smb_match.c

  *
  * CDDL HEADER END
  */
+
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
  */
 
 #ifndef _KERNEL
  */
 #define	SMB_MATCH_DEPTH_MAX	32
 
-#define	SMB_CRC_POLYNOMIAL	0xD8B5D8B5
+struct match_priv {
+	int depth;
+	boolean_t ci;
+};
 
-static int smb_match_private(const char *, const char *, int *);
-static int smb_match_ci_private(const char *, const char *, int *);
+static int smb_match_private(const char *, const char *, struct match_priv *);
+
+static const char smb_wildcards[] = "*?<>\"";
 
 /*
- * smb_match
+ * Return B_TRUE if pattern contains wildcards
  */
 boolean_t
-smb_match(char *patn, char *str)
+smb_contains_wildcards(const char *pattern)
 {
-	int depth = 0;
 
-	return (smb_match_private(patn, str, &depth) == 1);
+	return (strpbrk(pattern, smb_wildcards) != NULL);
 }
 
 /*
- * The '*' character matches multiple characters.
- * The '?' character matches a single character.
- *
- * If the pattern has trailing '?'s then it matches the specified number
- * of characters or less.  For example, "x??" matches "xab", "xa" and "x",
- * but not "xabc".
- *
- * Returns:
- * 1	match
- * 0	no-match
- * -1	no-match, too many wildcards in pattern
+ * NT-compatible file name match function.  [MS-FSA 3.1.4.4]
+ * Returns TRUE if there is a match.
  */
-static int
-smb_match_private(const char *patn, const char *str, int *depth)
+boolean_t
+smb_match(const char *p, const char *s, boolean_t ci)
 {
+	struct match_priv priv;
 	int rc;
 
-	for (;;) {
-		switch (*patn) {
-		case '\0':
-			return (*str == '\0');
+	/*
+	 * Optimize common patterns that match everything:
+	 * ("*", "<\"*")  That second one is the converted
+	 * form of "*.*" after smb_convert_wildcards() does
+	 * its work on it for an old LM client. Note that a
+	 * plain "*.*" never gets this far.
+	 */
+	if (p[0] == '*' && p[1] == '\0')
+		return (B_TRUE);
+	if (p[0] == '<' && p[1] == '\"' && p[2] == '*' && p[3] == '\0')
+		return (B_TRUE);
 
-		case '?':
-			if (*str != 0) {
-				str++;
-				patn++;
-				continue;
-			} else {
-				return (0);
-			}
-			/*NOTREACHED*/
+	/*
+	 * Match string ".." as if "."  This is Windows behavior
+	 * (not mentioned in MS-FSA) that was determined using
+	 * the Samba masktest program.
+	 */
+	if (s[0] == '.' && s[1] == '.' && s[2] == '\0')
+		s++;
 
-		case '*':
-			patn += strspn(patn, "*");
-			if (*patn == '\0')
-				return (1);
+	/*
+	 * Optimize simple patterns (no wildcards)
+	 */
+	if (NULL == strpbrk(p, smb_wildcards)) {
+		if (ci)
+			rc = smb_strcasecmp(p, s, 0);
+		else
+			rc = strcmp(p, s);
+		return (rc == 0);
+	}
 
-			if ((*depth)++ >= SMB_MATCH_DEPTH_MAX)
-				return (-1);
-
-			while (*str) {
-				rc = smb_match_private(patn, str, depth);
-				if (rc != 0)
-					return (rc);
-				str++;
-			}
-			return (0);
-
-		default:
-			if (*str != *patn)
-				return (0);
-			str++;
-			patn++;
-			continue;
-		}
-	}
-	/*NOTREACHED*/
+	/*
+	 * Do real wildcard match.
+	 */
+	priv.depth = 0;
+	priv.ci = ci;
+	rc = smb_match_private(p, s, &priv);
+	return (rc == 1);
 }
 
 /*
- * smb_match_ci
- */
-boolean_t
-smb_match_ci(char *patn, char *str)
-{
-	int depth = 0;
-
-	return (smb_match_ci_private(patn, str, &depth) == 1);
-}
-
-/*
- * The '*' character matches multiple characters.
- * The '?' character matches a single character.
+ * Internal file name match function.  [MS-FSA 3.1.4.4]
+ * This does the full expression evaluation.
  *
- * If the pattern has trailing '?'s then it matches the specified number
- * of characters or less.  For example, "x??" matches "xab", "xa" and "x",
- * but not "xabc".
+ * '*' matches zero of more of any characters.
+ * '?' matches exactly one of any character.
+ * '<' matches any string up through the last dot or EOS.
+ * '>' matches any one char not a dot, dot at EOS, or EOS.
+ * '"' matches a dot, or EOS.
  *
  * Returns:
- * 1	match
- * 0	no-match
- * -1	no-match, too many wildcards in pattern
+ *  1	match
+ *  0	no-match
+ * -1	no-match, error (illseq, too many wildcards in pattern, ...)
+ *
+ * Note that both the pattern and the string are in multi-byte form.
+ *
+ * The implementation of this is quite tricky.  First note that it
+ * can call itself recursively, though it limits the recursion depth.
+ * Each switch case in the while loop can basically do one of three
+ * things: (a) return "Yes, match", (b) return "not a match", or
+ * continue processing the match pattern.  The cases for wildcards
+ * that may match a variable number of characters ('*' and '<') do
+ * recursive calls, looking for a match of the remaining pattern,
+ * starting at the current and later positions in the string.
  */
 static int
-smb_match_ci_private(const char *patn, const char *str, int *depth)
+smb_match_private(const char *pat, const char *str, struct match_priv *priv)
 {
-	const char	*p;
-	smb_wchar_t	wc1, wc2;
-	int		nbytes1, nbytes2;
+	const char	*limit;
+	char		pc;		/* current pattern char */
 	int		rc;
+	smb_wchar_t	wcpat, wcstr;	/* current wchar in pat, str */
+	int		nbpat, nbstr;	/* multi-byte length of it */
+
+	if (priv->depth >= SMB_MATCH_DEPTH_MAX)
+		return (-1);
 
 	/*
-	 * "<" is a special pattern that matches only those names that do
-	 * NOT have an extension. "." and ".." are ok.
+	 * Advance over one multi-byte char, used in cases like
+	 * '?' or '>' where "match one character" needs to be
+	 * interpreted as "match one multi-byte sequence".
+	 *
+	 * This	macro needs to consume the semicolon following
+	 * each place it appears, so this is carefully written
+	 * as an if/else with a missing semicolon at the end.
 	 */
-	if (strcmp(patn, "<") == 0) {
-		if ((strcmp(str, ".") == 0) || (strcmp(str, "..") == 0))
-			return (1);
-		if (strchr(str, '.') == 0)
-			return (1);
-		return (0);
-	}
+#define	ADVANCE(str) \
+	if ((nbstr = smb_mbtowc(NULL, str, MTS_MB_CHAR_MAX)) < 1) \
+		return (-1); \
+	else \
+		str += nbstr	/* no ; */
 
-	for (;;) {
-		switch (*patn) {
-		case '\0':
-			return (*str == '\0');
+	/*
+	 * We move pat forward in each switch case so that the
+	 * default case can move it by a whole multi-byte seq.
+	 */
+	while ((pc = *pat) != '\0') {
+		switch (pc) {
 
-		case '?':
-			if (*str != 0) {
-				str++;
-				patn++;
+		case '?':	/* exactly one of any character */
+			pat++;
+			if (*str != '\0') {
+				ADVANCE(str);
 				continue;
-			} else {
-				p = patn;
-				p += strspn(p, "?");
-				return ((*p == '\0') ? 1 : 0);
 			}
-			/*NOTREACHED*/
-
-		case '*':
-			patn += strspn(patn, "*");
-			if (*patn == '\0')
-				return (1);
-
-			if ((*depth)++ >= SMB_MATCH_DEPTH_MAX)
-				return (-1);
-
-			while (*str) {
-				rc = smb_match_ci_private(patn, str, depth);
-				if (rc != 0)
-					return (rc);
-				str++;
-			}
+			/* EOS: no-match */
 			return (0);
 
-		default:
-			nbytes1 = smb_mbtowc(&wc1, patn, MTS_MB_CHAR_MAX);
-			nbytes2 = smb_mbtowc(&wc2, str, MTS_MB_CHAR_MAX);
-			if ((nbytes1 == -1) || (nbytes2 == -1))
+		case '*':	/* zero or more of any characters */
+			pat++;
+			/* Optimize '*' at end of pattern. */
+			if (*pat == '\0')
+				return (1); /* match */
+			while (*str != '\0') {
+				priv->depth++;
+				rc = smb_match_private(pat, str, priv);
+				priv->depth--;
+				if (rc != 0)
+					return (rc); /* match */
+				ADVANCE(str);
+			}
+			continue;
+
+		case '<':	/* any string up through the last dot or EOS */
+			pat++;
+			if ((limit = strrchr(str, '.')) != NULL)
+				limit++;
+			while (*str != '\0' && str != limit) {
+				priv->depth++;
+				rc = smb_match_private(pat, str, priv);
+				priv->depth--;
+				if (rc != 0)
+					return (rc); /* match */
+				ADVANCE(str);
+			}
+			continue;
+
+		case '>':	/* anything not a dot, dot at EOS, or EOS */
+			pat++;
+			if (*str == '.') {
+				if (str[1] == '\0') {
+					/* dot at EOS */
+					str++;	/* ADVANCE over '.' */
+					continue;
+				}
+				/* dot NOT at EOS: no-match */
+				return (0);
+			}
+			if (*str != '\0') {
+				/* something not a dot */
+				ADVANCE(str);
+				continue;
+			}
+			continue;
+
+		case '\"':	/* dot, or EOS */
+			pat++;
+			if (*str == '.') {
+				str++;	/* ADVANCE over '.' */
+				continue;
+			}
+			if (*str == '\0') {
+				continue;
+			}
+			/* something else: no-match */
+			return (0);
+
+		default:	/* not a wildcard */
+			nbpat = smb_mbtowc(&wcpat, pat, MTS_MB_CHAR_MAX);
+			nbstr = smb_mbtowc(&wcstr, str, MTS_MB_CHAR_MAX);
+			/* make sure we advance */
+			if (nbpat < 1 || nbstr < 1)
 				return (-1);
-
-			if (wc1 != wc2) {
-				wc1 = smb_tolower(wc1);
-				wc2 = smb_tolower(wc2);
-				if (wc1 != wc2)
-					return (0);
+			if (wcpat == wcstr) {
+				pat += nbpat;
+				str += nbstr;
+				continue;
 			}
-
-			patn += nbytes1;
-			str += nbytes2;
-			continue;
+			if (priv->ci) {
+				wcpat = smb_tolower(wcpat);
+				wcstr = smb_tolower(wcstr);
+				if (wcpat == wcstr) {
+					pat += nbpat;
+					str += nbstr;
+					continue;
+				}
+			}
+			return (0); /* no-match */
 		}
 	}
-	/*NOTREACHED*/
+	return (*str == '\0');
 }
-
-uint32_t
-smb_crc_gen(uint8_t *buf, size_t len)
-{
-	uint32_t crc = SMB_CRC_POLYNOMIAL;
-	uint8_t *p;
-	int i;
-
-	for (p = buf, i = 0; i < len; ++i, ++p) {
-		crc = (crc ^ (uint32_t)*p) + (crc << 12);
-
-		if (crc == 0 || crc == 0xFFFFFFFF)
-			crc = SMB_CRC_POLYNOMIAL;
-	}
-
-	return (crc);
-}

usr/src/common/smbsrv/smb_xdr.c

  */
 /*
  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <sys/sunddi.h>
 
 	return (TRUE);
 }
+
+/*
+ * The smbsrv ioctl callers include a CRC of the XDR encoded data,
+ * and kmod ioctl handler checks it.  Both use this function.  This
+ * is not really XDR related, but this is as good a place as any.
+ */
+#define	SMB_CRC_POLYNOMIAL	0xD8B5D8B5
+uint32_t
+smb_crc_gen(uint8_t *buf, size_t len)
+{
+	uint32_t crc = SMB_CRC_POLYNOMIAL;
+	uint8_t *p;
+	int i;
+
+	for (p = buf, i = 0; i < len; ++i, ++p) {
+		crc = (crc ^ (uint32_t)*p) + (crc << 12);
+
+		if (crc == 0 || crc == 0xFFFFFFFF)
+			crc = SMB_CRC_POLYNOMIAL;
+	}
+
+	return (crc);
+}

usr/src/lib/smbsrv/libsmb/common/mapfile-vers

 #
 #
 # Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
-# Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
 #
 
 #
 	smb_mac_init;
 	smb_mac_sign;
 	smb_match;
-	smb_match_ci;
 	smb_match_netlogon_seqnum;
 	smb_mbstos;
 	smb_mbstowcs;

usr/src/uts/common/fs/smbsrv/smb_delete.c

 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <smbsrv/smb_kproto.h>
 	/* fname component is, or resolves to, '.' (dot) */
 	if ((strcmp(pn->pn_fname, ".") == 0) ||
 	    (SMB_SEARCH_DIRECTORY(fqi->fq_sattr) &&
-	    (smb_match(pn->pn_fname, ".")))) {
+	    (smb_match(pn->pn_fname, ".", B_FALSE)))) {
 		smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
 		    ERRDOS, ERROR_INVALID_NAME);
 		return (-1);

usr/src/uts/common/fs/smbsrv/smb_kutil.c

  *
  * CDDL HEADER END
  */
+
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <sys/param.h>
 }
 
 /*
- * Return B_TRUE if pattern contains wildcards
- */
-boolean_t
-smb_contains_wildcards(const char *pattern)
-{
-	static const char *wildcards = "*?";
-
-	return (strpbrk(pattern, wildcards) != NULL);
-}
-
-/*
- * When converting wildcards a '.' in a name is treated as a base and
- * extension separator even if the name is longer than 8.3.
  *
- * The '*' character matches an entire part of the name.  For example,
- * "*.abc" matches any name with an extension of "abc".
- *
- * The '?' character matches a single character.
- * If the base contains all ? (8 or more) then it is treated as *.
- * If the extension contains all ? (3 or more) then it is treated as *.
- *
- * Clients convert ASCII wildcards to Unicode wildcards as follows:
+ * Convert old-style (DOS, LanMan) wildcard strings to NT style.
+ * This should ONLY happen to patterns that come from old clients,
+ * meaning dialect LANMAN2_1 etc. (dialect < NT_LM_0_12).
  *
  *	? is converted to >
- *	. is converted to " if it is followed by ? or *
  *	* is converted to < if it is followed by .
+ *	. is converted to " if it is followed by ? or * or end of pattern
  *
- * Note that clients convert "*." to '< and drop the '.' but "*.txt"
- * is sent as "<.TXT", i.e.
- *
- * 	dir *.		->	dir <
- * 	dir *.txt	->	dir <.TXT
- *
- * Since " and < are illegal in Windows file names, we always convert
- * these Unicode wildcards without checking the following character.
+ * Note: modifies pattern in place.
  */
 void
 smb_convert_wildcards(char *pattern)
 {
-	static char *match_all[] = {
-		"*.",
-		"*.*"
-	};
-	char	*extension;
 	char	*p;
-	int	len;
-	int	i;
 
-	/*
-	 * Special case "<" for "dir *.", and fast-track for "*".
-	 */
-	if ((*pattern == '<') || (*pattern == '*')) {
-		if (*(pattern + 1) == '\0') {
-			*pattern = '*';
-			return;
-		}
-	}
-
-	for (p = pattern; *p != '\0'; ++p) {
+	for (p = pattern; *p != '\0'; p++) {
 		switch (*p) {
-		case '<':
-			*p = '*';
+		case '?':
+			*p = '>';
 			break;
-		case '>':
-			*p = '?';
+		case '*':
+			if (p[1] == '.')
+				*p = '<';
 			break;
-		case '\"':
-			*p = '.';
-			break;
-		default:
-			break;
-		}
-	}
-
-	/*
-	 * Replace "????????.ext" with "*.ext".
-	 */
-	p = pattern;
-	p += strspn(p, "?");
-	if (*p == '.') {
-		*p = '\0';
-		len = strlen(pattern);
-		*p = '.';
-		if (len >= SMB_NAME83_BASELEN) {
-			*pattern = '*';
-			(void) strlcpy(pattern + 1, p, MAXPATHLEN - 1);
-		}
-	}
-
-	/*
-	 * Replace "base.???" with 'base.*'.
-	 */
-	if ((extension = strrchr(pattern, '.')) != NULL) {
-		p = ++extension;
-		p += strspn(p, "?");
-		if (*p == '\0') {
-			len = strlen(extension);
-			if (len >= SMB_NAME83_EXTLEN) {
-				*extension = '\0';
-				(void) strlcat(pattern, "*", MAXPATHLEN);
-			}
-		}
-	}
-
-	/*
-	 * Replace anything that matches an entry in match_all with "*".
-	 */
-	for (i = 0; i < sizeof (match_all) / sizeof (match_all[0]); ++i) {
-		if (strcmp(pattern, match_all[i]) == 0) {
-			(void) strlcpy(pattern, "*", MAXPATHLEN);
+		case '.':
+			if (p[1] == '?' || p[1] == '*' || p[1] == '\0')
+				*p = '\"';
 			break;
 		}
 	}

usr/src/uts/common/fs/smbsrv/smb_odir.c

  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
  */
 
 /*
 
 	tree = sr->tid_tree;
 
-	smb_convert_wildcards(path);
+	if (sr->session->dialect < NT_LM_0_12)
+		smb_convert_wildcards(path);
 
 	rc = smb_pathname_reduce(sr, sr->user_cr, path,
 	    tree->t_snode, tree->t_snode, &dnode, pattern);
  * - If shortnames are supported, generate the shortname from
  *   odirent->od_name and check if it matches od->d_pattern.
  */
-boolean_t
+static boolean_t
 smb_odir_match_name(smb_odir_t *od, smb_odirent_t *odirent)
 {
 	char	*name = odirent->od_name;
 	char	shortname[SMB_SHORTNAMELEN];
 	ino64_t	ino = odirent->od_ino;
+	boolean_t ci = (od->d_flags & SMB_ODIR_FLAG_IGNORE_CASE) != 0;
 
 	if (smb_is_reserved_dos_name(name))
 		return (B_FALSE);
 
-	if (smb_match_ci(od->d_pattern, name))
+	if (smb_match(od->d_pattern, name, ci))
 		return (B_TRUE);
 
 	if (od->d_flags & SMB_ODIR_FLAG_SHORTNAMES) {
 		smb_mangle(name, ino, shortname, SMB_SHORTNAMELEN);
-		if (smb_match_ci(od->d_pattern, shortname))
+		if (smb_match(od->d_pattern, shortname, ci))
 			return (B_TRUE);
 	}
 

usr/src/uts/common/fs/smbsrv/smb_pathname.c

  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
  */
 
 #include <smbsrv/smb_kproto.h>
 		return;
 	}
 
-	/* perform unicode wildcard conversion */
-	smb_convert_wildcards(pn->pn_path);
+	if (sr->session->dialect < NT_LM_0_12)
+		smb_convert_wildcards(pn->pn_path);
 
 	/* treat '/' as '\\' */
 	(void) strsubst(pn->pn_path, '/', '\\');

usr/src/uts/common/smbsrv/string.h

  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
  */
 
 #ifndef	_SMBSRV_STRING_H
 int smb_isstrlwr(const char *);
 int smb_strcasecmp(const char *, const char *, size_t);
 
-boolean_t smb_match(char *, char *);
-boolean_t smb_match_ci(char *, char *);
+boolean_t smb_match(const char *, const char *, boolean_t);
 
 size_t smb_mbstowcs(smb_wchar_t *, const char *, size_t);
 size_t smb_wcstombs(char *, const smb_wchar_t *, size_t);