From bc367f1880ae5ccc771d5780e35df4c42744a9c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Carlo=20Marcelo=20Arenas=20Bel=C3=B3n?= <carenas@gmail.com>
Date: Sun, 22 Sep 2024 01:49:03 -0700
Subject: [PATCH] pcre2_compile: avoid 1 byte buffer overread parsing VERBs
 (#487)

As reported recently by ef218fb (Guard against out-of-bounds memory
access when parsing LIMIT_HEAP et al (#463), 2024-09-07), a malformed
pattern could result in reading 1 byte past its end.

Fix a similar issue that affects all VERBs and add test cases to
ensure the original bug and all its siblings are no longer an issue.

While at it fix the wording of the related documentation.
---
 doc/pcre2syntax.3    |  4 ++--
 src/pcre2_compile.c  | 11 +++--------
 testdata/testinput2  |  8 ++++++++
 testdata/testoutput2 | 12 ++++++++++++
 4 files changed, 25 insertions(+), 10 deletions(-)

diff --git a/doc/pcre2syntax.3 b/doc/pcre2syntax.3
index 232125b82..db0bb6586 100644
--- a/doc/pcre2syntax.3
+++ b/doc/pcre2syntax.3
@@ -408,8 +408,8 @@ only one hyphen. Setting (but no unsetting) is allowed after (?^ for example
 example (?i:...).
 .P
 The following are recognized only at the very start of a pattern or after one
-of the newline or \eR options with similar syntax. More than one of them may
-appear. For the first three, d is a decimal number.
+of the newline or \eR sequences or options with similar syntax. More than one
+of them may appear. For the first three, d is a decimal number.
 .sp
   (*LIMIT_DEPTH=d) set the backtracking limit to d
   (*LIMIT_HEAP=d)  set the heap size limit to d * 1024 bytes
diff --git a/src/pcre2_compile.c b/src/pcre2_compile.c
index 7e48b26..3d9a500 100644
--- a/src/pcre2_compile.c
+++ b/src/pcre2_compile.c
@@ -9877,13 +9877,14 @@ if ((options & PCRE2_LITERAL) == 0)
     {
     for (i = 0; i < sizeof(pso_list)/sizeof(pso); i++)
       {
-      uint32_t c, pp;
       pso *p = pso_list + i;
 
       if (patlen - skipatstart - 2 >= p->length &&
           PRIV(strncmp_c8)(ptr + skipatstart + 2, (char *)(p->name),
             p->length) == 0)
         {
+        uint32_t c, pp;
+
         skipatstart += p->length + 2;
         switch(p->type)
           {
@@ -9910,18 +9911,12 @@ if ((options & PCRE2_LITERAL) == 0)
           case PSO_LIMH:
           c = 0;
           pp = skipatstart;
-          if (!IS_DIGIT(ptr[pp]))
-            {
-            errorcode = ERR60;
-            ptr += pp;
-            goto HAD_EARLY_ERROR;
-            }
           while (pp < patlen && IS_DIGIT(ptr[pp]))
             {
             if (c > UINT32_MAX / 10 - 1) break;   /* Integer overflow */
             c = c*10 + (ptr[pp++] - CHAR_0);
             }
-          if (pp >= patlen || ptr[pp] != CHAR_RIGHT_PARENTHESIS)
+          if (pp >= patlen || pp == skipatstart || ptr[pp] != CHAR_RIGHT_PARENTHESIS)
             {
             errorcode = ERR60;
             ptr += pp;
diff --git a/testdata/testinput2 b/testdata/testinput2
index a869c5bc2..542d14520 100644
--- a/testdata/testinput2
+++ b/testdata/testinput2
@@ -5261,6 +5261,14 @@ a)"xI
 
 /(*LIMIT_HEAP=0)xxx/I
 
+/(*LIMIT_HEAP=123/use_length
+
+/(*LIMIT_MATCH=/use_length
+
+/(*CRLF)(*LIMIT_DEPTH=/use_length
+
+/(*CRLF)(*LIMIT_RECURSION=1)(*BOGUS/use_length
+
 /\d{0,3}(*:abc)(?C1)xxx/callout_info
 
 # ----------------------------------------------------------------------
diff --git a/testdata/testoutput2 b/testdata/testoutput2
index bf7b7620e..b99d64781 100644
--- a/testdata/testoutput2
+++ b/testdata/testoutput2
@@ -16220,6 +16220,18 @@ First code unit = 'x'
 Last code unit = 'x'
 Subject length lower bound = 3
 
+/(*LIMIT_HEAP=123/use_length
+Failed: error 160 at offset 16: (*VERB) not recognized or malformed
+
+/(*LIMIT_MATCH=/use_length
+Failed: error 160 at offset 14: (*VERB) not recognized or malformed
+
+/(*CRLF)(*LIMIT_DEPTH=/use_length
+Failed: error 160 at offset 21: (*VERB) not recognized or malformed
+
+/(*CRLF)(*LIMIT_RECURSION=1)(*BOGUS/use_length
+Failed: error 160 at offset 34: (*VERB) not recognized or malformed
+
 /\d{0,3}(*:abc)(?C1)xxx/callout_info
 Callout 1  x
 
