PHP的preg-match_all导致Apache Segfault

时间:2009-12-10 16:34:38

标签: php apache segmentation-fault

我正在使用两个正则表达式从MySQL查询中提取分配并使用它们来创建审计跟踪。其中一个是“挑剔”的,需要引用的列名/等,另一个不需要。

它们都经过测试并正确解析出值。我遇到的问题是,对于某些查询,“挑剔”的正则表达式实际上只会导致Apache出现段错误。

我尝试了各种各样的事情来确定这是将regexp留在代码中的原因,只是修改条件以确保它没有运行(以排除某种编译时问题或其他问题) 。没有问题。只有当它针对特定查询运行正则表达式时才会出现段错误,而且我找不到任何明显的模式来告诉我原因。

有问题的代码:

if ($picky)
    preg_match_all("/[`'\"]((?:[A-Z]|[a-z]|_|[0-9])+)[`'\"] *= *'((?:[^'\\\\]|\\\\.)*)'/", $sql, $matches);
else
    preg_match_all("/[`'\"]?((?:[A-Z]|[a-z]|_|[0-9])+)[`'\"]? *= *[`'\"]?([^`'\" ,]+)[`'\"]?/", $sql, $matches);

两者之间的唯一区别是第一个删除了引号上的问号,使它们成为非可选项,并删除了对值使用不同类型引号的选项 - 仅允许使用单引号。用第二个替换第一个正则表达式(用于测试目的)并使用相同的数据可以解决问题 - 肯定与正则表达式有关。

导致我悲伤的特定SQL可在以下位置找到:
http://stackoverflow.pastebin.com/m75c2a2a0

有趣的是,当我删除突出显示的部分时,一切正常。尝试单独提交突出显示的部分不会导致错误。

我对这里发生的事情感到非常困惑。任何人都可以提供任何有关进一步调试或修复的建议吗?

编辑:没有什么非常令人兴奋的,但为了完整起见,这里是Apache的相关日志条目(/var/log/apache2/error.log - 该网站的error.log中没有任何内容。甚至没有提及请求在访问日志中。)

[Thu Dec 10 10:08:03 2009] [notice] child pid 20835 exit signal Segmentation fault (11)

包含该查询的每个请求中的一个。

EDIT2:根据Kuroki Kaze的建议,我尝试了相同长度的乱码并得到了同样的段错误。坐下来尝试了一堆不同的长度并找到了极限。 6035个字符工作正常。 6036段错误。

EDIT3:更改pcre.backtrack_limitpcre.recursion_limitphp.ini的值可以稍微缓解这个问题。 Apache不再是段错误,但我的正则表达式不再匹配字符串中的所有匹配项。显然这是PHP / PCRE中一个众所周知的(2007年)错误:
http://bugs.php.net/bug.php?id=40909

EDIT4:我在下面的答案中发布了我用来替换这个特定正则表达式的代码,因为我的目的无法接受解决方法(产品出售,不能保证php.ini更改和regexp只是部分工作删除我们要求的功能)。我发布的代码已发布到公共领域,不提供任何形式的担保或支持。我希望它可以帮助别人。 :)

谢谢大家的帮助!

亚当

3 个答案:

答案 0 :(得分:4)

  

有趣的是,当我删除突出显示的部分时,一切正常。尝试单独提交突出显示的部分不会导致错误。

提交的大小怎么样?如果你通过相同长度的胡言乱语,会发生什么?

编辑:拆分和合并将看起来像这样:

$strings = explode("\n", $sql);

$matches = array(array(), array(), array());

foreach ($strings AS $string) {
    preg_match_all("/[`'\"]?((?:[A-Z]|[a-z]|_|[0-9])+)[`'\"]? *= *[`'\"]?([^`'\" ,]+)[`'\"]?/", $string, $matches_temp);
    $matches[0] = array_merge($matches[0], $matches_temp[0]);
    $matches[1] = array_merge($matches[1], $matches_temp[1]);
    $matches[2] = array_merge($matches[2], $matches_temp[2]);
}

答案 1 :(得分:4)

我遇到了类似的preg_match相关问题,同样是Apache segfault。只有导致它的preg_match被内置到我正在使用的CMS中(WordPress)。

提供的“解决方法”是在php.ini中更改这些设置:

[PCRE] ; PCRE库回溯限制。 ; pcre.backtrack_limit = 100000 pcre.recursion_limit = 2亿 pcre.backtrack_limit =亿

权衡是为了渲染更大的页面(在我的情况下,> 200行;当其中一列被限制为1500个字符的文本描述时),你将获得相当高的CPU利用率,而我我仍然看到了段错误。只是不那么频繁。

我的网站已接近生命终结,因此我并不需要(或预算)来寻找真正的解决方案。但也许这可以缓解你所看到的问题。

答案 2 :(得分:1)

鉴于这只需要在保存页面或执行其他不经常执行的操作时匹配查询,我觉得以下代码的性能损失是可以接受的。它解析SQL查询($sql)并将name =>值对放入$data。似乎运作良好并处理大量查询。

            $quoted = '';
            $escaped = false;

            $key = '';
            $value = '';
            $target = 'key';

            for ($i=0; $i<strlen($sql); $i++)
            {
                if ($escaped)
                {
                    $$target .= $sql[$i];
                    $escaped = false;
                }
                else if ($quoted!='')
                {
                    if ($sql[$i]=='\\')
                        $escaped = true;
                    else if ($sql[$i]==$quoted)
                        $quoted = '';
                    else
                        $$target .= $sql[$i];
                }
                else
                {
                    if ($sql[$i]=='\'' || $sql[$i]=='`')
                    {
                        $quoted = $sql[$i];
                        $$target = '';
                    }
                    else if ($sql[$i]=='=')
                        $target = 'value';
                    else if ($sql[$i]==',')
                    {
                        $target = 'key';
                        $data[$key] = $value;
                        $key = '';
                        $value = '';
                    }
                }
            }

            if ($value!='')
                $data[$key] = $value;

感谢大家的帮助和指导!