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

Đang mở Phù hợp với người mới
#25,195 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
72/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
c, php
Lĩnh vực
backend, performance

Hướng nghiên cứu

Bắt đầu tại ext/opcache/zend_shared_alloc.c, ở zend_shared_alloc_startup(), tập trung vào lệnh trả về sớm khi handler_table == NULL và cách các lệnh trả về sớm khác đặt lại smm_shared_globals. Tái hiện lỗi bằng chương trình embed SAPI và thiết lập AddressSanitizer được cung cấp, hoặc kiểm tra các đường dẫn khởi động hiện có. Công việc hoàn tất khi đường dẫn không dùng SHM không còn để lại con trỏ tới biến cục bộ trên ngăn xếp và chương trình tái hiện chạy xong mà không có báo cáo stack-use-after-return.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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).

Ngôn ngữ chính
C
Star
41.7k
Fork
8.5k
Merge trung bình
2 ngày 18 giờ
Pull request đã merge (30 ngày)
145

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của php/php-src

Tất cả issue của php/php-src

Issue tương tự

Thêm issue về C

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.