Aktuális helyzet: PHP2Shell. Két kritikus PHP-rés (CVSS 9,2) lehetővé teszi kártékony kód futtatását bejelentkezés nélkül. Javítások 2026. 10. 21. óta.Mi a teendő most →

Memóriakezelés a PHP-ban: referenciaszámlálás és szemétgyűjtés

A PHP referenciaszámlálással és a ciklusokhoz való szemétgyűjtővel kezeli a memóriát. Az oldal bemutatja a mechanikát, a legfontosabb mérőfüggvényeket, és azt, hogy a PHP2Shell mindkét rése miért függ össze memóriaállapotokkal.

Állapot 5 perc olvasási időPHP-tudástár

Röviden: A PHP a memóriát elsősorban referenciaszámlálással szabadítja fel: minden érték tudja, hány változó mutat rá, és törlődik, amint ez a számláló eléri a nullát. Azokra a körkörös hivatkozásokra, amelyeket így sosem szabadítana fel, ciklusokat kezelő szemétgyűjtő (garbage collector) van. Minden kérés végén a PHP ezen felül rendbe teszi mindazt, amit a kérés lefoglalt. A PHP2Shell mindkét rése memóriaállapotokon támad: egy Out-of-Memory-állapoton a kérés feldolgozása közben, és olyan belső állapoton, amely a felszabadítás után túl sokáig megmarad.

Hogyan kezeli a PHP a memóriát: áttekintés

Az olyan nyelvekben, mint a C, a programozónak magának kell memóriát kérnie és ismét felszabadítania. A PHP ezt átveszi. Minden változó, minden tömb és minden objektum memóriát foglal, amelyet a PHP a saját memóriakezelőjén keresztül kezel. Ennek határát a memory_limit szabja meg, a kézikönyv szerint alapértelmezés szerint 128M.

A döntéshozók számára egy pont központi: a klasszikus, PHP-FPM-mel vagy mod_php-val végzett üzemben hosszú életű worker-folyamatok futnak, amelyek egymás után sok kérést dolgoznak fel. Amit egy kérés maga után hagy, azt a következő előtt megbízhatóan rendbe kell tenni. A PHP2Shell egyik rése éppen ezen a ponton van.

Referenciaszámlálás: számoljuk, ki használ egy értéket

A PHP minden értéket egy zval nevű tárolóban tárol. A típus és a tartalom mellett a kézikönyv szerint referenciaszámlálót (refcount) vezet, valamint egy jelölést arról, hogy az érték része-e egy & jellel képzett PHP-referenciának (is_ref).

<?php
$a = str_repeat('x', 1000);  // egy érték, refcount 1
$b = $a;                     // nincs másolás, refcount 2
$b .= 'y';                   // csak most másolódik (copy-on-write)
unset($a);                   // az eredeti refcountja csökken
// Ha egy számláló eléri a 0-t, a PHP azonnal felszabadítja a memóriát.

Az elv neve copy-on-write: a PHP csak akkor másol egy értéket, ha valamelyik változó módosítja. Ez sok memóriát spórol. Egy változó számlálóját az Xdebug bővítménnyel és az xdebug_debug_zval() függvénnyel lehet láthatóvá tenni. A kézikönyv felhívja a figyelmet, hogy a pontos számok PHP-verziónként eltérhetnek.

Ciklusok: amikor a referenciaszámlálás nem elég

A referenciaszámlálásnak ismert gyengéje van. Ha egy szerkezet önmagára mutat, a számlálója soha nem csökken nullára.

<?php
$a = ['eins'];
$a[] = &$a;   // a tömb önmagára mutató referenciát tartalmaz
unset($a);    // már senki sem férhet hozzá, de a refcount 1 marad

A kézikönyv szerint az ilyen körkörös hivatkozások különösen objektumoknál fordulnak elő, például szülő-gyermek szerkezetekben vagy értelmezőkben. Az egyes szivárgások ártalmatlanok, mert a PHP a kérés végén rendet tesz. A hosszan futó folyamatokban, például várakozási sor workerekben vagy nagy tesztfuttatásokban azonban több millió belőlük valódi memóriaproblémává összegződhet.

A ciklusokat kezelő szemétgyűjtő

A PHP 5.3.0 óta a PHP Bacon és Rajan algoritmusát használja az ilyen ciklusok megtalálására. Az alapgondolat: ciklus csak akkor keletkezhet, ha egy számláló nullánál nagyobb értékre csökken. A PHP csak ezeket az értékeket jegyzi meg lehetséges gyökérként.

  1. A lehetséges gyökerek egy pufferbe kerülnek. A kézikönyv alapértelmezett méretként 10 000 bejegyzést nevez meg; az éppen érvényes küszöbértéket a gc_status() mutatja a threshold mezőben.
  2. Ha a puffer megtelt, a PHP végigjárja az összes elérhető értéket, és próbaképpen csökkenti a számlálóikat.
  3. A második menetben az érvényes: a nulla számlálójú értékek szemétnek számítanak, az összes többinél a csökkentést visszavonják.
  4. A gyökereket eltávolítják a pufferből, és a szemétként felismert összes értéket felszabadítják.

Ezt a zend.enable_gc (alapértelmezés szerint bekapcsolva) vagy futásidőben a gc_enable() és a gc_disable() vezérli. A gc_collect_cycles() futást kényszerít ki, és visszaadja az összegyűjtött ciklusok számát.

