Na kratko: Življenjski cikel zahteve PHP ima dve ravni. Enkrat na proces PHP naloži svoje razširitve (faza MINIT) in jih ob koncu znova podre (MSHUTDOWN). Za vsako posamezno zahtevo obstaja lastna faza zagona in čiščenja (RINIT in RSHUTDOWN). Pri spletnem delovanju worker zaporedoma obdela veliko zahtev, zato mora PHP med dvema zahtevama zanesljivo ponastaviti vse stanje zahteve. Prav na tej meji je ležala CVE-2026-109619, druga ranljivost PHP2Shell.
Dve ravni: proces in zahteva
Za netehnike pomaga slika: worker PHP je kot okence na uradu. Okence se zjutraj enkrat uredi, z obrazci, žigi in računalnikom. Nato zaporedoma pridejo številni obiskovalci. Po vsakem obiskovalcu mora biti miza prazna, da naslednji ne vidi tujih dokumentov.
PHP deluje enako. Ob zagonu procesa naloži razširitve in nastavitve, ki so za vse zahteve enake. Vsaka zahteva nato dobi lasten pomnilnik, lastne spremenljivke in lastne podatke zahteve, kot so $_GET, $_POST ali $_FILES. Ob koncu zahteve PHP ta pomnilnik sprosti. PHP Internals Book to povzame na kratko: PHP ob koncu vsake zahteve pozabi pomnilnik zahteve.
Faze življenjskega cikla zahteve PHP na hitro
Imena izvirajo iz notranjosti PHP; vsaka razširitev se lahko vključi v te faze.
| Faza | Kdaj teče | Kako pogosto | Tipična naloga |
|---|---|---|---|
| MINIT (Module Init) | ob zagonu procesa | enkrat na proces | registracija razširitev, vnosi ini, trajni podatki |
| GINIT | ob ustvarjanju globalnih spremenljivk | na proces, pri nitih na nit | priprava globalnih spremenljivk razširitve |
| RINIT (Request Init) | na začetku vsake zahteve | na zahtevo | priprava pomnilnika in stanja zahteve |
| Izvajanje | vmes | na zahtevo | teče vaša koda PHP |
| RSHUTDOWN | ob koncu vsake zahteve | na zahtevo | sprostitev virov zahteve |
| PRSHUTDOWN | po RSHUTDOWN | na zahtevo, redko uporabljen | zadnja čistilna dela |
| MSHUTDOWN | ob zaključku procesa | enkrat na proces | sprostitev trajnih podatkov |
Pomemben stavek iz PHP Internals Book za razvijalce razširitev: globalne spremenljivke se po vsaki zahtevi ne izpraznijo samodejno. Kar mora biti ob vsaki zahtevi svežo, je treba v RINIT ponastaviti. To pravilo kaže, zakaj je meja med dvema zahtevama varnostno kritično mesto.
Ob koncu zahteve je tudi vrstni red določen: najprej se izvedejo funkcije za zaustavitev (shutdown), ki ste jih registrirali z register_shutdown_function(), nato destruktorji, nato se izprazni izhodni medpomnilnik. Šele zatem sledi RSHUTDOWN.
Zakaj se workerji ponovno uporabljajo
V ukazni vrstici en proces obdela natanko eno zahtevo in se konča. Pri spletnem delovanju bi bilo to prepočasno. PHP-FPM, Apache z mod_php, LiteSpeed in FrankenPHP zato držijo pripravljene workerje, ki streže veliko zahtev zapored. MINIT tedaj teče enkrat, RINIT in RSHUTDOWN tisočkrat. Kako FPM upravlja svoje workerje, razlaga PHP-FPM razložen.
V PHP-FPM pm.max_requests določa, po koliko zahtevah se worker znova zažene. Privzeto je 0, torej neomejeno. Priročnik nastavitev navaja kot sredstvo proti uhajanju pomnilnika v knjižnicah tretjih ponudnikov:
; pool FPM: worker znova zagnati po 500 zahtevah (proti uhajanju pomnilnika)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500
Še dlje gre način workerja pri FrankenPHP: tam se celotna aplikacija zažene enkrat in ostane v pomnilniku. Stanje aplikacije tedaj preživi tudi na ravni PHP. Primer kaže razliko:
<?php
function anfrageZaehler(): int
{
static $zaehler = 0; // pri klasičnem delovanju FPM je na zahtevo spet 0
return ++$zaehler; // v načinu workerja vrednost šteje naprej prek zahtev
}
echo anfrageZaehler();
Pri klasičnem delovanju klic vedno izpiše 1, ker PHP pomnilnik zahteve zavrže. V načinu workerja vrednost narašča, dokler worker živi. Dokumentacija FrankenPHP zato svetuje, da se stanje, vezano na zahtevo, med zahtevami namenoma ponastavi.
Kje CVE-2026-109619 napade
CVE-2026-109619 ne zadeva kode vaše aplikacije, temveč sam PHP. Po navedbah php.net je lahko notranje stanje, ki sodi k zahtevi, preživelo sprostitev svojega pomnilnika. Poznejša zahteva v istem workerju ga je lahko uporabila, preden ga je inicializacija ponastavila. V sliki: na mizi okenca je ostal listek, ki bi moral biti že pospravljen. Podrobnosti izkoriščanja namenoma ne objavljamo; uvrstitev je pod CVE-2026-109619.
Prehodni ukrepi php.net za to ranljivost se lotevajo potrjenih poti in niso popolna zaščita:
session.upload_progress.enabled=0v php.ini, poolu FPM ali konfiguraciji strežnika, nato znova zagnati vse workerje; ne prekini_set()ali.user.ini.- Na PHP 8.5 dodatno
fatal_error_backtraces=0in ponovni zagon. - Odstraniti neveljavne ali zastarele nastavitve, ki ob vsaki zahtevi pred inicializacijo ustvarjajo sporočila. Sporočila samo skriti po php.net ne pomaga.
- Odpraviti od zunaj sprožljive smrtne napake in prekoračitve časa v aplikaciji, tudi v povratnih klicih (callback), pri obdelavi nalaganja in uvoza.
Pravo pomoč pomeni posodobitev na PHP 8.5.12, 8.4.27, 8.3.36 ali 8.2.35. Ker napaka tiči v tekočem workerju, deluje šele po ponovnem zagonu vseh procesov; koraki so pod Ponovni zagon PHP-FPM in vseh workerjev.
Po ponovnem zagonu z ps -eo pid,lstart,cmd | grep php preverite, ali so bili vsi procesi PHP zagnani po času posodobitve.
Kaj življenjski cikel pomeni za obratovanje in razvoj
Iz modela izhajajo jasna pravila. Za skrbnike: posodobitev je učinkovita šele, ko ne teče več noben proces s staro kodo. Vsaka vrsta obratovanja z dolgoživimi workerji potrebuje dokumentiran ponovni zagon. Za razvijalce: koda, ki teče v načinih workerja, v statičnih spremenljivkah ali singletonih ne sme pustiti stanja, vezanega na zahtevo, na primer podatkov uporabnika ali pravic. Za oba velja: dnevnike od 20. 10. 2026 hranite, da je pozneje mogoče presoditi, ali se je pred popravkom kaj zgodilo.
Kako PHP SOS pomaga konkretno?
S storitvijo popravljanja PHP model uresničujemo v praksi:
- Določiti način delovanja: ugotovimo, ali teče PHP-FPM, mod_php, LiteSpeed, FrankenPHP ali php-cgi in kako se upravljajo workerji.
- Posodobitev in ponovni zagon: namestimo popravljeno različico in nadzorovano znova zaženemo vsakega workerja.
- Preverjanje procesov: na podlagi časov zagona in različic dokažemo, da ni ostal noben star proces.
- Preveriti načine workerja: pri dolgoživih aplikacijah pogledamo, ali se stanje, vezano na zahtevo, čisto ponastavi.
Pogosta vprašanja
Ali PHP za vsako zahtevo zažene nov proces?
V ukazni vrstici da, pri spletnem delovanju večinoma ne. PHP-FPM, Apache z mod_php, LiteSpeed ali FrankenPHP pustijo, da en proces zaporedoma obdela veliko zahtev. Med zahtevami PHP pospravi stanje zahteve.
Kakšna je razlika med MINIT in RINIT?
MINIT se izvede enkrat, ko se proces PHP zažene, in ustvari trajne strukture. RINIT se izvede na začetku vsake posamezne zahteve in pripravi tisto, kar potrebuje samo ta zahteva.
Ali pm.max_requests pomaga proti CVE-2026-109619?
php.net pogostejših ponovnih zagonov workerjev ne navaja kot prehodnega ukrepa. Nastavitev omejuje samo, koliko zahtev worker obdela, preden se znova zažene. Proti ranljivosti pomaga posodobitev s poznejšim ponovnim zagonom vseh workerjev.
Kaj je način workerja pri FrankenPHP?
V načinu workerja se aplikacija zažene enkrat in ostane v pomnilniku. To prihrani čas, vendar statične spremenljivke, globalne spremenljivke in predpomnilniki med zahtevami ostanejo. Po dokumentaciji FrankenPHP je treba stanje, vezano na zahtevo, zato namenoma ponastaviti.