Ajankohtainen tilanne: PHP2Shell. Kaksi kriittistä PHP-haavoittuvuutta (CVSS 9.2) sallii haittakoodin suorittamisen ilman kirjautumista. Korjaukset 21.10.2026 alkaen.Mitä nyt on tehtävä →

Muistinhallinta PHP:ssä: viitelaskenta ja roskienkeruu

PHP hallitsee muistia viitelaskennalla ja kehiä varten roskienkerääjällä. Sivulla selitetään mekaniikka, tärkeimmät mittausfunktiot ja se, miksi kummallakin PHP2Shell-aukolla on tekemistä muistitilojen kanssa.

Tilanne Lukuaika 4 minPHP-tietoa

Lyhyesti: PHP vapauttaa muistia ennen kaikkea viitelaskennalla: jokainen arvo tietää, kuinka moni muuttuja osoittaa siihen, ja se poistetaan heti, kun tämä laskuri saavuttaa nollan. Kehäviittauksille, joita ei näin koskaan vapautettaisi, on olemassa kehiä keräävä roskienkerääjä. Jokaisen pyynnön päättyessä PHP lisäksi siivoaa kaiken, minkä pyyntö on varannut. Kumpikin PHP2Shell-aukko kohdistuu muistitiloihin: Out-of-Memory-tilaan pyynnön käsittelyn aikana ja sisäiseen tilaan, joka säilyy vapautuksen jälkeen liian pitkään.

Miten PHP hallitsee muistia: yleiskatsaus

Kielissä kuten C ohjelmoijan on itse pyydettävä muistia ja vapautettava se. PHP ottaa tämän huolehdittavakseen. Jokainen muuttuja, taulukko ja olio varaa muistia, jota PHP hallitsee omalla muistinhallinnallaan. Rajan tälle asettaa memory_limit, joka on käsikirjan mukaan oletuksena 128M.

Päättäjälle yksi seikka on keskeinen: perinteisessä PHP-FPM- tai mod_php-käytössä pitkäikäiset worker-prosessit käsittelevät peräkkäin monta pyyntöä. Mitä pyyntö jättää jälkeensä, sen on oltava luotettavasti siivottu ennen seuraavaa. Juuri tässä kohdassa on toinen PHP2Shell-aukoista.

Viitelaskenta: lasketaan, kuka käyttää arvoa

PHP tallentaa jokaisen arvon säiliöön, jota kutsutaan nimellä zval. Tyypin ja sisällön lisäksi siinä on käsikirjan mukaan viitelaskuri (refcount) ja merkintä siitä, onko arvo osa PHP-viitettä merkillä & (is_ref).

<?php
$a = str_repeat('x', 1000);  // yksi arvo, refcount 1
$b = $a;                     // ei kopioida, refcount 2
$b .= 'y';                   // vasta nyt kopioidaan (copy-on-write)
unset($a);                   // alkuperäisen refcount laskee
// Kun laskuri saavuttaa arvon 0, PHP vapauttaa muistin heti.

Periaatetta kutsutaan nimellä copy-on-write: PHP kopioi arvon vasta, kun jokin muuttujista muuttaa sitä. Se säästää paljon muistia. Muuttujan laskurin voi tehdä näkyväksi Xdebug-laajennuksella ja funktiolla xdebug_debug_zval(). Käsikirja huomauttaa, että tarkat luvut voivat vaihdella PHP-versioittain.

Kehät: kun viitelaskenta ei riitä

Viitelaskennalla on tunnettu heikkous. Jos rakenne osoittaa itseensä, sen laskuri ei koskaan putoa nollaan.

<?php
$a = ['eins'];
$a[] = &$a;   // taulukko sisältää viitteen itseensä
unset($a);    // kukaan ei enää pääse siihen käsiksi, mutta refcount pysyy arvossa 1

Käsikirjan mukaan tällaisia kehäviittauksia esiintyy erityisesti olioissa, esimerkiksi vanhempi–lapsi-rakenteissa tai jäsentimissä. Yksittäiset vuodot ovat vaarattomia, koska PHP siivoaa pyynnön päättyessä. Pitkään käynnissä olevissa prosesseissa, kuten jonotyöntekijöissä tai suurissa testiajoissa, miljoonat tällaiset voivat kuitenkin kasvaa todelliseksi muisti-ongelmaksi.

Kehiä keräävä roskienkerääjä

Versiosta PHP 5.3.0 lähtien PHP käyttää Baconin ja Rajanin algoritmia tällaisten kehien löytämiseen. Perusajatus: kehä voi syntyä vain, jos laskuri laskee arvoon, joka on suurempi kuin nolla. Vain nämä arvot PHP muistaa mahdollisina juurina.

  1. Mahdolliset juuret päätyvät puskuriin. Käsikirja mainitsee oletuskooksi 10 000 alkiota; nykyisen kynnysarvon näyttää gc_status() kentässä threshold.
  2. Kun puskuri on täynnä, PHP käy läpi kaikki tavoitettavissa olevat arvot ja pienentää kokeeksi niiden laskureita.
  3. Toisella läpikäynnillä pätee: arvot, joiden laskuri on nolla, ovat roskaa; kaikilla muilla pienennys perutaan.
  4. Juuret poistetaan puskurista ja kaikki roskaksi tunnistetut arvot vapautetaan.

Toimintaa ohjataan asetuksella zend.enable_gc (oletuksena päällä) tai ajon aikana funktioilla gc_enable() ja gc_disable(). gc_collect_cycles() pakottaa ajon ja palauttaa kerättyjen kehien määrän.

