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

Ouverte Adaptée aux débutants
#25,195 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

Évaluation

Difficulté
2/5
Temps estimé
1-3 heures
Accessibilité débutants
72/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
Active
Stack technique
c, php
Domaine
backend, performance

Piste de recherche

Commencez dans ext/opcache/zend_shared_alloc.c, au niveau de zend_shared_alloc_startup(), en vous concentrant sur le retour anticipé lorsque handler_table == NULL et sur la façon dont les autres retours anticipés réinitialisent smm_shared_globals. Reproduisez l’échec avec le programme embed SAPI fourni et la configuration AddressSanitizer, ou examinez les chemins de démarrage existants. Le travail est terminé lorsque le chemin sans SHM ne laisse plus de pointeur vers la variable locale sur la pile et que le reproducteur se termine sans signaler d’utilisation de la pile après le retour.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

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

Langage dominant
C
Étoiles
41.7k
Forks
8.5k
Merge moyen
2 j 18 h
PR mergées (30 j)
145

Préparer son environnement

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de php/php-src

Toutes les issues de php/php-src

Issues similaires

Plus d'issues C

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.