A memória mérése és megfigyelése

Függvény Cél
memory_get_usage() a szkript által jelenleg lefoglalt memória
memory_get_usage(true) a memóriakezelő által a rendszertől lefoglalt memória; a memory_limit ezzel az értékkel szemben kerül ellenőrzésre
memory_get_peak_usage() a lefoglalt memória csúcsértéke
gc_status() futások, összegyűjtött ciklusok, küszöbérték, gyökerek; a PHP 8.3.0 óta futásidők is
gc_collect_cycles() a ciklusgyűjtés azonnali kiváltása
WeakMap objektumokhoz rendelés azok felszabadulásának megakadályozása nélkül, a PHP 8.0 óta

A kézikönyv szerint a memory_get_usage() csak azt a memóriát számolja, amelyet a PHP a saját memóriakezelőjén át kér. Amit a bővítmények közvetlenül az operációs rendszertől szereznek, az hiányzik ezekből a számokból. A teljes folyamat memóriahatáráért ezért az operációs rendszer vagy a konténer felel.

Memóriakezelés és PHP2Shell

Mindkét rés leírása közvetlenül ehhez a témához vezet. A CVE-2026-109618-nál egy Out-of-Memory-állapot megszakíthatja a PHP-t, miközben a kéradatok csak részben vannak inicializálva; a takarításkor érvénytelen adatok ilyenkor érvényesként kezelődhetnek. Hogy memóriatúlcsordulásnál mi történik belül, azt a memory_limit és Out-of-Memory oldal magyarázza. A CVE-2026-109619-nél egy kérés belső állapota a memória felszabadítása után megmaradhat, és egy későbbi kérés ugyanabban a workerben felhasználhatja, mielőtt visszaállnának. A folyamatot az indulástól a takarításig A kérés életciklusa oldal írja le.

Átmeneti intézkedésként a php.net azt ajánlja, hogy a memory_limit=-1 úgy legyen beállítva, hogy az alkalmazások ne írhassák felül, például az FPM-poolban php_admin_value-val, a memóriát pedig konténerekkel vagy cgroupokkal korlátozzák, nem az RLIMIT_AS vagy az RLIMIT_DATA segítségével. Hogyan működik ez a systemddel, azt a Memóriahatárok beállítása systemd-vel és cgroupokkal cikk mutatja.

; FPM-pool: a php.net szerinti átmeneti intézkedés, utána minden workert újra kell indítani
php_admin_value[memory_limit] = -1
Fontos

Konténeren vagy cgroupon keresztüli korlát nélkül egyetlen folyamat is lefoglalhatja a szervert. Egyetlen beállítás sem helyettesíti a frissítést a PHP 8.5.12, 8.4.27, 8.3.36 vagy 8.2.35 verzióra az összes worker újraindításával, mert csak az újraindítás távolítja el a régi folyamatokat a memóriából.

Hogyan segít konkrétan a PHP SOS?

  • Megvizsgáljuk a poolonkénti érvényes memóriabeállításokat, és azt, hogy az alkalmazások felülírhatják-e őket.
  • Az átmeneti intézkedéseket konténer- vagy cgroup-szintű memóriahatárral valósítjuk meg, és teszteljük a terhelést.
  • Telepítjük a frissítést, újraindítjuk az összes workert, és igazoljuk, hogy már nem fut régi folyamat, a PHP-javítási szolgáltatásunk keretében.
    1. október 20-tól kiértékeljük a memóriahibákat és a megszakadt workereket a naplókban.

Gyakori kérdések

Mi az a referenciaszámlálás a PHP-ban?

A PHP minden értéknél számolja, hány változó vagy elem hivatkozik rá. Ha a számláló nullára csökken, a memória azonnal felszabadul. Így a memória nagy részét külön takarítási menet nélkül adja vissza.

Mire kell akkor még a PHP-nak szemétgyűjtő?

A ciklusokhoz. Ha egy tömb önmagára hivatkozik, vagy két objektum ismeri egymást, a számláló soha nem csökken nullára, még ha senki nem is fér hozzá. A ciklusokat kezelő szemétgyűjtő megtalálja és eltávolítja az ilyen szerkezeteket.

Felszabadítja a PHP a memóriát a kérés végén?

Igen, azt a memóriát, amelyet egy szkript a kérés alatt lefoglalt, a PHP a kérés végén rendbe teszi. A PHP2Shellnél a CVE-2026-109619 viszont éppen arra az esetre vonatkozik, hogy a belső állapot a felszabadítás után megmaradhat, és ugyanabban a workerben egy későbbi kérés felhasználhatja.

Miért ajánlja a php.net a memory_limit=-1 értéket átmeneti intézkedésként?

A CVE-2026-109618 rés egy Out-of-Memory-állapottal függ össze. A php.net azt ajánlja, hogy a memory_limit=-1 úgy legyen beállítva, hogy az alkalmazások ne írhassák felül, a memóriát pedig konténerekkel vagy cgroupokkal korlátozzák. Ez nem teljes védelem, és nem helyettesíti a frissítést.

Források

Azonnali kapcsolat

Minden óra számít. Írjon nekünk most.

Ismertesse röviden a rendszert, a PHP-verziót és a megfigyelését. Jelentkezünk, tisztázzuk a helyzetet, és távolról dolgozunk, amíg az incidens le nem zárul és dokumentálva nem lesz.

Azonnali segítség