Muistin mittaus ja seuranta

Funktio Tarkoitus
memory_get_usage() komentosarjan tällä hetkellä varaama muisti
memory_get_usage(true) muistinhallinnan järjestelmältä varaama muisti; tätä arvoa vasten memory_limit tarkistetaan
memory_get_peak_usage() varatun muistin huippuarvo
gc_status() ajot, kerätyt kehät, kynnysarvo, juuret; versiosta PHP 8.3.0 lähtien myös ajoajat
gc_collect_cycles() käynnistää kehien keräyksen heti
WeakMap liitos olioihin estämättä niiden vapautusta, versiosta PHP 8.0 lähtien

Käsikirjan mukaan memory_get_usage() laskee vain muistin, jonka PHP pyytää omalla muistinhallinnallaan. Se, minkä laajennukset hakevat suoraan käyttöjärjestelmältä, puuttuu näistä luvuista. Koko prosessin muistirajasta vastaavat siksi käyttöjärjestelmä tai kontti.

Muistinhallinta ja PHP2Shell

Molempien aukkojen kuvaukset johtavat suoraan tähän aiheeseen. Haavoittuvuudessa CVE-2026-109618 Out-of-Memory-tila voi keskeyttää PHP:n, kun pyyntödata on vasta osittain alustettu; siivouksessa virheellistä dataa voidaan sitten käsitellä kelvollisena. Mitä muistin ylityksessä tapahtuu sisäisesti, selittää sivu memory_limit ja Out-of-Memory. Haavoittuvuudessa CVE-2026-109619 pyynnön sisäinen tila voi säilyä muistin vapautuksen jälkeen, ja myöhempi pyyntö samassa workerissa voi käyttää sitä ennen kuin se nollataan. Kulun alusta siivoukseen kuvaa sivu Pyynnön elinkaari.

Väliaikaisena toimenpiteenä php.net suosittelee asettamaan memory_limit=-1 niin, etteivät sovellukset voi ylikirjoittaa sitä, esimerkiksi php_admin_value-direktiivillä FPM-poolissa, ja rajaamaan muistia sen sijaan kontilla tai cgroupeilla, ei RLIMIT_AS- tai RLIMIT_DATA-rajoilla. Miten se tehdään systemd:llä, näyttää artikkeli Muistirajojen asettaminen systemd:llä ja cgroupeilla.

; FPM-pooli: php.netin mukainen väliaikainen toimenpide, sen jälkeen kaikkien workerien uudelleenkäynnistys
php_admin_value[memory_limit] = -1
Tärkeää

Ilman kontin tai cgroupin kautta asetettua rajaa yksittäinen prosessi voi kuormittaa palvelimen täyteen. Eikä mikään asetus korvaa päivitystä versioon PHP 8.5.12, 8.4.27, 8.3.36 tai 8.2.35 sekä kaikkien workerien uudelleenkäynnistystä, sillä vasta uudelleenkäynnistys poistaa vanhat prosessit muistista.

Miten PHP SOS auttaa konkreettisesti?

  • Tarkistamme voimassa olevat muistiasetukset poolikohtaisesti ja sen, voivatko sovellukset ylikirjoittaa ne.
  • Toteutamme väliaikaiset toimenpiteet muistirajalla kontin tai cgroupin tasolla ja testaamme kuorman.
  • Asennamme päivityksen, käynnistämme kaikki workerit uudelleen ja osoitamme, ettei vanhoja prosesseja enää ole käynnissä, PHP-korjauspalvelumme puitteissa.
  • Analysoimme lokeista muistivirheet ja keskeytyneet workerit 20.10.2026 alkaen.

Usein kysytyt kysymykset

Mitä viitelaskenta on PHP:ssä?

PHP laskee jokaiselle arvolle, kuinka moni muuttuja tai alkio viittaa siihen. Kun laskuri putoaa nollaan, muisti vapautetaan heti. Näin suurin osa muistista palautetaan ilman erillistä siivousajoa.

Miksi PHP tarvitsee silti roskienkerääjän?

Kehiä varten. Jos taulukko viittaa itseensä tai kaksi oliota tuntee toisensa, laskuri ei koskaan putoa nollaan, vaikka kukaan ei enää käytä niitä. Kehiä keräävä roskienkerääjä löytää ja poistaa tällaiset rakenteet.

Vapauttaako PHP muistin pyynnön päättyessä?

Kyllä, muistin, jonka komentosarja varaa pyynnön aikana, PHP siivoaa pyynnön päättyessä. PHP2Shellissä CVE-2026-109619 koskee kuitenkin juuri tapausta, jossa sisäinen tila voi säilyä vapautuksen jälkeen ja myöhempi pyyntö samassa workerissa voi käyttää sitä.

Miksi php.net suosittelee memory_limit=-1 väliaikaisena toimenpiteenä?

Haavoittuvuus CVE-2026-109618 liittyy Out-of-Memory-tilaan. php.net suosittelee asettamaan memory_limit=-1 niin, etteivät sovellukset voi ylikirjoittaa sitä, ja rajaamaan muistia sen sijaan kontilla tai cgroupeilla. Se ei ole täydellinen suoja eikä korvaa päivitystä.

Lähteet

Pikayhteys

Jokainen tunti on tärkeä. Kirjoittakaa meille nyt.

Kuvatkaa lyhyesti järjestelmä, PHP-versio ja havainnot. Otamme yhteyttä, selvitämme tilanteen ja työskentelemme etänä, kunnes tapaus on käsitelty loppuun ja dokumentoitu.

Pikaapu