Ukratko: PHP memoriju oslobađa ponajprije brojanjem referenci: svaka vrijednost zna koliko varijabli na nju pokazuje i briše se čim taj brojač dosegne nulu. Za kružne reference, koje se tako nikad ne bi oslobodile, postoji garbage collector ciklusa. Na kraju svakog zahtjeva PHP dodatno čisti sve što je zahtjev zauzeo. Obje ranjivosti PHP2Shella tiču se stanja memorije: stanja nedostatka memorije tijekom obrade zahtjeva i internog stanja koje nakon oslobađanja ostaje predugo.
Kako PHP upravlja memorijom: pregled
U jezicima poput C-a programer sâm mora zatražiti memoriju i ponovno je osloboditi. PHP to preuzima. Svaka varijabla, svako polje i svaki objekt zauzimaju memoriju kojom PHP upravlja putem vlastitog upravitelja memorije. Granicu za to postavlja memory_limit, prema priručniku prema zadanim postavkama 128M.
Za donositelje odluka jedna je točka ključna: kod klasičnog rada s PHP-FPM-om ili mod_php-om izvode se dugovječni worker procesi koji jedan za drugim obrađuju mnogo zahtjeva. Što zahtjev ostavi iza sebe, mora se pouzdano počistiti prije sljedećeg. Upravo na tom mjestu leži jedna od ranjivosti PHP2Shella.
Brojanje referenci: brojati tko koristi vrijednost
PHP svaku vrijednost sprema u spremnik koji se naziva zval. Uz tip i sadržaj prema priručniku vodi brojač referenci (refcount) i oznaku je li vrijednost dio PHP reference s & (is_ref).
<?php
$a = str_repeat('x', 1000); // jedna vrijednost, refcount 1
$b = $a; // bez kopiranja, refcount 2
$b .= 'y'; // tek sada se kopira (copy-on-write)
unset($a); // refcount izvornika pada
// Kad brojač dosegne 0, PHP odmah oslobađa memoriju.
Načelo se zove copy-on-write: PHP kopira vrijednost tek kad je jedna od varijabli promijeni. To štedi mnogo memorije. Brojač varijable može se učiniti vidljivim ekstenzijom Xdebug i funkcijom xdebug_debug_zval(). Priručnik upozorava da se točni brojevi razlikuju od verzije do verzije PHP-a.
Ciklusi: kad brojanje referenci nije dovoljno
Brojanje referenci ima poznatu slabost. Ako struktura upućuje sama na sebe, njezin brojač nikad ne padne na nulu.
<?php
$a = ['eins'];
$a[] = &$a; // polje sadrži referencu na samo sebe
unset($a); // nitko mu više ne može pristupiti, ali refcount ostaje 1
Prema priručniku takve kružne reference javljaju se osobito kod objekata, primjerice u strukturama roditelj-dijete ili parserima. Pojedinačna curenja bezazlena su jer PHP na kraju zahtjeva čisti. U dugotrajnim procesima poput workera redova čekanja ili velikih testnih pokretanja milijuni ih se ipak mogu zbrojiti u pravi problem s memorijom.
Garbage collector ciklusa
Od PHP-a 5.3.0 PHP za pronalaženje takvih ciklusa koristi algoritam prema Baconu i Rajanu. Osnovna ideja: ciklus može nastati samo ako brojač padne na vrijednost veću od nule. Samo te vrijednosti PHP pamti kao moguće korijene.
- Mogući korijeni završavaju u međuspremniku. Priručnik kao zadanu veličinu navodi 10 000 unosa; trenutačno važeći prag pokazuje
gc_status()u poljuthreshold. - Kad je međuspremnik pun, PHP prolazi kroz sve dostižne vrijednosti i probno smanjuje njihove brojače.
- U drugom prolazu vrijedi: vrijednosti s brojačem nula su smeće, a kod svih ostalih smanjenje se poništava.
- Korijeni se uklanjaju iz međuspremnika, a sve vrijednosti prepoznate kao smeće oslobađaju se.
Upravlja se time putem zend.enable_gc (prema zadanim postavkama uključeno) ili tijekom izvođenja funkcijama gc_enable() i gc_disable(). gc_collect_cycles() prisiljava prolaz i vraća broj skupljenih ciklusa.
Mjerenje i promatranje memorije
| Funkcija | Svrha |
|---|---|
memory_get_usage() |
memorija koju skripta trenutačno zauzima |
memory_get_usage(true) |
memorija koju je upravitelj memorije rezervirao kod sustava; prema ovoj se vrijednosti provjerava memory_limit |
memory_get_peak_usage() |
najviša razina zauzete memorije |
gc_status() |
prolazi, skupljeni ciklusi, prag, korijeni; od PHP-a 8.3.0 i vremena izvođenja |
gc_collect_cycles() |
odmah pokreće skupljanje ciklusa |
WeakMap |
pridruživanje objektima bez sprječavanja njihova oslobađanja, od PHP-a 8.0 |
Prema priručniku memory_get_usage() bilježi samo memoriju koju PHP traži putem vlastitog upravitelja memorije. Ono što ekstenzije uzimaju izravno od operacijskog sustava nedostaje u tim brojkama. Za granicu memorije cijelog procesa zato su zaduženi operacijski sustav ili container.
Upravljanje memorijom i PHP2Shell
Opisi obiju ranjivosti vode izravno u ovu temu. Kod CVE-2026-109618 stanje nedostatka memorije može prekinuti PHP dok su podaci zahtjeva tek djelomično inicijalizirani; pri čišćenju se nevaljani podaci tada mogu tretirati kao valjani. Što se interno događa pri prekoračenju memorije, objašnjava stranica memory_limit i Out-of-Memory. Kod CVE-2026-109619 interno stanje zahtjeva može nakon oslobađanja memorije ostati i upotrijebiti ga kasniji zahtjev u istom workeru prije nego što se vrati na početno. Tijek od pokretanja do čišćenja opisuje stranica Životni ciklus zahtjeva.
Kao prijelaznu mjeru php.net preporučuje postaviti memory_limit=-1 tako da ga aplikacije ne mogu prepisati, primjerice s php_admin_value u FPM poolu, a memoriju umjesto toga ograničiti putem containera ili cgroupa, a ne putem RLIMIT_AS ili RLIMIT_DATA. Kako to ide sa systemdom, pokazuje članak Postavljanje ograničenja memorije pomoću systemda i cgroupa.
; FPM pool: prijelazna mjera prema php.net, zatim ponovno pokrenuti sve workere
php_admin_value[memory_limit] = -1
Bez granice putem containera ili cgroupa jedan proces može opteretiti cijeli poslužitelj. I nijedna postavka ne zamjenjuje ažuriranje na PHP 8.5.12, 8.4.27, 8.3.36 ili 8.2.35 uz ponovno pokretanje svih workera, jer samo ponovno pokretanje uklanja stare procese iz memorije.
Kako PHP SOS konkretno pomaže?
- Provjeravamo djelotvorne postavke memorije po poolu i mogu li ih aplikacije prepisati.
- Prijelazne mjere provodimo uz ograničenje memorije na razini containera ili cgroupa i testiramo opterećenje.
- Instaliramo ažuriranje, ponovno pokrećemo sve workere i dokazujemo da više ne rade stari procesi, u okviru našeg PHP patch servisa.
- Analiziramo pogreške memorije i prekinute workere u zapisnicima od 20. 10. 2026.
Česta pitanja
Što je brojanje referenci u PHP-u?
PHP za svaku vrijednost broji koliko varijabli ili elemenata na nju upućuje. Kad brojač padne na nulu, memorija se odmah oslobađa. Tako se većina memorije vraća bez zasebnog prolaza čišćenja.
Čemu PHP onda još treba garbage collector?
Za cikluse. Ako polje upućuje samo na sebe ili se dva objekta međusobno poznaju, brojač nikad ne padne na nulu, iako im više nitko ne pristupa. Garbage collector ciklusa pronalazi i uklanja takve strukture.
Oslobađa li PHP memoriju na kraju zahtjeva?
Da, memoriju koju skripta zauzme tijekom zahtjeva PHP čisti na kraju zahtjeva. Kod PHP2Shella CVE-2026-109619 pogađa upravo slučaj u kojem unutarnje stanje nakon oslobađanja može ostati i upotrijebiti ga kasniji zahtjev u istom workeru.
Zašto php.net kao prijelaznu mjeru preporučuje memory_limit=-1?
Ranjivost CVE-2026-109618 povezana je sa stanjem nedostatka memorije (out-of-memory). php.net preporučuje da se memory_limit=-1 postavi tako da ga aplikacije ne mogu prepisati, a memoriju umjesto toga ograničiti putem containera ili cgroupa. To nije potpuna zaštita i ne zamjenjuje ažuriranje.