|
|
776ede |
From: Zephyr Pellerin <zv@nxvr.org>
|
|
|
776ede |
Date: Mon, 20 Mar 2017 15:36:41 -0700
|
|
|
776ede |
Subject: [PATCH] Fix CVE-2016-10253
|
|
|
776ede |
|
|
|
776ede |
|
|
|
776ede |
diff --git a/erts/emulator/pcre/pcre_compile.c b/erts/emulator/pcre/pcre_compile.c
|
|
|
776ede |
index d48126a..15a81fa 100644
|
|
|
776ede |
--- a/erts/emulator/pcre/pcre_compile.c
|
|
|
776ede |
+++ b/erts/emulator/pcre/pcre_compile.c
|
|
|
776ede |
@@ -2335,34 +2335,36 @@ for (;;)
|
|
|
776ede |
}
|
|
|
776ede |
}
|
|
|
776ede |
|
|
|
776ede |
-
|
|
|
776ede |
-
|
|
|
776ede |
/*************************************************
|
|
|
776ede |
-* Scan compiled branch for non-emptiness *
|
|
|
776ede |
-*************************************************/
|
|
|
776ede |
+ * Scan compiled branch for non-emptiness *
|
|
|
776ede |
+ *************************************************/
|
|
|
776ede |
|
|
|
776ede |
/* This function scans through a branch of a compiled pattern to see whether it
|
|
|
776ede |
-can match the empty string or not. It is called from could_be_empty()
|
|
|
776ede |
-below and from compile_branch() when checking for an unlimited repeat of a
|
|
|
776ede |
-group that can match nothing. Note that first_significant_code() skips over
|
|
|
776ede |
-backward and negative forward assertions when its final argument is TRUE. If we
|
|
|
776ede |
-hit an unclosed bracket, we return "empty" - this means we've struck an inner
|
|
|
776ede |
-bracket whose current branch will already have been scanned.
|
|
|
776ede |
-
|
|
|
776ede |
-Arguments:
|
|
|
776ede |
- code points to start of search
|
|
|
776ede |
- endcode points to where to stop
|
|
|
776ede |
- utf TRUE if in UTF-8 / UTF-16 / UTF-32 mode
|
|
|
776ede |
- cd contains pointers to tables etc.
|
|
|
776ede |
-
|
|
|
776ede |
-Returns: TRUE if what is matched could be empty
|
|
|
776ede |
+ can match the empty string or not. It is called from could_be_empty()
|
|
|
776ede |
+ below and from compile_branch() when checking for an unlimited repeat of a
|
|
|
776ede |
+ group that can match nothing. Note that first_significant_code() skips over
|
|
|
776ede |
+ backward and negative forward assertions when its final argument is TRUE. If we
|
|
|
776ede |
+ hit an unclosed bracket, we return "empty" - this means we've struck an inner
|
|
|
776ede |
+ bracket whose current branch will already have been scanned.
|
|
|
776ede |
+
|
|
|
776ede |
+ Arguments:
|
|
|
776ede |
+ code points to start of search
|
|
|
776ede |
+ endcode points to where to stop
|
|
|
776ede |
+ utf TRUE if in UTF-8 / UTF-16 / UTF-32 mode
|
|
|
776ede |
+ cd contains pointers to tables etc.
|
|
|
776ede |
+ recurses chain of recurse_check to catch mutual recursion
|
|
|
776ede |
+
|
|
|
776ede |
+ Returns: TRUE if what is matched could be empty
|
|
|
776ede |
*/
|
|
|
776ede |
|
|
|
776ede |
+
|
|
|
776ede |
static BOOL
|
|
|
776ede |
could_be_empty_branch(const pcre_uchar *code, const pcre_uchar *endcode,
|
|
|
776ede |
- BOOL utf, compile_data *cd)
|
|
|
776ede |
+ BOOL utf, compile_data *cd, recurse_check *recurses)
|
|
|
776ede |
{
|
|
|
776ede |
register pcre_uchar c;
|
|
|
776ede |
+recurse_check this_recurse;
|
|
|
776ede |
+
|
|
|
776ede |
for (code = first_significant_code(code + PRIV(OP_lengths)[*code], TRUE);
|
|
|
776ede |
code < endcode;
|
|
|
776ede |
code = first_significant_code(code + PRIV(OP_lengths)[c], TRUE))
|
|
|
776ede |
@@ -2390,25 +2392,47 @@ for (code = first_significant_code(code + PRIV(OP_lengths)[*code], TRUE);
|
|
|
776ede |
|
|
|
776ede |
if (c == OP_RECURSE)
|
|
|
776ede |
{
|
|
|
776ede |
- const pcre_uchar *scode;
|
|
|
776ede |
+ const pcre_uchar *scode = cd->start_code + GET(code, 1);
|
|
|
776ede |
+ const pcre_uchar *endgroup = scode;
|
|
|
776ede |
BOOL empty_branch;
|
|
|
776ede |
|
|
|
776ede |
- /* Test for forward reference */
|
|
|
776ede |
+ /* Test for forward reference or uncompleted reference. This is disabled
|
|
|
776ede |
+ when called to scan a completed pattern by setting cd->start_workspace to
|
|
|
776ede |
+ NULL. */
|
|
|
776ede |
|
|
|
776ede |
- for (scode = cd->start_workspace; scode < cd->hwm; scode += LINK_SIZE)
|
|
|
776ede |
- if ((int)GET(scode, 0) == (int)(code + 1 - cd->start_code)) return TRUE;
|
|
|
776ede |
+ if (cd->start_workspace != NULL)
|
|
|
776ede |
+ {
|
|
|
776ede |
+ const pcre_uchar *tcode;
|
|
|
776ede |
+ for (tcode = cd->start_workspace; tcode < cd->hwm; tcode += LINK_SIZE)
|
|
|
776ede |
+ if ((int)GET(tcode, 0) == (int)(code + 1 - cd->start_code)) return TRUE;
|
|
|
776ede |
+ if (GET(scode, 1) == 0) return TRUE; /* Unclosed */
|
|
|
776ede |
+ }
|
|
|
776ede |
|
|
|
776ede |
- /* Not a forward reference, test for completed backward reference */
|
|
|
776ede |
+ /* If the reference is to a completed group, we need to detect whether this
|
|
|
776ede |
+ is a recursive call, as otherwise there will be an infinite loop. If it is
|
|
|
776ede |
+ a recursion, just skip over it. Simple recursions are easily detected. For
|
|
|
776ede |
+ mutual recursions we keep a chain on the stack. */
|
|
|
776ede |
|
|
|
776ede |
- empty_branch = FALSE;
|
|
|
776ede |
- scode = cd->start_code + GET(code, 1);
|
|
|
776ede |
- if (GET(scode, 1) == 0) return TRUE; /* Unclosed */
|
|
|
776ede |
+ do endgroup += GET(endgroup, 1); while (*endgroup == OP_ALT);
|
|
|
776ede |
+ if (code >= scode && code <= endgroup) continue; /* Simple recursion */
|
|
|
776ede |
+ else
|
|
|
776ede |
+ {
|
|
|
776ede |
+ recurse_check *r = recurses;
|
|
|
776ede |
+ for (r = recurses; r != NULL; r = r->prev)
|
|
|
776ede |
+ if (r->group == scode) break;
|
|
|
776ede |
+ if (r != NULL) continue; /* Mutual recursion */
|
|
|
776ede |
+ }
|
|
|
776ede |
+
|
|
|
776ede |
+ /* Completed reference; scan the referenced group, remembering it on the
|
|
|
776ede |
+ stack chain to detect mutual recursions. */
|
|
|
776ede |
|
|
|
776ede |
- /* Completed backwards reference */
|
|
|
776ede |
+ empty_branch = FALSE;
|
|
|
776ede |
+ this_recurse.prev = recurses;
|
|
|
776ede |
+ this_recurse.group = scode;
|
|
|
776ede |
|
|
|
776ede |
do
|
|
|
776ede |
{
|
|
|
776ede |
- if (could_be_empty_branch(scode, endcode, utf, cd))
|
|
|
776ede |
+ if (could_be_empty_branch(scode, endcode, utf, cd, &this_recurse))
|
|
|
776ede |
{
|
|
|
776ede |
empty_branch = TRUE;
|
|
|
776ede |
break;
|
|
|
776ede |
@@ -2448,7 +2472,7 @@ for (code = first_significant_code(code + PRIV(OP_lengths)[*code], TRUE);
|
|
|
776ede |
if (c == OP_BRA || c == OP_BRAPOS ||
|
|
|
776ede |
c == OP_CBRA || c == OP_CBRAPOS ||
|
|
|
776ede |
c == OP_ONCE || c == OP_ONCE_NC ||
|
|
|
776ede |
- c == OP_COND)
|
|
|
776ede |
+ c == OP_COND || c == OP_SCOND)
|
|
|
776ede |
{
|
|
|
776ede |
BOOL empty_branch;
|
|
|
776ede |
if (GET(code, 1) == 0) return TRUE; /* Hit unclosed bracket */
|
|
|
776ede |
@@ -2464,8 +2488,8 @@ for (code = first_significant_code(code + PRIV(OP_lengths)[*code], TRUE);
|
|
|
776ede |
empty_branch = FALSE;
|
|
|
776ede |
do
|
|
|
776ede |
{
|
|
|
776ede |
- if (!empty_branch && could_be_empty_branch(code, endcode, utf, cd))
|
|
|
776ede |
- empty_branch = TRUE;
|
|
|
776ede |
+ if (!empty_branch && could_be_empty_branch(code, endcode, utf, cd,
|
|
|
776ede |
+ recurses)) empty_branch = TRUE;
|
|
|
776ede |
code += GET(code, 1);
|
|
|
776ede |
}
|
|
|
776ede |
while (*code == OP_ALT);
|
|
|
776ede |
@@ -2522,34 +2546,57 @@ for (code = first_significant_code(code + PRIV(OP_lengths)[*code], TRUE);
|
|
|
776ede |
|
|
|
776ede |
/* Opcodes that must match a character */
|
|
|
776ede |
|
|
|
776ede |
+ case OP_ANY:
|
|
|
776ede |
+ case OP_ALLANY:
|
|
|
776ede |
+ case OP_ANYBYTE:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_PROP:
|
|
|
776ede |
case OP_NOTPROP:
|
|
|
776ede |
+ case OP_ANYNL:
|
|
|
776ede |
+
|
|
|
776ede |
+ case OP_NOT_HSPACE:
|
|
|
776ede |
+ case OP_HSPACE:
|
|
|
776ede |
+ case OP_NOT_VSPACE:
|
|
|
776ede |
+ case OP_VSPACE:
|
|
|
776ede |
case OP_EXTUNI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_NOT_DIGIT:
|
|
|
776ede |
case OP_DIGIT:
|
|
|
776ede |
case OP_NOT_WHITESPACE:
|
|
|
776ede |
case OP_WHITESPACE:
|
|
|
776ede |
case OP_NOT_WORDCHAR:
|
|
|
776ede |
case OP_WORDCHAR:
|
|
|
776ede |
- case OP_ANY:
|
|
|
776ede |
- case OP_ALLANY:
|
|
|
776ede |
- case OP_ANYBYTE:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_CHAR:
|
|
|
776ede |
case OP_CHARI:
|
|
|
776ede |
case OP_NOT:
|
|
|
776ede |
case OP_NOTI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_PLUS:
|
|
|
776ede |
+ case OP_PLUSI:
|
|
|
776ede |
case OP_MINPLUS:
|
|
|
776ede |
- case OP_POSPLUS:
|
|
|
776ede |
- case OP_EXACT:
|
|
|
776ede |
+ case OP_MINPLUSI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_NOTPLUS:
|
|
|
776ede |
+ case OP_NOTPLUSI:
|
|
|
776ede |
case OP_NOTMINPLUS:
|
|
|
776ede |
+ case OP_NOTMINPLUSI:
|
|
|
776ede |
+
|
|
|
776ede |
+ case OP_POSPLUS:
|
|
|
776ede |
+ case OP_POSPLUSI:
|
|
|
776ede |
case OP_NOTPOSPLUS:
|
|
|
776ede |
+ case OP_NOTPOSPLUSI:
|
|
|
776ede |
+
|
|
|
776ede |
+ case OP_EXACT:
|
|
|
776ede |
+ case OP_EXACTI:
|
|
|
776ede |
case OP_NOTEXACT:
|
|
|
776ede |
+ case OP_NOTEXACTI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_TYPEPLUS:
|
|
|
776ede |
case OP_TYPEMINPLUS:
|
|
|
776ede |
case OP_TYPEPOSPLUS:
|
|
|
776ede |
case OP_TYPEEXACT:
|
|
|
776ede |
+
|
|
|
776ede |
return FALSE;
|
|
|
776ede |
|
|
|
776ede |
/* These are going to continue, as they may be empty, but we have to
|
|
|
776ede |
@@ -2583,30 +2630,58 @@ for (code = first_significant_code(code + PRIV(OP_lengths)[*code], TRUE);
|
|
|
776ede |
return TRUE;
|
|
|
776ede |
|
|
|
776ede |
/* In UTF-8 mode, STAR, MINSTAR, POSSTAR, QUERY, MINQUERY, POSQUERY, UPTO,
|
|
|
776ede |
- MINUPTO, and POSUPTO may be followed by a multibyte character */
|
|
|
776ede |
+ MINUPTO, and POSUPTO and their caseless and negative versions may be
|
|
|
776ede |
+ followed by a multibyte character. */
|
|
|
776ede |
|
|
|
776ede |
#if defined SUPPORT_UTF && !defined COMPILE_PCRE32
|
|
|
776ede |
case OP_STAR:
|
|
|
776ede |
case OP_STARI:
|
|
|
776ede |
+ case OP_NOTSTAR:
|
|
|
776ede |
+ case OP_NOTSTARI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_MINSTAR:
|
|
|
776ede |
case OP_MINSTARI:
|
|
|
776ede |
+ case OP_NOTMINSTAR:
|
|
|
776ede |
+ case OP_NOTMINSTARI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_POSSTAR:
|
|
|
776ede |
case OP_POSSTARI:
|
|
|
776ede |
+ case OP_NOTPOSSTAR:
|
|
|
776ede |
+ case OP_NOTPOSSTARI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_QUERY:
|
|
|
776ede |
case OP_QUERYI:
|
|
|
776ede |
+ case OP_NOTQUERY:
|
|
|
776ede |
+ case OP_NOTQUERYI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_MINQUERY:
|
|
|
776ede |
case OP_MINQUERYI:
|
|
|
776ede |
+ case OP_NOTMINQUERY:
|
|
|
776ede |
+ case OP_NOTMINQUERYI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_POSQUERY:
|
|
|
776ede |
case OP_POSQUERYI:
|
|
|
776ede |
+ case OP_NOTPOSQUERY:
|
|
|
776ede |
+ case OP_NOTPOSQUERYI:
|
|
|
776ede |
+
|
|
|
776ede |
if (utf && HAS_EXTRALEN(code[1])) code += GET_EXTRALEN(code[1]);
|
|
|
776ede |
break;
|
|
|
776ede |
|
|
|
776ede |
case OP_UPTO:
|
|
|
776ede |
case OP_UPTOI:
|
|
|
776ede |
+ case OP_NOTUPTO:
|
|
|
776ede |
+ case OP_NOTUPTOI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_MINUPTO:
|
|
|
776ede |
case OP_MINUPTOI:
|
|
|
776ede |
+ case OP_NOTMINUPTO:
|
|
|
776ede |
+ case OP_NOTMINUPTOI:
|
|
|
776ede |
+
|
|
|
776ede |
case OP_POSUPTO:
|
|
|
776ede |
case OP_POSUPTOI:
|
|
|
776ede |
+ case OP_NOTPOSUPTO:
|
|
|
776ede |
+ case OP_NOTPOSUPTOI:
|
|
|
776ede |
+
|
|
|
776ede |
if (utf && HAS_EXTRALEN(code[1 + IMM2_SIZE])) code += GET_EXTRALEN(code[1 + IMM2_SIZE]);
|
|
|
776ede |
break;
|
|
|
776ede |
#endif
|
|
|
776ede |
@@ -2632,7 +2707,6 @@ return TRUE;
|
|
|
776ede |
}
|
|
|
776ede |
|
|
|
776ede |
|
|
|
776ede |
-
|
|
|
776ede |
/*************************************************
|
|
|
776ede |
* Scan compiled regex for non-emptiness *
|
|
|
776ede |
*************************************************/
|
|
|
776ede |
@@ -2660,7 +2734,7 @@ could_be_empty(const pcre_uchar *code, const pcre_uchar *endcode,
|
|
|
776ede |
{
|
|
|
776ede |
while (bcptr != NULL && bcptr->current_branch >= code)
|
|
|
776ede |
{
|
|
|
776ede |
- if (!could_be_empty_branch(bcptr->current_branch, endcode, utf, cd))
|
|
|
776ede |
+ if (!could_be_empty_branch(bcptr->current_branch, endcode, utf, cd, NULL))
|
|
|
776ede |
return FALSE;
|
|
|
776ede |
bcptr = bcptr->outer;
|
|
|
776ede |
}
|
|
|
776ede |
@@ -2668,7 +2742,6 @@ return TRUE;
|
|
|
776ede |
}
|
|
|
776ede |
|
|
|
776ede |
|
|
|
776ede |
-
|
|
|
776ede |
/*************************************************
|
|
|
776ede |
* Check for POSIX class syntax *
|
|
|
776ede |
*************************************************/
|
|
|
776ede |
@@ -5392,7 +5465,7 @@ for (;; ptr++)
|
|
|
776ede |
pcre_uchar *scode = bracode;
|
|
|
776ede |
do
|
|
|
776ede |
{
|
|
|
776ede |
- if (could_be_empty_branch(scode, ketcode, utf, cd))
|
|
|
776ede |
+ if (could_be_empty_branch(scode, ketcode, utf, cd, NULL))
|
|
|
776ede |
{
|
|
|
776ede |
*bracode += OP_SBRA - OP_BRA;
|
|
|
776ede |
break;
|
|
|
776ede |
diff --git a/erts/emulator/pcre/pcre_internal.h b/erts/emulator/pcre/pcre_internal.h
|
|
|
776ede |
index af436bd..eb0db89 100644
|
|
|
776ede |
--- a/erts/emulator/pcre/pcre_internal.h
|
|
|
776ede |
+++ b/erts/emulator/pcre/pcre_internal.h
|
|
|
776ede |
@@ -2455,6 +2455,13 @@ typedef struct branch_chain {
|
|
|
776ede |
pcre_uchar *current_branch;
|
|
|
776ede |
} branch_chain;
|
|
|
776ede |
|
|
|
776ede |
+/* Structure for mutual recursion detection. */
|
|
|
776ede |
+
|
|
|
776ede |
+typedef struct recurse_check {
|
|
|
776ede |
+ struct recurse_check *prev;
|
|
|
776ede |
+ const pcre_uchar *group;
|
|
|
776ede |
+} recurse_check;
|
|
|
776ede |
+
|
|
|
776ede |
/* Structure for items in a linked list that represents an explicit recursive
|
|
|
776ede |
call within the pattern; used by pcre_exec(). */
|
|
|
776ede |
|