返回资讯中心
外部精选
软件工程
#开发者实时流

PHP2Shell 修复两周后,php-src 问题追踪器显示哪些问题

来源标题:The PHP2Shell fix, two weeks on: what the PHP-src issue tracker shows

PHP2Shell 修复于 10 月 21 日发布,两周后公开的 php-src 问题报告显示,修复工作远未结束,回归测试和崩溃积压仍需处理。修复需覆盖所有路径并保证已有功能正常,这比找漏洞更难。PHP Foundation 指出,请求恰好在错误时机触及 memory_limit 时,致命错误会从设置内部状态的 C 代码中跳出,留下半构建状态。

Hacker News 最新titusblair2026/11/4 · 今天17 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

PHP2Shell 的修复于 10 月 21 日发布。我们梳理了此后提交到 php-src 的 issue,在已打补丁和未打补丁的 PHP 上跑了一项回归测试,并统计了公开的崩溃积压数量。

IEEE Spectrum 关于 PHP2Shell 的报道今天登上了首页,标题是《AI 正迫使我们偿还 40 年的技术债》。文章大部分内容都在讲如何发现 bug。文中引用的一位研究者在中途提出了相反的观点:修复才是更难的那一半,因为你必须堵住每一条路径,同时保证所有已经能正常工作的东西继续正常工作。

这一点似乎值得核实。修复于 10 月 21 日发布,因此有两周的公开 bug 报告可以查看。我们把它们过了一遍。

Spectrum 的文章将根本原因描述为内存不足错误,“即分配的内存数量太小,无法容纳数据”。这个定义描述的是缓冲区溢出,那是另一种 bug。

PHP Foundation 的说明更为具体。如果一个请求恰好在错误的时刻触达 memory_limit,这个致命错误就会从正在设置内部状态的 C 代码中跳出来(PHP 通过 longjmp、zend_bailout() 实现这一点)。搭建到一半的状态被留在原地,后续代码却把它当作有效状态使用。两个 CVE,CVE-2026-109618 和 CVE-2026-109619,都源于这同一种模式。

Mattias Geniar 在发布当天撰写了机制说明,并在容器中对已打补丁和未打补丁的构建分别做了运行(ma.ttias.be/php-memory-hard-limit)。细节请阅读那篇文章。简要版本如下,我们也对照 PHP-8.5 分支上的 Zend/zend_alloc.c 做了核实:

该提交为 31c0ea7d,“Avoid state corruption via bailout”,新增 11,556 行、删除 1,017 行。它随 8.5.12、8.4.27、8.3.36 和 8.2.35 发布。我们注意到一个小问题:在 PHP-8.5 分支上,这项新设置没有出现在 UPGRADING、php.ini-production 或 php.ini-development 中。

在 GitHub 上搜索该修复提交的哈希(8.5 上为 31c0ea7d,8.2 上为 3584a98bda),可以找到发布以来提交的八个 issue。其中四个是行为回归,按提交顺序列在下面。另外四个是:一个在编译失败时被丢弃的待处理内存不足错误(GH-24631)、一个自 10 月 22 日起每晚在 x32 上发生段错误的栈限制测试(GH-24799)、一份较早的 FPM 设置泄漏报告(有评论者将其与该修复联系起来,GH-24689),以及一个被关闭为早期报告重复项的 use-after-free(GH-24672)。

GH-24492(发布当天提交)。一些代码路径仍在使用不允许失败的分配器,而分配大小由调用方决定。这些路径过去会给出正常的致命错误。现在它们会越过硬限制,进程直接退出。报告者给出的最简单复现用例:

<?php sprintf("%400000000s", ""); echo "survived\n"; 我们用 php -n -d memory_limit=128M 在三个构建上运行了它。在我们手头的两个未打补丁版本 8.5.0 和 8.3.30 上,得到的是旧行为: Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 503316512 bytes) in repro.php on line 2 exit=255 在官方 php:8.5.12-cli Docker 镜像(10 月 28 日构建)上,同一脚本始终不会打印 “survived”: Hard memory limit of 268435456 bytes exhausted (tried to allocate 503320576 bytes) in /x/repro.php on line 2 exit=137 这与 bug 报告和源码一致。约 480 MiB 远远超过 256 MiB 的硬限制,因此进程退出时既没有致命错误,也没有执行关闭函数。报告还列出了 pack()、number_format()、bcadd()、bin2hex()、base64_encode()、urlencode() 和 var_export(),以及一个约 100 字节的精心构造的 phar:// manifest。我们尚未复现 phar 那个用例。如果它成立,一个小型上传文件就能让一个 worker 挂掉。 Geniar 发现,另一种大分配给他的是正常的致命错误,所以这取决于具体的调用点。有些调用点被改成了软失败。这些被漏掉了。 GH-24492 有四个修复 PR:#24548、#24596、#24618,以及针对 ext/xml 的 Expat 部分的 #24516。截至今晚,这四个都处于打开状态,没有一个被合并。NEWS 中日期为 11 月 19 日的 8.5.13 部分尚未列出 GH-24492。 GH-24493。用于上传进度的会话解码器现在会拒绝三个保留属性名,例如 __PHP_Incomplete_Class_Name。一旦拒绝,整个解码就会失败,于是 PHP 会删除该会话。报告者展示,session_encode() 自身就可能写出这样的会话,例如当 $_SESSION 保存的是对用户输入执行 json_decode() 的结果时。在 8.5.11 中,同样的会话可以存活。 GH-24787。如果在请求启动期间、在没有任何东西检查待处理错误的间隙中越过了限制,扩展的 RINIT 就可能失败。随后 worker 在发送任何响应之前调用 exit(1),上传的临时文件则留在 upload_tmp_dir 中。PHP 团队已将此标记为 Verified。 GH-24563。其作者称这只是一条信息性报告:master 上仍有少数分配失败路径使用旧的致命错误路线。三天后,同一位作者贴出了通过一个巨大的 serialize_precision 触达其中一条路径的方法。 Foundation 的文章称,每一个候选修复都伴随着取舍,并列举了兼容性破坏和新的拒绝服务风险。文章还直言,修复前长达数月的拖延是其自身的一次结构性失败。上述报告正落入前两类之中。 Spectrum 的文章暗示,无人认真对待的崩溃报告正是这类 bug 潜伏的地方。公开的 issue tracker 却不太符合这个说法。我们提取了 php-src 中所有正文包含 AddressSanitizer 输出的 issue,自 2022 年 1 月以来共 362 个: php-src 中的 sanitizer 崩溃报告大多在几天内被关闭。而 PHP2Shell 的根本原因从未出现在那个队列里。据 Foundation 所述,一位核心开发者在修复早先一次审计发现时发现了最初的迹象,2024 年有人从理论上提出了可能的利用方式。修复工作停滞又重启,持续了数月,直到 Calif 在 8 月 11 日报告了一个可用的远程代码执行。所有这些都发生在私有的安全流程中。 (原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →