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

Offen Anfängerfreundlich
#25,195 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
2/5
Geschätzter Aufwand
1-3 Stunden
Anfängerfreundlichkeit
72/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
c, php
Bereich
backend, performance

Rechercherichtung

Beginne in ext/opcache/zend_shared_alloc.c bei zend_shared_alloc_startup() und konzentriere dich auf die frühe Rückgabe bei handler_table == NULL sowie darauf, wie die anderen frühen Rückgaben smm_shared_globals zurücksetzen. Reproduziere den Fehler mit dem bereitgestellten embed-SAPI-Programm und dem AddressSanitizer-Setup oder untersuche die bestehenden Startpfade. Fertig ist die Aufgabe, wenn der Pfad ohne SHM keinen Zeiger auf die lokale Stack-Variable mehr hinterlässt und der Reproducer ohne einen Bericht über Stack-use-after-return durchläuft.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

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

Vorherrschende Sprache
C
Sterne
41.7k
Forks
8.5k
Ø Merge
2 T. 18 Std.
Gemergte PRs (30 T.)
145

Entwicklungsumgebung

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus php/php-src

Alle Issues in php/php-src

Ähnliche Issues

Weitere Issues zu C

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.