Aktuālā situācija: PHP2Shell. Divas kritiskas PHP ievainojamības (CVSS 9.2) ļauj izpildīt ļaunprātīgu kodu bez pieteikšanās. Ielāpi kopš 21.10.2026.Kas jādara tagad →

Atmiņas pārvaldība PHP: atsauču skaitīšana un Garbage Collection

PHP pārvalda atmiņu ar atsauču skaitīšanu un Garbage Collector ciklu atrašanai. Šī lapa izskaidro mehāniku, svarīgākās mērīšanas funkcijas un kāpēc abas PHP2Shell ievainojamības saistītas ar atmiņas stāvokļiem.

Aktuāls uz 4 min lasīšanas laiksPHP zināšanas

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

  1. 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.
  2. Kad buferis ir pilns, PHP apiet visas sasniedzamās vērtības un izmēģinājuma kārtā samazina to skaitītājus.
  3. Otrajā gājienā: vērtības ar skaitītāju nulle ir atkritumi, visām pārējām samazināšana tiek atcelta.
  4. 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
Svarīgi

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.

Avoti

Tūlītējs kontakts

Katra stunda ir svarīga. Rakstiet mums tūlīt.

Īsi aprakstiet sistēmu, PHP versiju un novēroto. Mēs sazināsimies, noskaidrosim situāciju un strādāsim attālināti, līdz incidents ir slēgts un dokumentēts.

Tūlītēja palīdzība