条件跳转或移动依赖于未初始化的值(出现在php_pcre_match_impl中)

编程语言 2026-07-12

我在PHP-C扩展中创建一个简单函数,我使用的是从源码编译的PHP 8.3,开启opcache和 pcre.jit。我把ZEND_DONT_UNLOAD_MODULES设置为1,USE_ZEND_ALLOC设置为0,以便Valgrind使用。
在我的扩展中,我直接调用php_pcre_match_impl

#include <php.h>
#include <zend_interfaces.h>
#include <ext/session/php_session.h>
#include <ext/pcre/php_pcre.h>

#include "swat.h"

PHP_FUNCTION(run) {
    zval matched, matches;

    ZEND_PARSE_PARAMETERS_NONE();

    ZVAL_UNDEF(&matched);
    array_init(&matches);

    zend_string *pattern = zend_string_init(ZEND_STRL("/\\/page-[0-9]+/"), 0);
    zend_string *subject = zend_string_init(ZEND_STRL("backend/product"), 0); // Valgrind stack trace error

    pcre_cache_entry *pce = pcre_get_compiled_regex_cache(pattern);

    if (!pce) {
        zend_string_release(pattern);
        zend_string_release(subject);
        RETURN_FALSE;
    }

    php_pcre_match_impl(pce, subject, &matched, &matches, 1, 0, 0, 0);

    zend_string_release(pattern);
    zend_string_release(subject);

    zval_ptr_dtor(&matched);
    zval_ptr_dtor(&matches);

    RETURN_TRUE;
}

在使用Valgrind运行PHP时:

valgrind --leak-check=full php -S 0.0.0.0:8686 index.php

我看到如下错误:

==11== Invalid read of size 16
==11==    at 0x8B43C56: ???
==11==    by 0x8850AF7: ???
==11==    by 0x8850AF7: ???
==11==    by 0x8850B06: ???
==11==    by 0x84BF75F: ???
==11==    by 0x8850AF7: ???
==11==  Address 0x8850aff is 31 bytes inside a block of size 40 alloc'd
==11==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==11==    by 0x89B4E5: __zend_malloc (zend_alloc.c:3142)
==11==    by 0x899FBA: _malloc_custom (zend_alloc.c:2494)
==11==    by 0x89A104: _emalloc (zend_alloc.c:2613)
==11==    by 0x87003FB: zend_string_alloc (zend_string.h:174)
==11==    by 0x870046E: zend_string_init (zend_string.h:196)
==11==    by 0x87005CB: zif_run (swat.c:20)
==11==    by 0x923C92: ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER (zend_vm_execute.h:1275)
==11==    by 0x9A257B: execute_ex (zend_vm_execute.h:57273)
==11==    by 0x9A7E45: zend_execute (zend_vm_execute.h:61665)
==11==    by 0x8DE991: zend_execute_scripts (zend.c:1895)
==11==    by 0xA7551F: php_cli_server_dispatch_router (php_cli_server.c:2286)
==11==    by 0xA756FF: php_cli_server_dispatch (php_cli_server.c:2326)
==11==    by 0xA762DD: php_cli_server_recv_event_read_request (php_cli_server.c:2667)
==11==    by 0xA766D9: php_cli_server_do_event_for_each_fd_callback (php_cli_server.c:2754)
==11==    by 0xA71C73: php_cli_server_poller_iter_on_active (php_cli_server.c:932)
==11==    by 0xA76773: php_cli_server_do_event_for_each_fd (php_cli_server.c:2774)
==11==    by 0xA7681F: php_cli_server_do_event_loop (php_cli_server.c:2786)
==11==    by 0xA76C84: do_cli_server (php_cli_server.c:2918)
==11==    by 0xA6C1D9: main (php_cli.c:1344)
==11== 
==11== Conditional jump or move depends on uninitialised value(s)
==11==    at 0x8B43CFA: ???
==11==    by 0x8850AF7: ???
==11==    by 0x8850AF7: ???
==11==    by 0x8850B06: ???
==11==    by 0x84BF75F: ???
==11==    by 0x8850AF7: ???
==11==

预期:
该函数应该在没有内存错误的情况下运行。

实际:
Valgrind报告了无效读取和未初始化值的使用。

我怀疑问题可能与我如何使用php_pcre_match_impl或 zvals的初始化有关,但不确定到底哪里出了问题。

直接在扩展中调用php_pcre_match_impl是否安全,还是我遗漏了必要的初始化步骤?

解决方案

恐怕除了不使用JIT之外,几乎没有可做到的办法。在Valgrind的 Qt用户FAQ中也有提及那里使用的是pcre2进行正则表达式匹配 https://valgrind.org/docs/manual/faq.html#faq.pcre2_qt

问题在于pcre2的 JIT编译代码出于优化目的故意使用未定义行为(UB)。你可以从错误信息 Address 0x8850aff is 31 bytes inside a block of size 40 alloc'd 看出。这意味着有40字节的分配,但pcre2的 JIT编码从字节31到字节47进行了16字节的读取。那包含了超出40字节分配结束的位置的字节40到 47。这就是未定义行为。更糟的是,这发生在JIT编译的代码中。这意味着pcre2已经把一些机器码装载到内存并正在执行它。Valgrind无法知道那来自哪里——没有DWARF调试信息,且该内存地址范围也不对应任何映射到内存中的ELF二进制文件。结果在调用栈中只能看到问号。

这种对UB的优化使用相当常见。C标准库在与SIMD指令配合时常会采用它。在这些情况下,Valgrind会用它自有的str/mem函数版本来替代,以避免产生错误。该优化利用了几乎所有编译器和libc实现都使用16字节默认分配粒度这一事实。这意味着在本例中有一次 malloc 调用请求40字节,而malloc很可能分配了48字节。这也并非ISO C或 ISO C++标准所规定。

曾尝试对总分配大小进行formalise的信息,https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0901r5.html。那份论文未被接受。

至于修复问题,我相信PHP有类似Qt的机制,你可以在php.ini中设置类似 export PCRE_JIT=0pcre.jit = 0 的选项。环境变量似乎更适合,因为它可以更容易地限定在你的Valgrind测试中。我不是PHP开发者,所以无法确认这种做法是否有效。

如果你能确认这是一个可行的解决方案,我会更新Valgrind的 FAQ。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章