fix [perl #112370] memEQ off-by-one in Perl_regexec_flags()
authorYves Orton <demerphq@gmail.com>
Sun, 15 Apr 2012 09:23:11 +0000 (11:23 +0200)
committerYves Orton <demerphq@gmail.com>
Sun, 15 Apr 2012 09:36:40 +0000 (11:36 +0200)
The problem was that when we had a floating non-unicode substr
we could read past the beginning of the string.

So for instance this code:

    foreach ("\x{2603}", 'a') {
        'b' =~ /(?:^|.)$_$/;
    }

When run under valgrind would show an illegal read.

If run under use re 'debug' we would see output like this:

    floating utf8 "a"$ at 0..1 (checking floating) minlen 1
    Matching REx "(?:^|.)a$" against "b"
    UTF-8 pattern...
    Can't trim the tail, match fails (should not happen)
    Match failed

Which suggests we are falling into a "should not happen" branch of the
code.

This patch fixes the logic to properly test string length issues, and
changes the debug output so it looks like this:

    floating utf8 "a"$ at 0..1 (checking floating) minlen 1
    Matching REx "(?:^|.)a$" against "b"
    UTF-8 pattern...
    String does not contain required trailing substring, cannot match.
    Match failed

Which makes more sense.

regexec.c

index 3a4779d..ea4810d 100644 (file)
--- a/regexec.c
+++ b/regexec.c
@@ -2448,15 +2448,52 @@ Perl_regexec_flags(pTHX_ REGEXP * const rx, char *stringarg, register char *stre
            else {
                STRLEN len;
                 const char * const little = SvPV_const(float_real, len);
-
                if (SvTAIL(float_real)) {
-                   if (memEQ(strend - len + 1, little, len - 1))
-                       last = strend - len + 1;
-                   else if (!multiline)
-                       last = memEQ(strend - len, little, len)
-                           ? strend - len : NULL;
-                   else
+                   /* This means that float_real contains an artificial \n on the end
+                    * due to the presence of something like this: /foo$/
+                    * where we can match both "foo" and "foo\n" at the end of the string.
+                    * So we have to compare the end of the string first against the float_real
+                    * without the \n and then against the full float_real with the string.
+                    * We have to watch out for cases where the string might be smaller
+                    * than the float_real or the float_real without the \n.
+                    */
+                   char *checkpos= strend - len;
+                   DEBUG_OPTIMISE_r(
+                       PerlIO_printf(Perl_debug_log,
+                           "%sChecking for float_real.%s\n",
+                           PL_colors[4], PL_colors[5]));
+                   if (checkpos + 1 < strbeg) {
+                       /* can't match, even if we remove the trailing \n string is too short to match */
+                       DEBUG_EXECUTE_r(
+                           PerlIO_printf(Perl_debug_log,
+                               "%sString shorter than required trailing substring, cannot match.%s\n",
+                               PL_colors[4], PL_colors[5]));
+                       goto phooey;
+                   } else if (memEQ(checkpos + 1, little, len - 1)) {
+                       /* can match, the end of the string matches without the "\n" */
+                       last = checkpos + 1;
+                   } else if (checkpos < strbeg) {
+                       /* cant match, string is too short when the "\n" is included */
+                       DEBUG_EXECUTE_r(
+                           PerlIO_printf(Perl_debug_log,
+                               "%sString does not contain required trailing substring, cannot match.%s\n",
+                               PL_colors[4], PL_colors[5]));
+                       goto phooey;
+                   } else if (!multiline) {
+                       /* non multiline match, so compare with the "\n" at the end of the string */
+                       if (memEQ(checkpos, little, len)) {
+                           last= checkpos;
+                       } else {
+                           DEBUG_EXECUTE_r(
+                               PerlIO_printf(Perl_debug_log,
+                                   "%sString does not contain required trailing substring, cannot match.%s\n",
+                                   PL_colors[4], PL_colors[5]));
+                           goto phooey;
+                       }
+                   } else {
+                       /* multiline match, so we have to search for a place where the full string is located */
                        goto find_last;
+                   }
                } else {
                  find_last:
                    if (len)