svn commit: r317749 - in head/lib/libc: gen tests/gen

Conrad Meyer cem at FreeBSD.org
Wed May 3 15:55:31 UTC 2017


Author: cem
Date: Wed May  3 15:55:29 2017
New Revision: 317749
URL: https://svnweb.freebsd.org/changeset/base/317749

Log:
  libc glob: Avoid pathological exponential behavior
  
  Adapt glob's match() routine to use a greedy algorithm that avoids
  exponential runtime in byzantine inputs.
  
  While here, add a testcase for the byzantine input.
  
  Prompted by:	https://research.swtch.com/glob
  Authored by:	Yves Orton <demerphq at gmail.com>
  Obtained from:	Perl (33252c318625f3c6c89b816ee88481940e3e6f95)
  Sponsored by:	Dell EMC Isilon

Added:
  head/lib/libc/tests/gen/glob2_test.c   (contents, props changed)
Modified:
  head/lib/libc/gen/glob.c
  head/lib/libc/tests/gen/Makefile

Modified: head/lib/libc/gen/glob.c
==============================================================================
--- head/lib/libc/gen/glob.c	Wed May  3 15:20:40 2017	(r317748)
+++ head/lib/libc/gen/glob.c	Wed May  3 15:55:29 2017	(r317749)
@@ -903,61 +903,73 @@ globextend(const Char *path, glob_t *pgl
 }
 
 /*
- * pattern matching function for filenames.  Each occurrence of the *
- * pattern causes a recursion level.
+ * pattern matching function for filenames.
  */
 static int
 match(Char *name, Char *pat, Char *patend)
 {
 	int ok, negate_range;
-	Char c, k;
+	Char c, k, *nextp, *nextn;
 	struct xlocale_collate *table =
 		(struct xlocale_collate*)__get_locale()->components[XLC_COLLATE];
 
-	while (pat < patend) {
-		c = *pat++;
-		switch (c & M_MASK) {
-		case M_ALL:
-			if (pat == patend)
-				return (1);
-			do
-			    if (match(name, pat, patend))
-				    return (1);
-			while (*name++ != EOS);
-			return (0);
-		case M_ONE:
-			if (*name++ == EOS)
-				return (0);
-			break;
-		case M_SET:
-			ok = 0;
-			if ((k = *name++) == EOS)
-				return (0);
-			if ((negate_range = ((*pat & M_MASK) == M_NOT)) != 0)
-				++pat;
-			while (((c = *pat++) & M_MASK) != M_END)
-				if ((*pat & M_MASK) == M_RNG) {
-					if (table->__collate_load_error ?
-					    CHAR(c) <= CHAR(k) &&
-					    CHAR(k) <= CHAR(pat[1]) :
-					    __wcollate_range_cmp(CHAR(c),
-					    CHAR(k)) <= 0 &&
-					    __wcollate_range_cmp(CHAR(k),
-					    CHAR(pat[1])) <= 0)
+	nextn = NULL;
+	nextp = NULL;
+
+	while (1) {
+		while (pat < patend) {
+			c = *pat++;
+			switch (c & M_MASK) {
+			case M_ALL:
+				if (pat == patend)
+					return (1);
+				if (*name == EOS)
+					return (0);
+				nextn = name + 1;
+				nextp = pat - 1;
+				break;
+			case M_ONE:
+				if (*name++ == EOS)
+					goto fail;
+				break;
+			case M_SET:
+				ok = 0;
+				if ((k = *name++) == EOS)
+					goto fail;
+				if ((negate_range = ((*pat & M_MASK) == M_NOT)) != 0)
+					++pat;
+				while (((c = *pat++) & M_MASK) != M_END)
+					if ((*pat & M_MASK) == M_RNG) {
+						if (table->__collate_load_error ?
+						    CHAR(c) <= CHAR(k) &&
+						    CHAR(k) <= CHAR(pat[1]) :
+						    __wcollate_range_cmp(CHAR(c),
+						    CHAR(k)) <= 0 &&
+						    __wcollate_range_cmp(CHAR(k),
+						    CHAR(pat[1])) <= 0)
+							ok = 1;
+						pat += 2;
+					} else if (c == k)
 						ok = 1;
-					pat += 2;
-				} else if (c == k)
-					ok = 1;
-			if (ok == negate_range)
-				return (0);
-			break;
-		default:
-			if (*name++ != c)
-				return (0);
-			break;
+				if (ok == negate_range)
+					goto fail;
+				break;
+			default:
+				if (*name++ != c)
+					goto fail;
+				break;
+			}
 		}
+		if (*name == EOS)
+			return (1);
+
+	fail:
+		if (nextn == NULL)
+			break;
+		pat = nextp;
+		name = nextn;
 	}
-	return (*name == EOS);
+	return (0);
 }
 
 /* Free allocated data belonging to a glob_t structure. */

Modified: head/lib/libc/tests/gen/Makefile
==============================================================================
--- head/lib/libc/tests/gen/Makefile	Wed May  3 15:20:40 2017	(r317748)
+++ head/lib/libc/tests/gen/Makefile	Wed May  3 15:55:29 2017	(r317749)
@@ -8,6 +8,7 @@ ATF_TESTS_C+=		fmtmsg_test
 ATF_TESTS_C+=		fnmatch2_test
 ATF_TESTS_C+=		fpclassify2_test
 ATF_TESTS_C+=		ftw_test
+ATF_TESTS_C+=		glob2_test
 ATF_TESTS_C+=		popen_test
 ATF_TESTS_C+=		posix_spawn_test
 ATF_TESTS_C+=		wordexp_test

Added: head/lib/libc/tests/gen/glob2_test.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/lib/libc/tests/gen/glob2_test.c	Wed May  3 15:55:29 2017	(r317749)
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2017 Dell EMC Isilon
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+/*
+ * Derived from Russ Cox' pathological case test program used for the
+ * https://research.swtch.com/glob article.
+ */
+ATF_TC_WITHOUT_HEAD(glob_pathological_test);
+ATF_TC_BODY(glob_pathological_test, tc)
+{
+	struct timespec t, t2;
+	glob_t g;
+	const char *longname = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+	    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+	char pattern[1000], *p;
+	double dt;
+	unsigned i, j, k, mul;
+	int fd, rc;
+
+	fd = open(longname, O_CREAT | O_RDWR, 0666);
+	ATF_REQUIRE(fd >= 0);
+
+	/*
+	 * Test up to 100 a* groups.  Exponential implementations typically go
+	 * bang at i=7 or 8.
+	 */
+	for (i = 0; i < 100; i++) {
+		/*
+		 * Create a*...b pattern with i 'a*' groups.
+		 */
+		p = pattern;
+		for (k = 0; k < i; k++) {
+			*p++ = 'a';
+			*p++ = '*';
+		}
+		*p++ = 'b';
+		*p = '\0';
+
+		clock_gettime(CLOCK_REALTIME, &t);
+		for (j = 0; j < mul; j++) {
+			memset(&g, 0, sizeof g);
+			rc = glob(pattern, 0, 0, &g);
+			if (rc == GLOB_NOSPACE || rc == GLOB_ABORTED) {
+				ATF_REQUIRE_MSG(rc == GLOB_NOMATCH,
+				    "an unexpected error occurred: "
+				    "rc=%d errno=%d", rc, errno);
+				/* NORETURN */
+			}
+
+			ATF_CHECK_MSG(rc == GLOB_NOMATCH,
+			    "A bogus match occurred: '%s' ~ '%s'", pattern,
+			    g.gl_pathv[0]);
+			globfree(&g);
+		}
+		clock_gettime(CLOCK_REALTIME, &t2);
+
+		t2.tv_sec -= t.tv_sec;
+		t2.tv_nsec -= t.tv_nsec;
+		dt = t2.tv_sec + (double)t2.tv_nsec/1e9;
+		dt /= mul;
+
+		ATF_CHECK_MSG(dt < 1, "glob(3) took far too long: %d %.9f", i,
+		    dt);
+
+		if (dt >= 0.0001)
+			mul = 1;
+	}
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+	ATF_TP_ADD_TC(tp, glob_pathological_test);
+
+	return (atf_no_error());
+}


More information about the svn-src-all mailing list