Bug #77193 [Fbk->Opn]: Infinite loop in preg_replace_callback

From: Date: Thu, 29 Nov 2018 09:04:48 +0000
Subject: Bug #77193 [Fbk->Opn]: Infinite loop in preg_replace_callback
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-218203@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=77193&edit=1

 ID:                 77193
 User updated by:    mlocati at gmail dot com
 Reported by:        mlocati at gmail dot com
 Summary:            Infinite loop in preg_replace_callback
-Status:             Feedback
+Status:             Open
 Type:               Bug
 Package:            PCRE related
 PHP Version:        7.3.0RC6
 Block user comment: N
 Private report:     N

 New Comment:

@ab Yes! That patch fixed the issue:


C:\MyPath>composer test -- --filter=ContentPageTranslateTest::testFrom
> phpunit "--filter=ContentPageTranslateTest::testFrom"
PHPUnit 4.8.36 by Sebastian Bergmann and contributors.

Runtime:        PHP 7.3.0-dev
Configuration:  C:\MyPath\phpunit.xml

.

Time: 4.68 seconds, Memory: 30.00MB

OK (1 test, 1 assertion)


Great job, Anatol!


Previous Comments:
------------------------------------------------------------------------
[2018-11-28 20:32:20] ab@php.net

@mlocati thanks, with the docker way reproduced it as well.

I still can't repro it on a reduced snippet. I guess, the method call inside the callback
somehow resets the match context (so somewhere in the call trace is a PCRE API call), thus
preventing loop termination. Could you please check, whether this fixes it?

diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c
index 5165209b85..f8fc721d81 100644
--- a/ext/pcre/php_pcre.c
+++ b/ext/pcre/php_pcre.c
@@ -1880,6 +1880,9 @@ matched:

                        new_len = result_len + offsets[0] - start_offset; /* part before the match
*/

+                       /* Advance to the next piece. */
+                       start_offset = offsets[1];
+
                        /* Use custom function to get replacement string and its length. */
                        eval_result = preg_do_repl_func(fci, fcc, subject, offsets, subpat_names,
count,
                                pcre2_get_mark(match_data));
@@ -1908,9 +1911,6 @@ matched:

                        limit--;

-                       /* Advance to the next piece. */
-                       start_offset = offsets[1];
-
                        /* If we have matched an empty string, mimic what Perl's /g options
does.
                           This turns out to be rather cunning. First we set PCRE2_NOTEMPTY_ATSTART
and try
                           the match again at the same point. If this fails (picked up above) we

------------------------------------------------------------------------
[2018-11-27 15:56:27] mlocati at gmail dot com

> This doesn't look like a PCRE issue. URL::to() seems to block the callback,
> so then it never returns. The actual issue should be somewhere later in
> the call stack starting from URL::to(), something like an endless loop or
> unterminated recursive call.

I modified the code a bit to show what's happening.

With this code:

ob_end_clean(); // Required because PHPUnit enables output buffering
echo 'Method: ', __METHOD__, "\n";
echo 'Text: ', $text, "\n";
$text = preg_replace_callback(
    '/{CCM:CID_([0-9]+)}/i',
    function ($matches) {
        print_r($matches);
        $cID = $matches[1];
        if ($cID > 0) {
            $c = Page::getByID($cID, 'ACTIVE');
            if ($c->isActive()) {
                return (string) \URL::to($c);
            }
        }
    },
    $text
);


The output is the following:

Method: Concrete\Core\Editor\LinkAbstractor::translateFrom
Text: <a href="{CCM:CID_3}">Super Cool!</a>
Array
(
    [0] => {CCM:CID_3}
    [1] => 3
)
Array
(
    [0] => {CCM:CID_3}
    [1] => 3
)
Array
(
    [0] => {CCM:CID_3}
    [1] => 3
)
[...endless list of Array()...]

As you can see, the \URL::to() call is not blocking the execution, but the callback is called
forever even if we have just one match.

------------------------------------------------------------------------
[2018-11-27 15:05:34] ab@php.net

This doesn't look like a PCRE issue. URL::to() seems to block the callback, so then it never
returns. The actual issue should be somewhere later in the call stack starting from URL::to(),
something like an endless loop or unterminated recursive call.

I'm going to check the suggested repro way with the docker container next days otherwise.

Thanks.

------------------------------------------------------------------------
[2018-11-25 19:21:55] mlocati at gmail dot com

I tried both with
pcre.jit=0
and with
pcre.jit=1

in php.ini.

The infinite loop also occurs when I remove the "return" instruction from "return
(string) \URL::to($c);", and always returning an empty string, that is:

$text = preg_replace_callback(
    '/{CCM:CID_([0-9]+)}/i',
    function ($matches) {
        $cID = $matches[1];
        if ($cID > 0) {
            $c = Page::getByID($cID, 'ACTIVE');
            if ($c->isActive()) {
                (string) \URL::to($c);
            }
        }
        return '';
    },
    $text
);

------------------------------------------------------------------------
[2018-11-25 12:37:21] cmb@php.net

Does this happen regardless of the value of pcre.jit?  Also, does
this happen if you actually return an empty string from the
callback instead of the implicit NULL?

------------------------------------------------------------------------


The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at

    https://bugs.php.net/bug.php?id=77193


--
Edit this bug report at https://bugs.php.net/bug.php?id=77193&edit=1


Thread (22 messages)

« previous php.bugs (#218203) next »