Īsi: PHP atmiņu atbrīvo galvenokārt ar atsauču skaitīšanu: katra vērtība zina, cik mainīgo uz to norāda, un tiek dzēsta, tiklīdz šis skaitītājs sasniedz nulli. Cikliskām atsaucēm, kas tādā veidā nekad netiktu atbrīvotas, ir ciklu Garbage Collector. Katra pieprasījuma beigās PHP papildus uzkopj visu, ko pieprasījums aizņēmis. Abas PHP2Shell ievainojamības saistītas ar atmiņas stāvokļiem: ar Out-of-Memory stāvokli pieprasījuma apstrādes laikā un ar iekšēju stāvokli, kas pēc atbrīvošanas saglabājas pārāk ilgi.
Kā PHP pārvalda atmiņu: pārskats
Tādās valodās kā C programmētājam atmiņa jāpieprasa un jāatbrīvo pašam. PHP to uzņemas. Katrs mainīgais, katrs masīvs un katrs objekts aizņem atmiņu, ko PHP pārvalda ar savu atmiņas pārvaldnieku. Robežu tai nosaka memory_limit, saskaņā ar rokasgrāmatu pēc noklusējuma 128M.
Lēmumu pieņēmējiem būtisks ir viens punkts: klasiskā darbībā ar PHP-FPM vai mod_php darbojas ilgdzīvojoši darbprocesi, kas secīgi apstrādā daudzus pieprasījumus. Ko pieprasījums atstāj, pirms nākamā jābūt droši uzkopts. Tieši šeit atrodas viena no PHP2Shell ievainojamībām.
Atsauču skaitīšana: skaita, kas izmanto vērtību
PHP katru vērtību glabā konteinerā, ko sauc par zval. Līdzās tipam un saturam tas saskaņā ar rokasgrāmatu satur atsauču skaitītāju (refcount) un atzīmi, vai vērtība ir daļa no PHP atsauces ar & (is_ref).
<?php
$a = str_repeat('x', 1000); // viena vērtība, refcount 1
$b = $a; // bez kopēšanas, refcount 2
$b .= 'y'; // tikai tagad tiek kopēts (Copy-on-Write)
unset($a); // oriģināla refcount samazinās
// Kad skaitītājs sasniedz 0, PHP atmiņu atbrīvo uzreiz.
Princips saucas Copy-on-Write: PHP vērtību kopē tikai tad, kad kāds no mainīgajiem to maina. Tas ietaupa daudz atmiņas. Mainīgā skaitītāju var padarīt redzamu ar paplašinājumu Xdebug un funkciju xdebug_debug_zval(). Rokasgrāmata norāda, ka precīzi skaitļi dažādās PHP versijās var atšķirties.
Cikli: kad atsauču skaitīšanas nepietiek
Atsauču skaitīšanai ir zināms trūkums. Ja struktūra norāda uz sevi, tās skaitītājs nekad nenokrītas līdz nullei.
<?php
$a = ['eins'];
$a[] = &$a; // masīvs satur atsauci uz sevi
unset($a); // neviens tam vairs nevar piekļūt, bet refcount paliek 1
Saskaņā ar rokasgrāmatu šādas cikliskas atsauces īpaši sastopamas objektiem, piemēram, vecāku–bērnu struktūrās vai parseros. Atsevišķas noplūdes ir nekaitīgas, jo PHP uzkopj pieprasījuma beigās. Ilgi darbojošos procesos, piemēram, rindu darbprocesos vai lielos testu palaidienos, miljoniem šādu var summēties īstā atmiņas problēmā.
Ciklu Garbage Collector
Kopš PHP 5.3.0 PHP šādu ciklu atrašanai izmanto Bacon un Rajan algoritmu. Pamatideja: cikls var rasties tikai tad, ja skaitītājs nokrītas līdz vērtībai, kas lielāka par nulli. Tikai šīs vērtības PHP atceras kā iespējamās saknes.
- Iespējamās saknes nonāk buferī. Rokasgrāmata kā noklusējuma izmēru min 10 000 ierakstu; pašlaik spēkā esošo sliekšņa vērtību rāda
gc_status()laukāthreshold. - Kad buferis ir pilns, PHP apiet visas sasniedzamās vērtības un izmēģinājuma kārtā samazina to skaitītājus.
- Otrajā gājienā: vērtības ar skaitītāju nulle ir atkritumi, visām pārējām samazināšana tiek atcelta.
- Saknes tiek noņemtas no bufera un visas kā atkritumi atpazītās vērtības tiek atbrīvotas.
To vada ar zend.enable_gc (pēc noklusējuma ieslēgts) vai izpildes laikā ar gc_enable() un gc_disable(). gc_collect_cycles() piespiedu kārtā izpilda gājienu un atgriež savākto ciklu skaitu.
Atmiņas mērīšana un novērošana
| Funkcija | Nolūks |
|---|---|
memory_get_usage() |
pašlaik skripta aizņemtā atmiņa |
memory_get_usage(true) |
atmiņa, ko atmiņas pārvaldnieks rezervējis pie sistēmas; pret šo vērtību tiek pārbaudīts memory_limit |
memory_get_peak_usage() |
aizņemtās atmiņas maksimums |
gc_status() |
gājieni, savāktie cikli, slieksnis, saknes; kopš PHP 8.3.0 arī izpildes laiki |
gc_collect_cycles() |
ciklu savākšanas tūlītēja palaišana |
WeakMap |
piesaiste objektiem, nekavējot to atbrīvošanu, kopš PHP 8.0 |
Saskaņā ar rokasgrāmatu memory_get_usage() uzskaita tikai atmiņu, ko PHP pieprasa ar savu atmiņas pārvaldnieku. Ko paplašinājumi ņem tieši no operētājsistēmas, šajos skaitļos trūkst. Par visa procesa atmiņas robežu tāpēc atbild operētājsistēma vai konteiners.
Atmiņas pārvaldība un PHP2Shell
Abu ievainojamību apraksti ved tieši uz šo tēmu. CVE-2026-109618 gadījumā Out-of-Memory stāvoklis var pārtraukt PHP, kamēr pieprasījuma dati ir inicializēti tikai daļēji; uzkopšanas laikā nederīgi dati tad var tikt apstrādāti kā derīgi. Kas atmiņas pārpildes gadījumā notiek iekšēji, izskaidro lapa memory_limit un Out-of-Memory. CVE-2026-109619 gadījumā pieprasījuma iekšējais stāvoklis pēc atmiņas atbrīvošanas var saglabāties un vēlāks pieprasījums tajā pašā darbprocesā to var izmantot, pirms tas tiek atiestatīts. Norisi no starta līdz uzkopšanai apraksta lapa Pieprasījuma dzīves cikls.
Kā pagaidu pasākumu php.net iesaka memory_limit=-1 iestatīt tā, lai lietojumprogrammas to nevarētu pārrakstīt, piemēram, ar php_admin_value FPM pūlā, un atmiņu tā vietā ierobežot ar konteineru vai cgroups, nevis ar RLIMIT_AS vai RLIMIT_DATA. Kā to izdarīt ar systemd, rāda raksts Atmiņas robežu iestatīšana ar systemd un cgroups.
; FPM pūls: pagaidu pasākums saskaņā ar php.net, pēc tam pārstartēt visus darbprocesus
php_admin_value[memory_limit] = -1
Bez robežas, kas iestatīta ar konteineru vai cgroup, atsevišķs process var noslogot serveri. Un neviens iestatījums neaizstāj atjauninājumu uz PHP 8.5.12, 8.4.27, 8.3.36 vai 8.2.35 kopā ar visu darbprocesu pārstartēšanu, jo tikai pārstartēšana vecos procesus izņem no atmiņas.
Kā PHP SOS palīdz konkrēti?
- Mēs pārbaudām spēkā esošos atmiņas iestatījumus katrā pūlā un to, vai lietojumprogrammas tos var pārrakstīt.
- Mēs ieviešam pagaidu pasākumus ar atmiņas robežu konteinera vai cgroup līmenī un testējam slodzi.
- Mēs ieviešam atjauninājumu, pārstartējam visus darbprocesus un pierādām, ka vecie procesi vairs nedarbojas, mūsu PHP ielāpu servisa ietvaros.
- Mēs izvērtējam atmiņas kļūdas un pārtrauktos darbprocesus žurnālos no 20.10.2026.
Biežāk uzdotie jautājumi
Kas ir atsauču skaitīšana PHP?
PHP katrai vērtībai skaita, cik mainīgo vai elementu uz to norāda. Kad skaitītājs nokrītas līdz nullei, atmiņa tiek atbrīvota uzreiz. Tā lielākā daļa atmiņas tiek atdota bez atsevišķas uzkopšanas.
Kāpēc PHP tad vēl vajadzīgs Garbage Collector?
Cikliem. Ja masīvs norāda uz sevi vai divi objekti pazīst cits citu, skaitītājs nekad nenokrītas līdz nullei, pat ja neviens tiem vairs nepiekļūst. Ciklu Garbage Collector šādas struktūras atrod un noņem.
Vai PHP atbrīvo atmiņu pieprasījuma beigās?
Jā, atmiņu, ko skripts pieprasījuma laikā aizņem, PHP uzkopj pieprasījuma beigās. PHP2Shell gadījumā CVE-2026-109619 tomēr attiecas tieši uz gadījumu, kad iekšējais stāvoklis pēc atbrīvošanas var saglabāties un to var izmantot vēlāks pieprasījums tajā pašā darbprocesā.
Kāpēc php.net kā pagaidu pasākumu iesaka memory_limit=-1?
Ievainojamība CVE-2026-109618 saistīta ar Out-of-Memory stāvokli. php.net iesaka memory_limit=-1 iestatīt tā, lai lietojumprogrammas to nevarētu pārrakstīt, un atmiņu tā vietā ierobežot ar konteineru vai cgroups. Tā nav pilnīga aizsardzība un neaizstāj atjauninājumu.