opcache: smm_shared_globals left pointing to a dead stack frame when there is no SHM backend
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 72/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 领域
- backend, performance
调研方向
从 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 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
php/php-src 的其他 Issue
-
SNMP accepts malformed remote ports可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 半天 新手友好度 62/100
维护者通常 1 天内回复
-
Bug Status: Needs Triage
难度 2/5 1-3 小时 新手友好度 64/100
维护者通常 1 天内回复
-
quoted_printable_encode() produces lines longer than 76 characters for bytes 0xF5 to 0xFF可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
Bug Status: Needs Triage
难度 2/5 1-3 小时 新手友好度 62/100
维护者通常 1 天内回复
-
Feature Status: Needs Triage
难度 2/5 1-3 小时 新手友好度 73/100
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 72/100
containers/bubblewrap#826 ·
维护者通常 1 天内回复
-
feature/themes QA/No release-notes/exclude tests tests/missing-coverage tests/test-quality
难度 2/5 1-3 小时 新手友好度 72/100
brave/brave-browser#60971 ·
维护者通常 1 天内回复
-
[compiler-rt] Rtsan-aarch64-NoInstTest: error: no such file or directory: 'NOTFOUND' (libatomic)未关闭build-problem compiler-rt:rtsan platform:linux
难度 2/5 1-3 小时 新手友好度 63/100
llvm/llvm-project#239039 ·
维护者通常 1 天内回复
-
component:obi contribfest go test
难度 2/5 1 小时以内 新手友好度 83/100
open-telemetry/opentelemetry-ebpf-instrumentation#4143 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 64/100
checkpoint-restore/criu#3304 ·
维护者通常 1 天内回复