Kort gezegd: PHP geeft geheugen vooral vrij via referentietelling: elke waarde weet hoeveel variabelen ernaar wijzen en wordt gewist zodra die teller nul bereikt. Voor kringverwijzingen, die op die manier nooit zouden worden vrijgegeven, is er een cycli-garbage-collector. Aan het einde van elk verzoek ruimt PHP bovendien alles op wat het verzoek in beslag heeft genomen. Beide PHP2Shell-lekken grijpen aan op geheugentoestanden: op een out-of-memory-toestand tijdens de verwerking van het verzoek en op interne toestand die na het vrijgeven te lang blijft bestaan.
Hoe PHP geheugen beheert: het overzicht
In talen als C moet de programmeur zelf geheugen aanvragen en weer vrijgeven. PHP neemt dat uit handen. Elke variabele, elke array en elk object neemt geheugen in beslag dat PHP via een eigen geheugenmanager beheert. De grens daarvoor stelt memory_limit, volgens de handleiding standaard 128M.
Voor beslissers is één punt centraal: bij klassiek bedrijf met PHP-FPM of mod_php draaien langlevende workerprocessen die achter elkaar veel verzoeken afhandelen. Wat een verzoek achterlaat, moet vóór het volgende betrouwbaar zijn opgeruimd. Precies op die plek zit een van de PHP2Shell-lekken.
Referentietelling: tellen wie een waarde gebruikt
PHP slaat elke waarde op in een container, de zogeheten zval. Naast type en inhoud bevat hij volgens de handleiding een referentieteller (refcount) en een markering of de waarde deel uitmaakt van een PHP-referentie met & (is_ref).
<?php
$a = str_repeat('x', 1000); // een waarde, refcount 1
$b = $a; // geen kopie, refcount 2
$b .= 'y'; // pas nu wordt gekopieerd (copy-on-write)
unset($a); // refcount van het origineel daalt
// Bereikt een teller 0, dan geeft PHP het geheugen direct vrij.
Het principe heet copy-on-write: PHP kopieert een waarde pas wanneer een van de variabelen hem wijzigt. Dat bespaart veel geheugen. De teller van een variabele kan men zichtbaar maken met de extensie Xdebug en de functie xdebug_debug_zval(). De handleiding wijst erop dat de exacte getallen per PHP-versie kunnen afwijken.
Cycli: wanneer referentietelling niet volstaat
Referentietelling heeft een bekende zwakte. Wijst een structuur naar zichzelf, dan daalt haar teller nooit naar nul.
<?php
$a = ['eins'];
$a[] = &$a; // de array bevat een referentie naar zichzelf
unset($a); // niemand heeft er nog toegang toe, maar refcount blijft 1
Volgens de handleiding komen zulke kringverwijzingen vooral bij objecten voor, bijvoorbeeld in ouder-kindstructuren of parsers. Afzonderlijke lekken zijn onschuldig, omdat PHP aan het einde van het verzoek opruimt. In langlopende processen zoals wachtrij-workers of grote testruns kunnen er miljoenen van bij elkaar opgeteld echter een echt geheugenprobleem worden.
De cycli-garbage-collector
Sinds PHP 5.3.0 gebruikt PHP een algoritme van Bacon en Rajan om zulke cycli te vinden. Het basisidee: een cyclus kan alleen ontstaan als een teller daalt naar een waarde groter dan nul. Alleen die waarden onthoudt PHP als mogelijke wortels.
- Mogelijke wortels komen in een buffer terecht. De handleiding noemt als standaardgrootte 10.000 items; de actueel geldende drempel toont
gc_status()in het veldthreshold. - Is de buffer vol, dan doorloopt PHP alle bereikbare waarden en verlaagt proefsgewijs hun tellers.
- In een tweede ronde geldt: waarden met teller nul zijn afval, bij alle andere wordt de verlaging ongedaan gemaakt.
- De wortels worden uit de buffer verwijderd en alle als afval herkende waarden worden vrijgegeven.
Het geheel wordt gestuurd via zend.enable_gc (standaard aan) of tijdens de uitvoering met gc_enable() en gc_disable(). gc_collect_cycles() forceert een ronde en geeft het aantal verzamelde cycli terug.
Geheugen meten en observeren
| Functie | Doel |
|---|---|
memory_get_usage() |
momenteel door het script in beslag genomen geheugen |
memory_get_usage(true) |
door de geheugenmanager bij het systeem gereserveerd geheugen; aan deze waarde wordt memory_limit getoetst |
memory_get_peak_usage() |
hoogste stand van het in beslag genomen geheugen |
gc_status() |
rondes, verzamelde cycli, drempelwaarde, wortels; sinds PHP 8.3.0 ook looptijden |
gc_collect_cycles() |
cyclusverzameling direct starten |
WeakMap |
koppeling aan objecten zonder hun vrijgave te verhinderen, sinds PHP 8.0 |
Volgens de handleiding registreert memory_get_usage() alleen geheugen dat PHP via de eigen geheugenmanager aanvraagt. Wat extensies rechtstreeks bij het besturingssysteem ophalen, ontbreekt in deze cijfers. Voor de geheugengrens van het hele proces zijn daarom het besturingssysteem of de container verantwoordelijk.
Geheugenbeheer en PHP2Shell
De beschrijvingen van beide lekken leiden rechtstreeks naar dit onderwerp. Bij CVE-2026-109618 kan een out-of-memory-toestand PHP onderbreken terwijl verzoekgegevens maar gedeeltelijk zijn geïnitialiseerd; bij het opruimen kunnen ongeldige gegevens dan als geldig worden behandeld. Wat er bij een geheugenoverschrijding intern gebeurt, legt de pagina memory_limit en out-of-memory uit. Bij CVE-2026-109619 kan interne toestand van een verzoek na het vrijgeven van het geheugen blijven bestaan en door een later verzoek in dezelfde worker worden gebruikt voordat hij wordt teruggezet. Het verloop van start tot opruimen beschrijft de pagina De levenscyclus van een verzoek.
Als overgangsmaatregel adviseert php.net memory_limit=-1 zo in te stellen dat toepassingen het niet kunnen overschrijven, bijvoorbeeld met php_admin_value in de FPM-pool, en het geheugen in plaats daarvan via containers of cgroups te begrenzen, niet via RLIMIT_AS of RLIMIT_DATA. Hoe dat met systemd kan, toont het artikel Geheugengrenzen instellen met systemd en cgroups.
; FPM-pool: overgangsmaatregel volgens php.net, daarna alle workers opnieuw starten
php_admin_value[memory_limit] = -1
Zonder grens via container of cgroup kan een enkel proces de server volledig belasten. En geen enkele instelling vervangt de update naar PHP 8.5.12, 8.4.27, 8.3.36 of 8.2.35 inclusief herstart van alle workers, want alleen de herstart haalt oude processen uit het geheugen.
Hoe helpt PHP SOS concreet?
- Wij controleren de werkzame geheugeninstellingen per pool en of toepassingen ze kunnen overschrijven.
- Wij voeren de overgangsmaatregelen door met een geheugengrens op het niveau van container of cgroup en testen de belasting.
- Wij voeren de update door, starten alle workers opnieuw en tonen aan dat er geen oude processen meer draaien, in het kader van onze PHP-patchservice.
- Wij beoordelen geheugenfouten en afgebroken workers in de logs vanaf 20 oktober 2026.
Veelgestelde vragen
Wat is referentietelling in PHP?
PHP telt voor elke waarde hoeveel variabelen of elementen ernaar verwijzen. Daalt de teller naar nul, dan wordt het geheugen direct vrijgegeven. Zo wordt het meeste geheugen teruggegeven zonder aparte opruimronde.
Waarom heeft PHP dan nog een garbage collector nodig?
Voor cycli. Verwijst een array naar zichzelf of kennen twee objecten elkaar wederzijds, dan daalt de teller nooit naar nul, ook al heeft niemand er nog toegang toe. De cycli-garbage-collector vindt en verwijdert zulke structuren.
Geeft PHP het geheugen aan het einde van een verzoek vrij?
Ja, geheugen dat een script tijdens een verzoek in beslag neemt, ruimt PHP aan het einde van het verzoek op. Bij PHP2Shell betreft CVE-2026-109619 echter juist het geval dat interne toestand na het vrijgeven blijft bestaan en door een later verzoek in dezelfde worker kan worden gebruikt.
Waarom adviseert php.net memory_limit=-1 als overgangsmaatregel?
Het lek CVE-2026-109618 hangt samen met een out-of-memory-toestand. php.net adviseert memory_limit=-1 zo in te stellen dat toepassingen het niet kunnen overschrijven, en het geheugen in plaats daarvan via containers of cgroups te begrenzen. Dat is geen volledige bescherming en vervangt de update niet.