【问题标题】:PHP's preg-match_all causing Apache SegfaultPHP preg_match_all 导致 Apache Segfault
【发布时间】:2010-12-25 07:59:24
【问题描述】:

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

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

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

有问题的代码:

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:在php.ini 中更改pcre.backtrack_limitpcre.recursion_limit 的值在一定程度上缓解了这个问题。 Apache 不再出现段错误,但我的正则表达式不再匹配字符串中的所有匹配项。显然这是 PHP/PCRE 中一个众所周知的(从 2007 年开始)错误:
http://bugs.php.net/bug.php?id=40909

EDIT4:我在下面的答案中发布了我用来替换此特定正则表达式的代码,因为我的目的不可接受变通办法(待售产品,不能保证 php.ini 更改并且正则表达式仅部分更改工作删除了我们需要的功能)。我发布的代码已发布到公共领域,没有任何形式的保证或支持。我希望它可以帮助别人。 :)

感谢大家的帮助!

亚当

【问题讨论】:

    标签: php apache segmentation-fault


    【解决方案1】:

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

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

    [Pcre] ;PCRE 库回溯限制。 ;pcre.backtrack_limit=100000 pcre.recursion_limit=200000000 pcre.backtrack_limit=100000000

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

    我的网站即将报废,因此我真的没有太多需要(或预算)来寻找真正的解决方案。但也许这可以缓解您看到的问题。

    【讨论】:

    • 提高这些值并不能缓解问题,但降低它们确实可以。不幸的是,正则表达式不再匹配长字段(page_content)。不过,停止段错误对我来说肯定是一个很好的临时工作,谢谢:) 进一步搜索发现这似乎是 PHP/PCRE 中一个长期已知的错误:bugs.php.net/bug.php?id=40909
    【解决方案2】:

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

    提交的大小如何?如果你传递相同长度的乱码,会发生什么?

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

    $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]);
    }
    

    【讨论】:

    • 另外,preg 函数现在不是被弃用了吗?
    • 好电话。一串相同长度的“X”会导致相同的错误。我玩了一下,发现正好 6035 个字符的查询长度可以正常工作。 6036 段错误。
    • 好的。现在您可以用一些肯定不匹配的标记来拆分它,从拆分的字符串中获取匹配项并将它们合并。
    • SQL 中实际上没有任何换行符,在我看来,解决方案仍然会遇到单个字段超过长度(这是这里的问题)或包含新行的问题。不过谢谢你:)
    【解决方案3】:

    考虑到这只需要在保存页面或执行其他不经常执行的操作时匹配查询,我觉得以下代码的性能损失是可以接受的。它解析 SQL 查询 ($sql) 并将 name=>value 对放入 $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;
    

    感谢大家的帮助和指导!

    【讨论】:

      猜你喜欢
      • 2019-08-25
      • 1970-01-01
      • 2016-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-29
      • 1970-01-01
      • 2021-12-31
      相关资源
      最近更新 更多