Skip to content

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

Closed

Description

@lyrixx

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

Activity

  1. added 3 commits that reference this issue on Nov 8, 2026
    f854ff9
    32d982e
    7efad6d
  2. lyrixx commented on Nov 8, 2026

    @lyrixx
    Author

    @lazerg Mwai! As I said in the issue, I could do a PR if it's accepted. You've stolen my work and given me no credit... not even a thank you! That's not how open-source software works!

  3. ndossche commented on Nov 8, 2026

    @ndossche
    Member

    @lyrixx sorry about that, I will amend the news entry when I get home

  4. ndossche commented on Nov 8, 2026

    @ndossche
    Member

    I've corrected the news entry now.

  5. lyrixx commented on Nov 8, 2026

    @lyrixx
    Author

    @ndossche Thank you very much 💛

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions