Aktuální situace: PHP2Shell. Dvě kritické zranitelnosti PHP (CVSS 9.2) umožňují spustit škodlivý kód bez přihlášení. Záplaty od 21. 10. 2026.Co nyní udělat →

Správa paměti v PHP: počítání referencí a garbage collection

PHP spravuje paměť pomocí počítání referencí a garbage collectoru pro cykly. Tato stránka vysvětluje mechanismus, nejdůležitější měřicí funkce a proč obě zranitelnosti PHP2Shell souvisejí se stavy paměti.

Stav 4 min čteníZnalosti o PHP

Stručně: PHP uvolňuje paměť především počítáním referencí: každá hodnota ví, kolik proměnných na ni ukazuje, a smaže se, jakmile tento čítač dosáhne nuly. Pro kruhové odkazy, které by se tak nikdy neuvolnily, existuje garbage collector cyklů. Na konci každého požadavku PHP navíc uklidí vše, co požadavek obsadil. Obě zranitelnosti PHP2Shell míří na stavy paměti: na stav nedostatku paměti při zpracování požadavku a na interní stav, který po uvolnění zůstává příliš dlouho zachován.

Jak PHP spravuje paměť: přehled

V jazycích jako C si programátor musí paměť sám vyžádat a znovu uvolnit. PHP to převezme. Každá proměnná, každé pole a každý objekt zabírají paměť, kterou PHP spravuje vlastním správcem paměti. Hranici určuje memory_limit, podle příručky standardně 128M.

Pro rozhodovatele je klíčový jeden bod: při klasickém provozu s PHP-FPM nebo mod_php běží dlouhověké procesy workerů, které postupně zpracovávají mnoho požadavků. Co po sobě požadavek zanechá, musí být před dalším spolehlivě uklizeno. Právě v tomto místě leží jedna ze zranitelností PHP2Shell.

Počítání referencí: počítat, kdo hodnotu používá

PHP ukládá každou hodnotu do kontejneru, takzvaného zval. Vedle typu a obsahu vede podle příručky čítač referencí (refcount) a značku, zda je hodnota součástí reference PHP pomocí & (is_ref).

<?php
$a = str_repeat('x', 1000);  // jedna hodnota, refcount 1
$b = $a;                     // žádné kopírování, refcount 2
$b .= 'y';                   // teprve nyní se kopíruje (copy-on-write)
unset($a);                   // refcount originálu klesá
// Když čítač dosáhne 0, PHP paměť okamžitě uvolní.

Princip se jmenuje copy-on-write: PHP hodnotu zkopíruje, teprve když ji některá z proměnných změní. To ušetří hodně paměti. Čítač proměnné lze zviditelnit pomocí rozšíření Xdebug a funkce xdebug_debug_zval(). Příručka upozorňuje, že přesná čísla se mohou podle verze PHP lišit.

Cykly: když počítání referencí nestačí

Počítání referencí má známou slabinu. Pokud struktura ukazuje sama na sebe, její čítač nikdy neklesne na nulu.

<?php
$a = ['eins'];
$a[] = &$a;   // pole obsahuje referenci samo na sebe
unset($a);    // nikdo k němu už nemůže přistoupit, ale refcount zůstává 1

Podle příručky se takové kruhové odkazy vyskytují zejména u objektů, například ve strukturách rodič-potomek nebo v parserech. Jednotlivé úniky jsou neškodné, protože PHP na konci požadavku uklidí. V dlouho běžících procesech, jako jsou workery front nebo velké testovací běhy, se ale miliony takových úniků mohou sečíst ve skutečný problém s pamětí.

Garbage collector cyklů

Od PHP 5.3.0 používá PHP k nalezení takových cyklů algoritmus podle Bacona a Rajana. Základní myšlenka: cyklus může vzniknout jen tehdy, když čítač klesne na hodnotu větší než nula. Jen tyto hodnoty si PHP zapamatuje jako možné kořeny.

  1. Možné kořeny skončí v bufferu. Příručka uvádí jako standardní velikost 10 000 položek; aktuálně platnou prahovou hodnotu ukazuje gc_status() v poli threshold.
  2. Když je buffer plný, PHP projde všechny dosažitelné hodnoty a zkusmo sníží jejich čítače.
  3. Ve druhém průchodu platí: hodnoty s čítačem nula jsou odpad, u všech ostatních se snížení vrátí zpět.
  4. Kořeny se odstraní z bufferu a všechny hodnoty rozpoznané jako odpad se uvolní.

Řídí se to pomocí zend.enable_gc (standardně zapnuto) nebo za běhu pomocí gc_enable() a gc_disable(). gc_collect_cycles() vynutí běh a vrátí počet nasbíraných cyklů.

Měření a sledování paměti

Funkce Účel
memory_get_usage() paměť aktuálně obsazená skriptem
memory_get_usage(true) paměť, kterou správce paměti rezervoval u systému; proti této hodnotě se kontroluje memory_limit
memory_get_peak_usage() maximum obsazené paměti
gc_status() běhy, nasbírané cykly, prahová hodnota, kořeny; od PHP 8.3.0 i doby běhu
gc_collect_cycles() okamžité spuštění sbírání cyklů
WeakMap přiřazení k objektům, aniž by bránilo jejich uvolnění, od PHP 8.0

Podle příručky memory_get_usage() zachycuje jen paměť, kterou PHP vyžádá přes vlastní správce paměti. Co si rozšíření berou přímo od operačního systému, v těchto číslech chybí. Za limit paměti celého procesu proto odpovídá operační systém nebo kontejner.

Správa paměti a PHP2Shell

Popisy obou zranitelností vedou přímo k tomuto tématu. U CVE-2026-109618 může stav nedostatku paměti přerušit PHP ve chvíli, kdy jsou data požadavku inicializována jen částečně; při úklidu pak mohou být neplatná data považována za platná. Co se při přetečení paměti děje uvnitř, vysvětluje stránka memory_limit a Out-of-Memory. U CVE-2026-109619 může interní stav požadavku po uvolnění paměti zůstat zachován a pozdější požadavek ve stejném workeru jej může využít dříve, než se vynuluje. Průběh od startu po úklid popisuje stránka Životní cyklus požadavku.

Jako přechodné opatření php.net doporučuje nastavit memory_limit=-1 tak, aby jej aplikace nemohly přepsat, například pomocí php_admin_value v poolu FPM, a paměť omezit místo toho přes kontejner nebo cgroups, ne přes RLIMIT_AS nebo RLIMIT_DATA. Jak to jde se systemd, ukazuje článek Nastavení limitů paměti pomocí systemd a cgroups.

; pool FPM: přechodné opatření podle php.net, poté restartovat všechny workery
php_admin_value[memory_limit] = -1
Důležité

Bez limitu přes kontejner nebo cgroup může jediný proces vytížit celý server. A žádné nastavení nenahrazuje aktualizaci na PHP 8.5.12, 8.4.27, 8.3.36 nebo 8.2.35 včetně restartu všech workerů, protože staré procesy z paměti odstraní jen restart.

Jak konkrétně pomáhá PHP SOS?

  • Zkontrolujeme účinná nastavení paměti pro každý pool a zda je aplikace mohou přepsat.
  • Zavedeme přechodná opatření s limitem paměti na úrovni kontejneru nebo cgroup a otestujeme zátěž.
  • Nasadíme aktualizaci, restartujeme všechny workery a prokážeme, že už neběží žádné staré procesy, v rámci naší služby záplatování PHP.
  • Vyhodnotíme chyby paměti a ukončené workery v lozích od 20. 10. 2026.

Časté dotazy

Co je počítání referencí v PHP?

PHP u každé hodnoty počítá, kolik proměnných nebo prvků na ni odkazuje. Když čítač klesne na nulu, paměť se okamžitě uvolní. Většina paměti se tak vrací bez vlastního úklidového běhu.

K čemu pak PHP ještě potřebuje garbage collector?

Pro cykly. Pokud pole odkazuje samo na sebe nebo se dva objekty navzájem znají, čítač nikdy neklesne na nulu, i když k nim už nikdo nepřistupuje. Garbage collector cyklů takové struktury najde a odstraní.

Uvolní PHP paměť na konci požadavku?

Ano, paměť, kterou skript během požadavku obsadil, PHP na konci požadavku uklidí. U PHP2Shell se ale CVE-2026-109619 týká právě případu, kdy může interní stav po uvolnění zůstat zachován a může jej využít pozdější požadavek ve stejném workeru.

Proč php.net doporučuje memory_limit=-1 jako přechodné opatření?

Zranitelnost CVE-2026-109618 souvisí se stavem nedostatku paměti. php.net doporučuje nastavit memory_limit=-1 tak, aby jej aplikace nemohly přepsat, a paměť omezit místo toho přes kontejner nebo cgroups. Není to úplná ochrana a aktualizaci to nenahrazuje.

Zdroje

Okamžitý kontakt

Každá hodina se počítá. Napište nám hned.

Stručně popište systém, verzi PHP a pozorování. Ozveme se, vyjasníme situaci a budeme pracovat na dálku, dokud nebude incident uzavřen a zdokumentován.

Okamžitá pomoc