PHP尝试使用preg_match和preg_replace分配127 TB的内存和内存泄漏

时间:2014-12-15 09:59:14

标签: php apache unicode memory-leaks preg-match

我认为我发现了一个问题,当unicode字符作为分隔符或有时在preg_matchpreg_replace的正则表达式中的任何位置时,似乎会在Apache / PHP中造成内存泄漏。有可能在更多preg_*方法中发生这种情况。

测试用例1

使用以下内容创建一个新的PHP文件test.php

<?php
    preg_match( '°test°i', 'test', $matches );

测试用例2

使用以下内容创建一个新的PHP文件test.php

<?php
    preg_match( '°', 'test', $matches );

用作分隔符的unicode字符°是度数符号。如果你愿意,可以尝试使用任何其他unicode字符来查看会发生什么。

结果

将文件上传到Apache 2.4.10 (Debian)PHP 5.6.0-1+b1的网络服务器后,从您喜爱的浏览器中运行该文件。期待看到空白页面或消息说&#34;无效响应&#34;或&#34;此页面无法加载&#34;。

这将导致Apache error.log中的以下两行(通常是/var/log/error.log):

[Mon Dec 15 10:31:09.941622 2014] [:error] [pid 6292] [client ###.###.###.###:64413] PHP Warning:  preg_match():  in /path/to/test.php on line 2
[Mon Dec 15 10:31:09.941796 2014] [:error] [pid 6292] [client ###.###.###.###:64413] PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 139979759686648 bytes) in Unknown on line 0

请注意,PHP尝试分配的字节数刚刚超过127太字节。

您需要立即重启Apache

在尝试上述脚本后运行PHP脚本将导致各种通知或致命错误,即使在甚至无法生成它们的代码中也会弹出。例如,自动加载扩展类似乎不再正常工作,可能会显示如下错误:

Class MyClass not found in file MyExtendingClass.php on Line 3

文件MyExtendingClass.php看起来像这样:

<?php
    class MyExtendingClass extends MyClass
    {
    }

正如您所看到的,MyClass显然位于第2行,即使它确实存在且自动装带器已正确设置,PHP也无法再找到它。

显然,不要在正则表达式中使用unicode字符。但是为什么PHP在使用某些unicode字符时会泄漏内存?这种行为有解释吗?我想知道为什么PHP认为它应该分配如此大量的字节。

系统信息

Apache / 2.4.10(Debian)PHP / 5.6.0-1 + b1配置OpenSSL / 1.0.1i

1 个答案:

答案 0 :(得分:1)

我遇到了类似的错误,PHP尝试加载到RAM大于127 TB的内容,但对我来说,它会在脚本完成后发生。

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 140683487706824 bytes) in Unknown on line 0

我正在使用PHP版本5.4.39-0 + deb7u2,我发现它在脚本执行后发生,因为脚本本身工作正常并且没有显示任何问题。我实际上尝试记录脚本执行时间和RAM使用情况,这一切都很好,直到脚本结束。

我的问题很可能与使用dblib + freetds组合连接到Debian的远程MSSQL服务器有关,这是known bug #64511。它实际上已报告固定,但就我而言似乎并非如此。

实际上很难在这种行为中得到任何规则,但这就是我所看到的。

  • 我连接到远程MSSQL
  • 执行查询(实际上包含对存储过程的调用,然后在其后执行SELECT查询)

执行后,如果我使用类似这样的代码来获取结果:

$sprocResultSet = $PDOquery->fetchAll(PDO::FETCH_ASSOC);
$PDOquery->nextRowset();
$sprocExitCode = $PDOquery->fetchAll(PDO::FETCH_ASSOC);

大多数的情况下,我会得到奇怪的RAM分配错误,假设已经加载了太字节数。

当我没有在fetchAll()中指定fetch样式时,我使用了setFetchMode(),就像这里:

$PDOquery->setFetchMode(PDO::FETCH_ASSOC);
$sprocResultSet = $PDOquery->fetchAll();
$PDOquery->nextRowset();
$sprocExitCode = $PDOquery->fetchAll();

然后我从来没有注意到分配RAM的错误。

我确实尝试关闭游标并清空$ PDOquery变量,或者只是让它在脚本端自动关闭 - 没有帮助。另外,可能很重要的是 - 在它只返回一行数据后,sproc和附加的SELECT查询,所以肯定没有大的结果集回来。

所以...我不确定这在所有情况下是否有帮助,但是如果你和我一样有相似的情况,请尝试提前为PDO设置默认提取模式。

  

只需添加两件事。首先,在上面的代码中,使用了PDO的nextResultSet(),它也被报告为一个错误 - 导致内存问题:nextRowset causes memory corruption。   其次,不使用PDO的fetch(),因为无论何时使用它都会因报告DBLIB错误而死亡。