Kort sagt: PHP Request Lifecycle har två nivåer. En gång per process laddar PHP sina tillägg (fasen MINIT) och river ner dem igen i slutet (MSHUTDOWN). För varje enskild förfrågan finns en egen start- och städfas (RINIT och RSHUTDOWN). I webbdrift hanterar en worker många förfrågningar efter varandra, därför måste PHP pålitligt återställa allt förfrågningstillstånd mellan två förfrågningar. Just vid den gränsen låg CVE-2026-109619, den andra PHP2Shell-luckan.
Två nivåer: processen och förfrågan
För den som inte är tekniker hjälper en bild: En PHP-worker är som en lucka på ett kontor. Luckan ordnas en gång på morgonen, med blanketter, stämplar och dator. Därefter kommer många besökare efter varandra. Efter varje besökare måste bordet vara tomt, så att nästa inte ser andras handlingar.
PHP arbetar på samma sätt. När en process startar laddar den tillägg och inställningar som är desamma för alla förfrågningar. Varje förfrågan får sedan eget minne, egna variabler och egna förfrågningsdata som $_GET, $_POST eller $_FILES. I slutet av förfrågan frigör PHP det minnet. PHP Internals Book sammanfattar det kort: PHP glömmer förfrågningsminnet i slutet av varje förfrågan.
Faserna i PHP Request Lifecycle i överblick
Namnen kommer från PHP:s inre; varje tillägg kan haka in sig i de här faserna.
| Fas | När den körs | Hur ofta | Typisk uppgift |
|---|---|---|---|
| MINIT (Module Init) | när processen startar | en gång per process | registrera tillägg, skapa ini-poster, varaktiga data |
| GINIT | när globalerna skapas | per process, vid trådar per tråd | förbereda ett tilläggs globala variabler |
| RINIT (Request Init) | i början av varje förfrågan | per förfrågan | förbereda förfrågningsminne och förfrågningstillstånd |
| Körning | däremellan | per förfrågan | er PHP-kod körs |
| RSHUTDOWN | i slutet av varje förfrågan | per förfrågan | frigöra förfrågningsresurser |
| PRSHUTDOWN | efter RSHUTDOWN | per förfrågan, sällan använd | sista städarbeten |
| MSHUTDOWN | när processen avslutas | en gång per process | frigöra varaktiga data |
En viktig mening ur PHP Internals Book för tilläggsutvecklare: Globaler töms inte automatiskt efter varje förfrågan. Det som måste vara fräscht per förfrågan hör till att återställas i RINIT. Den regeln visar varför gränsen mellan två förfrågningar är en säkerhetskritisk punkt.
I slutet av en förfrågan är ordningen också fastlagd: Först körs shutdown-funktioner som ni har registrerat med register_shutdown_function(), sedan destruktorer, därefter töms utdatabufferten. Först efter det följer RSHUTDOWN.
Varför workers återanvänds
På kommandoraden hanterar en process exakt en förfrågan och tar slut. I webbdrift vore det för långsamt. PHP-FPM, Apache med mod_php, LiteSpeed och FrankenPHP håller därför workers i beredskap som betjänar många förfrågningar i rad. MINIT körs då en gång, RINIT och RSHUTDOWN tusentals gånger. Hur FPM styr sina workers förklarar PHP-FPM förklarat.
I PHP-FPM anger pm.max_requests efter hur många förfrågningar en worker startar om. Standard är 0, alltså obegränsat. Manualen nämner inställningen som medel mot minnesläckor i tredjepartsbibliotek:
; FPM-pool: starta om workers efter 500 förfrågningar (mot minnesläckor)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500
Ännu längre går FrankenPHP:s worker-läge: Där startar hela applikationen en gång och stannar kvar i minnet. Applikationstillstånd överlever då även på PHP-nivå. Exemplet visar skillnaden:
<?php
function anfrageZaehler(): int
{
static $zaehler = 0; // i klassisk FPM-drift åter 0 per förfrågan
return ++$zaehler; // i worker-läget räknas värdet vidare över förfrågningar
}
echo anfrageZaehler();
I klassisk drift skriver anropet alltid ut 1, eftersom PHP kastar förfrågningsminnet. I worker-läget växer värdet så länge workern lever. FrankenPHP:s dokumentation råder därför att medvetet återställa förfrågningsbundet tillstånd mellan förfrågningar.
Där CVE-2026-109619 sätter in
CVE-2026-109619 gäller inte er applikationskod utan PHP självt. Enligt php.net kunde internt tillstånd som hör till en förfrågan överleva frigörandet av sitt minne. En senare förfrågan i samma worker kunde använda det innan initialiseringen återställde det. I bilden: På luckans bord blev en lapp liggande som egentligen redan var undanröjd. Detaljer om utnyttjandet publicerar vi medvetet inte; bedömningen finns under CVE-2026-109619.
php.nets tillfälliga åtgärder för den här sårbarheten sätter in vid bekräftade vägar, de är inget fullständigt skydd:
session.upload_progress.enabled=0i php.ini, FPM-poolen eller serverkonfigurationen, starta därefter om alla workers; inte viaini_set()eller.user.ini.- På PHP 8.5 dessutom
fatal_error_backtraces=0och omstart. - Ta bort ogiltiga eller föråldrade inställningar per förfrågan som ger meddelanden före initialiseringen. Att bara dölja meddelandena hjälper enligt php.net inte.
- Åtgärda fatala fel och tidsöverskridanden som kan utlösas utifrån i applikationen, även i callbacks, uppladdnings- och importbearbetning.
Den egentliga åtgärden är uppdateringen till PHP 8.5.12, 8.4.27, 8.3.36 eller 8.2.35. Eftersom felet sitter i den körande workern verkar den först efter en omstart av alla processer; stegen står under Starta om PHP-FPM och alla workers.
Kontrollera efter omstarten med ps -eo pid,lstart,cmd | grep php att alla PHP-processer har startats efter uppdateringens tidpunkt.
Vad livscykeln betyder för drift och utveckling
Av modellen går det att härleda tydliga regler. För administratörer: En uppdatering är först verksam när ingen process med gammal kod längre körs. Varje driftläge med långlivade workers behöver en dokumenterad omstart. För utvecklare: Kod som körs i worker-lägen får inte lämna förfrågningsbundet tillstånd i statiska variabler eller singletons, till exempel användardata eller behörigheter. För båda gäller: Spara loggar från och med den 20 oktober 2026 för att senare kunna bedöma om något hände före patchen.
Hur hjälper PHP SOS konkret?
Med PHP-patchtjänsten genomför vi modellen i praktiken:
- Fastställa driftläge: Vi tar reda på om PHP-FPM, mod_php, LiteSpeed, FrankenPHP eller php-cgi körs och hur workers hanteras.
- Uppdatering och omstart: Vi installerar den korrigerade versionen och startar om varje worker kontrollerat.
- Processkontroll: Vi styrker med starttider och versioner att ingen gammal process är kvar.
- Kontrollera worker-lägen: Vid långlivade applikationer tittar vi på om förfrågningsbundet tillstånd återställs rent.
Vanliga frågor
Startar PHP en ny process för varje förfrågan?
På kommandoraden ja, i webbdrift oftast inte. PHP-FPM, Apache med mod_php, LiteSpeed eller FrankenPHP låter en process hantera många förfrågningar efter varandra. Mellan förfrågningarna städar PHP förfrågningstillståndet.
Vad är skillnaden mellan MINIT och RINIT?
MINIT körs en gång när PHP-processen startar och skapar varaktiga strukturer. RINIT körs i början av varje enskild förfrågan och förbereder det som bara den förfrågan behöver.
Hjälper pm.max_requests mot CVE-2026-109619?
php.net nämner inte tätare omstarter av workers som tillfällig åtgärd. Inställningen begränsar bara hur många förfrågningar en worker hanterar innan den startar om. Mot sårbarheten hjälper uppdateringen med efterföljande omstart av alla workers.
Vad är FrankenPHP:s worker-läge?
I worker-läget startar applikationen en gång och stannar kvar i minnet. Det sparar tid, men statiska variabler, globaler och cacher bevaras mellan förfrågningar. Enligt FrankenPHP:s dokumentation måste förfrågningsbundet tillstånd därför medvetet återställas.