Praegune olukord: PHP2Shell. Kaks kriitilist PHP turvaauku (CVSS 9,2) lubavad kahjulikku koodi ilma sisselogimata. Paigad alates 21.10.2026.Mida nüüd teha →

Mäluhaldus PHP-s: viidete loendus ja Garbage Collection

PHP haldab mälu viidete loenduse ja tsüklite jaoks mõeldud Garbage Collectoriga. Leht selgitab mehhanismi, olulisemaid mõõtefunktsioone ja miks mõlemad PHP2Shelli turvaaugud on seotud mäluseisunditega.

Seis Lugemisaeg 4 minPHP teadmised

Lühidalt: PHP vabastab mälu peamiselt viidete loenduse abil: iga väärtus teab, mitu muutujat sellele osutab, ja kustutatakse niipea, kui see loendur jõuab nullini. Tsükliliste viidete jaoks, mida niimoodi kunagi ei vabastataks, on tsüklite Garbage Collector. Iga päringu lõpus koristab PHP lisaks kõik, mida päring hõivas. Mõlemad PHP2Shelli turvaaugud ründavad mäluseisundeid: Out-of-Memory seisundit päringu töötlemisel ja sisemist seisundit, mis jääb pärast vabastamist liiga kauaks alles.

Kuidas PHP mälu haldab: ülevaade

Sellistes keeltes nagu C peab programmeerija mälu ise taotlema ja uuesti vabastama. PHP võtab selle üle. Iga muutuja, iga massiiv ja iga objekt hõivab mälu, mida PHP haldab oma mäluhalduriga. Piiri selleks seab memory_limit, käsiraamatu järgi vaikimisi 128M.

Otsustajale on üks punkt kesksel kohal: klassikalises töös PHP-FPM-i või mod_php-ga töötavad pikaealised worker-protsessid, mis töötlevad järjest paljusid päringuid. Mida päring maha jätab, peab enne järgmist usaldusväärselt koristatud olema. Just siin asub üks PHP2Shelli turvaaukudest.

Viidete loendus: lugeda, kes väärtust kasutab

PHP salvestab iga väärtuse konteinerisse, nn zval'i. Lisaks tüübile ja sisule on selles käsiraamatu järgi viidete loendur (refcount) ning märge, kas väärtus on osa PHP viitest märgiga & (is_ref).

<?php
$a = str_repeat('x', 1000);  // üks väärtus, refcount 1
$b = $a;                     // kopeerimist ei toimu, refcount 2
$b .= 'y';                   // alles nüüd kopeeritakse (Copy-on-Write)
unset($a);                   // originaali refcount väheneb
// Kui loendur jõuab nullini, vabastab PHP mälu kohe.

Põhimõtet nimetatakse Copy-on-Write'iks: PHP kopeerib väärtuse alles siis, kui üks muutujatest seda muudab. See säästab palju mälu. Muutuja loendurit saab nähtavaks teha Xdebug laiendi ja funktsiooniga xdebug_debug_zval(). Käsiraamat juhib tähelepanu, et täpsed numbrid võivad PHP versiooniti erineda.

Tsüklid: kui viidete loendusest ei piisa

Viidete loenduse tuntud nõrkus on järgmine. Kui struktuur osutab iseendale, ei lange selle loendur kunagi nulli.

<?php
$a = ['eins'];
$a[] = &$a;   // massiiv sisaldab viidet iseendale
unset($a);    // keegi ei pääse sellele enam ligi, kuid refcount jääb 1

Käsiraamatu järgi esinevad sellised tsüklilised viited eriti objektide puhul, näiteks vanema-lapse struktuurides või parserites. Üksikud lekked on kahjutud, sest PHP koristab päringu lõpus. Kaua töötavates protsessides, nagu järjekorra workerid või suured testijooksud, võivad miljonid neist siiski kokku kasvada tõsiseks mäluprobleemiks.

Tsüklite Garbage Collector

Alates PHP 5.3.0-st kasutab PHP selliste tsüklite leidmiseks Bacon-Rajani algoritmi. Põhiidee: tsükkel saab tekkida ainult siis, kui loendur langeb väärtusele, mis on suurem kui null. Ainult need väärtused jätab PHP meelde võimalike juurtena.

  1. Võimalikud juured satuvad puhvrisse. Käsiraamat nimetab vaikesuuruseks 10 000 kirjet; praegu kehtiva lävendi näitab gc_status() väljal threshold.
  2. Kui puhver on täis, käib PHP läbi kõik ulatatavad väärtused ja vähendab proovi korras nende loendureid.
  3. Teisel läbimisel kehtib: väärtused, mille loendur on null, on prügi, kõigi teiste puhul võetakse vähendamine tagasi.
  4. Juured eemaldatakse puhvrist ja kõik prügiks tunnistatud väärtused vabastatakse.

Seda juhitakse sättega zend.enable_gc (vaikimisi sees) või täitmisajal funktsioonidega gc_enable() ja gc_disable(). gc_collect_cycles() sunnib jooksu käivituma ja tagastab kogutud tsüklite arvu.

Mälu mõõtmine ja jälgimine

Funktsioon Otstarve
memory_get_usage() skripti praegu hõivatud mälu
memory_get_usage(true) mäluhalduri süsteemilt reserveeritud mälu; selle väärtuse vastu kontrollitakse memory_limit
memory_get_peak_usage() hõivatud mälu tipptase
gc_status() jooksud, kogutud tsüklid, lävend, juured; alates PHP 8.3.0-st ka tööajad
gc_collect_cycles() tsüklite kogumise kohene käivitamine
WeakMap objektidega seostus, ilma nende vabastamist takistamata, alates PHP 8.0-st

Käsiraamatu järgi hõlmab memory_get_usage() ainult mälu, mille PHP taotleb oma mäluhalduri kaudu. Mida laiendused otse operatsioonisüsteemist võtavad, nendes numbrites puudub. Kogu protsessi mälupiiri eest vastutavad seetõttu operatsioonisüsteem või konteiner.

Mäluhaldus ja PHP2Shell

Mõlema turvaaugu kirjeldused viivad otse sellesse teemasse. CVE-2026-109618 puhul võib Out-of-Memory seisund PHP töö katkestada ajal, mil päringuandmed on alles osaliselt lähtestatud; koristamisel võidakse siis kehtetuid andmeid käsitleda kehtivatena. Mis mälu ülekäitumisel sisemiselt toimub, selgitab leht memory_limit ja Out-of-Memory. CVE-2026-109619 puhul võib päringu sisemine seisund jääda pärast mälu vabastamist alles ja seda võib hilisem päring samas workeris kasutada enne, kui see lähtestatakse. Kulgu algusest koristamiseni kirjeldab leht Päringu elutsükkel.

Ajutise meetmena soovitab php.net seada memory_limit=-1 nii, et rakendused ei saa seda üle kirjutada, näiteks php_admin_value abil FPM-poolis, ning piirata mälu selle asemel konteineri või cgroupide kaudu, mitte RLIMIT_AS või RLIMIT_DATA abil. Kuidas see systemd-ga käib, näitab artikkel Mälupiirangute seadmine systemd ja cgroupidega.

; FPM-pool: php.net ajutine meede, seejärel kõik workerid uuesti käivitada
php_admin_value[memory_limit] = -1
Oluline

Ilma konteineri või cgroupi kaudu seatud piirita võib üksik protsess serveri täielikult koormata. Ja ükski säte ei asenda uuendust versioonile PHP 8.5.12, 8.4.27, 8.3.36 või 8.2.35 koos kõigi workerite taaskäivitusega, sest ainult taaskäivitus eemaldab vanad protsessid mälust.

Kuidas PHP SOS konkreetselt aitab?

  • Kontrollime toimivaid mälusätteid pooli kaupa ja seda, kas rakendused saavad neid üle kirjutada.
  • Rakendame ajutised meetmed koos mälupiiriga konteineri või cgroupi tasemel ja testime koormust.
  • Paigaldame uuenduse, käivitame kõik workerid uuesti ja tõendame, et vanu protsesse enam ei tööta, meie PHP paikamisteenuse raames.
  • Hindame mälu vigu ja katkenud workereid logides alates 20.10.2026.

Korduma kippuvad küsimused

Mis on viidete loendus PHP-s?

PHP loeb iga väärtuse kohta, mitu muutujat või elementi sellele viitab. Kui loendur langeb nulli, vabastatakse mälu kohe. Nii antakse suurem osa mälust tagasi ilma eraldi koristusjooksuta.

Milleks vajab PHP siis veel Garbage Collectorit?

Tsüklite jaoks. Kui massiiv viitab iseendale või kaks objekti tunnevad teineteist, ei lange loendur kunagi nulli, isegi kui keegi sellele enam ligi ei pääse. Tsüklite Garbage Collector leiab sellised struktuurid üles ja eemaldab need.

Kas PHP vabastab mälu päringu lõpus?

Jah, mälu, mida skript päringu ajal hõivab, koristab PHP päringu lõpus. PHP2Shelli puhul puudutab CVE-2026-109619 aga just juhtu, kus sisemine seisund jääb pärast vabastamist alles ja seda saab kasutada hilisem päring samas workeris.

Miks soovitab php.net ajutise meetmena memory_limit=-1?

Turvaauk CVE-2026-109618 on seotud Out-of-Memory seisundiga. php.net soovitab seada memory_limit=-1 nii, et rakendused ei saa seda üle kirjutada, ja piirata mälu selle asemel konteineri või cgroupide kaudu. See ei ole täielik kaitse ega asenda uuendust.

Allikad

Kohene kontakt

Iga tund loeb. Kirjutage meile kohe.

Kirjeldage lühidalt süsteemi, PHP versiooni ja tähelepanekut. Võtame teiega ühendust, selgitame olukorra välja ja töötame kaugtööna, kuni intsident on lõpetatud ja dokumenteeritud.

Kohene abi