Thanks for the bug report and test case. I installed the attached patch, which
fixed the bug for me, and which adds a similar test case to the test database.
>From 675dccfdaa14ff5513b5b652da8d9286e65b69e2 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Tue, 10 Feb 2015 23:44:36 -0800
Subject: [PATCH] Grow the JIT stack if it becomes exhausted
Problem reported by Oliver Freyermuth in: http://bugs.gnu.org/19833
* NEWS: Document the fix.
* tests/Makefile.am (TESTS): Add pcre-jitstack.
* tests/pcre-jitstack: New file.
* src/pcresearch.c (NSUB): Move decl earlier, since it's needed
earlier now.
(jit_stack_size) [PCRE_STUDY_JIT_COMPILE]: New static var.
(jit_exec): New function.
(Pcompile): Initialize jit_stack_size.
(Pexecute): Use new jit_exec function. Report a useful diagnostic
if the error is PCRE_ERROR_JIT_STACKLIMIT.
---
NEWS | 3 +++
src/pcresearch.c | 73 +++++++++++++++++++++++++++++++++++++++--------------
tests/Makefile.am | 1 +
tests/pcre-jitstack | 39 ++++++++++++++++++++++++++++
4 files changed, 97 insertions(+), 19 deletions(-)
create mode 100755 tests/pcre-jitstack
diff --git a/NEWS b/NEWS
index da8bc78..071caab 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,9 @@ GNU grep NEWS -*- outline -*-
grep no longer reads from uninitialized memory or from beyond the end
of the heap-allocated input buffer. This fix addressed CVE-2015-1345.
+ When the JIT stack is exhausted, grep -P now grows the stack rather
+ than reporting an internal PCRE error.
+
* Noteworthy changes in release 2.21 (2014-11-23) [stable]
diff --git a/src/pcresearch.c b/src/pcresearch.c
index 0bd021a..afe4f2b 100644
--- a/src/pcresearch.c
+++ b/src/pcresearch.c
@@ -27,6 +27,11 @@
#endif
#if HAVE_LIBPCRE
+
+/* This must be at least 2; everything after that is for performance
+ in pcre_exec. */
+enum { NSUB = 300 };
+
/* Compiled internal form of a Perl regular expression. */
static pcre *cre;
@@ -36,6 +41,45 @@ static pcre_extra *extra;
# ifndef PCRE_STUDY_JIT_COMPILE
# define PCRE_STUDY_JIT_COMPILE 0
# endif
+
+# if PCRE_STUDY_JIT_COMPILE
+/* Maximum size of the JIT stack. */
+static int jit_stack_size;
+# endif
+
+/* Match the already-compiled PCRE pattern against the data in P, of
+ size SEARCH_BYTES, with options OPTIONS, and storing resulting
+ matches into SUB. Return the (nonnegative) match location or a
+ (negative) error number. */
+static int
+jit_exec (char const *p, int search_bytes, int options, int *sub)
+{
+ while (true)
+ {
+ int e = pcre_exec (cre, extra, p, search_bytes, 0, options, sub, NSUB);
+
+# if PCRE_STUDY_JIT_COMPILE
+ if (e == PCRE_ERROR_JIT_STACKLIMIT
+ && 0 < jit_stack_size && jit_stack_size <= INT_MAX / 2)
+ {
+ int old_size = jit_stack_size;
+ int new_size = jit_stack_size = old_size * 2;
+ static pcre_jit_stack *jit_stack;
+ if (jit_stack)
+ pcre_jit_stack_free (jit_stack);
+ jit_stack = pcre_jit_stack_alloc (old_size, new_size);
+ if (!jit_stack)
+ error (EXIT_TROUBLE, 0,
+ _("failed to allocate memory for the PCRE JIT stack"));
+ pcre_assign_jit_stack (extra, NULL, jit_stack);
+ continue;
+ }
+# endif
+
+ return e;
+ }
+}
+
#endif
#if HAVE_LIBPCRE
@@ -44,10 +88,6 @@ static pcre_extra *extra;
static int empty_match[2];
#endif
-/* This must be at least 2; everything after that is for performance
- in pcre_exec. */
-enum { NSUB = 300 };
-
void
Pcompile (char const *pattern, size_t size)
{
@@ -121,19 +161,11 @@ Pcompile (char const *pattern, size_t size)
if (pcre_fullinfo (cre, extra, PCRE_INFO_JIT, &e))
error (EXIT_TROUBLE, 0, _("internal error (should never happen)"));
+ /* The PCRE documentation says that a 32 KiB stack is the default. */
if (e)
- {
- /* A 32K stack is allocated for the machine code by default, which
- can grow to 512K if necessary. Since JIT uses far less memory
- than the interpreter, this should be enough in practice. */
- pcre_jit_stack *jit_stack = pcre_jit_stack_alloc (32 * 1024, 512 * 1024);
- if (!jit_stack)
- error (EXIT_TROUBLE, 0,
- _("failed to allocate memory for the PCRE JIT stack"));
- pcre_assign_jit_stack (extra, NULL, jit_stack);
- }
-
+ jit_stack_size = 32 << 10;
# endif
+
free (re);
int sub[NSUB];
@@ -214,8 +246,7 @@ Pexecute (char const *buf, size_t size, size_t *match_size,
if (multiline)
options |= PCRE_NO_UTF8_CHECK;
- e = pcre_exec (cre, extra, p, search_bytes, 0,
- options, sub, NSUB);
+ e = jit_exec (p, search_bytes, options, sub);
if (e != PCRE_ERROR_BADUTF8)
{
if (0 < e && multiline && sub[1] - sub[0] != 0)
@@ -268,9 +299,13 @@ Pexecute (char const *buf, size_t size, size_t *match_size,
case PCRE_ERROR_NOMEMORY:
error (EXIT_TROUBLE, 0, _("memory exhausted"));
+# if PCRE_STUDY_JIT_COMPILE
+ case PCRE_ERROR_JIT_STACKLIMIT:
+ error (EXIT_TROUBLE, 0, _("PCRE JIT stack exhausted"));
+# endif
+
case PCRE_ERROR_MATCHLIMIT:
- error (EXIT_TROUBLE, 0,
- _("exceeded PCRE's backtracking limit"));
+ error (EXIT_TROUBLE, 0, _("exceeded PCRE's backtracking limit"));
default:
/* For now, we lump all remaining PCRE failures into this basket.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0508cd2..8fcf8f6 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -95,6 +95,7 @@ TESTS = \
pcre-abort \
pcre-infloop \
pcre-invalid-utf8-input \
+ pcre-jitstack \
pcre-o \
pcre-utf8 \
pcre-w \
diff --git a/tests/pcre-jitstack b/tests/pcre-jitstack
new file mode 100755
index 0000000..03587f1
--- /dev/null
+++ b/tests/pcre-jitstack
@@ -0,0 +1,39 @@
+#! /bin/sh
+# Grep 2.21 would report "grep: internal PCRE error: -27"
+#
+# Copyright 2015 Free Software Foundation, Inc.
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+require_pcre_
+
+nl_base64=$(echo | (base64) 2>/dev/null) && test "X$nl_base64" = XCg== ||
+ skip_ "your system lacks the base64 program"
+foo=$( (echo foo | gzip | gzip -d) 2>/dev/null) && test "X$foo" = Xfoo ||
+ skip_ "your system lacks the gzip program"
+
+fail=0
+
+base64 -d >pcrejit.txt.gz <<'EOF'
+H4sIAAAAAAACA+2bUU4DMQxE/7mMz5T7XwKE+IBKVLue58yk0B9EtX6xJxN7t4VaH69a6+tHrW+/
+r4e3n75KARWShSOFTtiumE3FPVyo79ATIJ0Ry0No/yXe99UIUqTGKKUzYHFJHJoaCONQDCnDSCDS
+IPAvGCVeXNsZ7lpbWFfdaZtgPos5LeK2C1TBKzD09V3HFlCOsbFT/hNbz4HzJaRjnjdam9FXw/o6
+VyPozhMmiaRYAMeNSJR1iMjBEFLMtsH7lptartfxkzPQgFVofwRlxKsMYn2KNDnU9fsOQCkRIYVT
+G80ZRqBpSQjRYPX7s9gvtqknyNE2f8V09sxHM7YPmMMJgrmVna2AT717n5fUAIDkiBCqFgWUUgKD
+8jOc0Rgj5JS6vZnQI14wkaTDAkD266p/iVHs8gjCrMFARVM0iEVgFAa9YRAQT4tkgsmloTJLmyCm
+uSHRnTkzIdZMmZ5kYX/iJFtTwu9cFvr3aDWcUx4pUW/cVQwPoQSlwguNd4M0vTpAauKodmLFXv1P
+dkcKkYUglER2Q4L4gnmOiNGzSBATwGQgwihs5/QffIhyfg4hJvM2r4Rp6L+1ibCCd4jYZ6jCiBlc
+2+y4fl4yTGIwcWXNAUEeXmu8iCMV96DNTnmRNICDk2N5qaXGbsF91OX/0hlcYTjrMfy02p9Xv70D
+mv3RZCFOAAA=
+EOF
+test $? -eq 0 || framework_failure_
+
+gzip -d pcrejit.txt || framework_failure_
+
+LC_ALL=C grep -P -n '^([/](?!/)|[^/])*~/.*' pcrejit.txt
+test $? -eq 1 || fail=1
+
+Exit $fail
--
2.1.0