opcache: smm_shared_globals left pointing to a dead stack frame when there is no SHM backend

未关闭 适合新手
#25,195 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
72/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
c, php

调研方向

从 ext/opcache/zend_shared_alloc.c 中的 zend_shared_alloc_startup() 开始,重点关注 handler_table == NULL 时的提前返回,以及其他提前返回如何重置 smm_shared_globals。使用提供的 embed SAPI 程序和 AddressSanitizer 配置复现故障,或检查现有的启动路径。当无 SHM 路径不再留下指向局部栈变量的指针,且复现程序运行完成时没有报告 stack-use-after-return,即为完成。

由索引模型根据 Issue 内容生成。

描述

Description

When opcache has no shared memory backend (handler_table is empty), zend_shared_alloc_startup() returns NO_SHM_BACKEND with smm_shared_globals still pointing to its local tmp_shared_globals. The other early returns (FAILED_REATTACHED, ALLOC_FALLBACK) reset it to NULL; this one does not.

Nothing reads it while opcache is disabled. But if the engine is started again in the same process (embed SAPI: php_embed_init() / php_embed_shutdown()), with opcache.file_cache_only=1, zend_shared_alloc_startup() is not called. zend_accel_in_shm() then reads ZSMMG(shared_segments_count) and ZSMMG(shared_segments) from that dead frame, and zend_persist uses its answer to decide what is already persisted.

We hit it with PHP compiled to WebAssembly (emscripten), which has no SHM backend, as a memory corruption ("memory access out of bounds") when opcache later compiled some files to its file cache. Here is a native reproducer, with the SHM backends disabled at configure time:

php_cv_shm_ipc=no php_cv_shm_mmap_anon=no php_cv_shm_mmap_posix=no \
    ./configure --enable-embed --disable-all --disable-opcache-jit --enable-address-sanitizer
make && make install
gcc -fsanitize=address repro.c -o repro $(php-config --includes) -L/usr/local/lib -lphp
mkdir /tmp/opcache
USE_ZEND_ALLOC=0 ASAN_OPTIONS=detect_leaks=0:detect_stack_use_after_return=1 ./repro

repro.c:

#include <sapi/embed/php_embed.h>
#include <stdlib.h>

static void start(const char *phprc, const char *code)
{
    setenv("PHPRC", phprc, 1);
    php_embed_init(0, NULL);
    zend_first_try {
        zend_eval_string((char *) code, NULL, "repro");
    } zend_end_try();
    php_embed_shutdown();
}

int main(void)
{
    start("/repro/first", "var_dump(opcache_get_status(false));");
    start("/repro/second", "var_dump(opcache_compile_file('/repro/script.php'));");

    return 0;
}

/repro/first/php.ini:

opcache.enable=1
opcache.log_verbosity_level=4

/repro/second/php.ini:

opcache.enable=1
opcache.file_cache=/tmp/opcache
opcache.file_cache_only=1

/repro/script.php:

<?php

const PRIMES = [2, 3, 5, 7];

function primes(): array
{
    static $calls = [];
    $calls[] = true;

    return PRIMES;
}

Resulted in this output:

Message Opcode Caching is disabled: No available SHM backend. Set opcache.enable=0 to hide this message.
bool(false)
==1==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f1327702a38 at pc 0x7f1329e77995 bp 0x7ffe0f741820 sp 0x7ffe0f741818
READ of size 4 at 0x7f1327702a38 thread T0
    #0 0x7f1329e77994 in zend_accel_in_shm /php-src/ext/opcache/zend_shared_alloc.c:701
    #1 0x7f1329e598f4 in zend_persist_op_array_calc_ex /php-src/ext/opcache/zend_persist_calc.c:244
    #2 0x7f1329e61c73 in zend_accel_script_persist_calc /php-src/ext/opcache/zend_persist_calc.c:665
    #3 0x7f1329e797c2 in store_script_in_file_cache /php-src/ext/opcache/ZendAccelerator.c:1520
    #4 0x7f1329e8da01 in cache_script_in_file_cache /php-src/ext/opcache/ZendAccelerator.c:1571
    #5 0x7f1329e8da01 in file_cache_compile_file /php-src/ext/opcache/ZendAccelerator.c:1962
    #6 0x7f1329e9396d in persistent_compile_file /php-src/ext/opcache/ZendAccelerator.c:2017
    #7 0x7f1329e3e564 in zif_opcache_compile_file /php-src/ext/opcache/zend_accelerator_module.c:1013

Address 0x7f1327702a38 is located in stack of thread T0 at offset 56 in frame
    #0 0x7f1329e75c2f in zend_shared_alloc_startup /php-src/ext/opcache/zend_shared_alloc.c:185

  This frame has 1 object(s):
    [48, 128) 'tmp_shared_globals' (line 188) <== Memory access at offset 56 is inside this variable

But I expected this output instead:

Message Opcode Caching is disabled: No available SHM backend. Set opcache.enable=0 to hide this message.
bool(false)
bool(true)

Resetting it before that return gives the expected output, with no ASan report:

 		if (handler_table->name == NULL) {
+			smm_shared_globals = NULL;
 			return NO_SHM_BACKEND;
 		}

I can open a PR with this fix.

PHP Version
PHP 8.5.12, and master (d15160d5)
Operating System

Debian trixie, x86_64, gcc 14.2 (ASan). First seen with emscripten 6.0.13 (WebAssembly).

主要语言
C
星标
41.8k
派生
8.5k
平均合并
2 天 18 小时
30 天内合并 PR
145

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

php/php-src 的其他 Issue

查看 php/php-src 的全部 Issue

相似的 Issue

更多 C Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。