In breve: un errore PHP di Out-of-Memory si verifica quando uno script vuole richiedere più memoria di quanta ne consenta l'impostazione memory_limit (predefinito: 128 MB per richiesta). PHP interrompe allora la richiesta con un errore fatale e ripulisce. Proprio questo percorso di interruzione e pulizia era difettoso in CVE-2026-109618, una delle due vulnerabilità PHP2Shell. Da PHP 8.5.12, 8.4.27, 8.3.36 e 8.2.35 memory_limit è un limite flessibile, integrato dal limite rigido memory_hard_limit_percent.
Che cosa significa «Allowed memory size exhausted»?
Il noto messaggio di errore suona più o meno così: «Allowed memory size of 134217728 bytes exhausted (tried to allocate …)». Il primo numero è il limite impostato in byte, qui 128 MB. Uno script ha quindi tentato di occupare memoria oltre questo limite, ad esempio caricando un file grande, con una query enorme al database o per un ciclo infinito.
Per chi decide è importante: il limite vale per ogni processo PHP e per ogni richiesta, non per l'intero server. Protegge il server dal fatto che un singolo script difettoso consumi tutta la memoria. Il carico complessivo risulta solo dal numero di worker in esecuzione contemporaneamente. Con 20 worker PHP-FPM con un limite di 256 MB ciascuno sono, a conti fatti, oltre 5 GB.
Come PHP conta la memoria
PHP gestisce la memoria per le richieste tramite un proprio gestore, lo Zend Memory Manager. Preleva blocchi più grandi dal sistema operativo e li distribuisce internamente a variabili, array e oggetti. È rispetto a questo valore riservato che PHP verifica memory_limit. La memoria che un'estensione richiede direttamente al sistema operativo, secondo il manuale, non compare in questo conteggio.
Può rendere visibili i valori nel codice:
<?php
echo memory_get_usage(), PHP_EOL; // memoria attualmente occupata dallo script
echo memory_get_usage(true), PHP_EOL; // riservata dal gestore della memoria; memory_limit verifica rispetto a questa
$liste = range(1, 200000);
echo memory_get_peak_usage(), PHP_EOL; // picco finora raggiunto
unset($liste);
memory_reset_peak_usage(); // azzerare il picco, da PHP 8.2
Come conteggio dei riferimenti e garbage collection liberino di nuovo la memoria lo spiega la pagina Gestione della memoria in PHP.
Che cosa accade all'interno in caso di Out-of-Memory
In forma semplificata, un'interruzione per memoria si svolge in questi passaggi:
- Lo script richiede memoria, ad esempio per un nuovo elemento di array o durante la lettura dei dati della richiesta.
- Il gestore della memoria verifica se la richiesta supera il limite.
- Se è così, PHP genera un errore fatale e abbandona subito l'elaborazione in corso, anche nel mezzo di un compito eseguito a metà.
- Le funzioni di shutdown registrate vengono ancora eseguite; con esse molte applicazioni registrano l'errore nel log.
- Alla fine PHP ripulisce la richiesta e libera la sua memoria. Il worker è poi pronto per la richiesta successiva.
Il punto delicato è il passaggio 5. La pulizia deve saper gestire strutture di dati che al momento dell'interruzione erano state create solo a metà. Secondo la politica di sicurezza del progetto PHP, una normale interruzione per memoria non è considerata una vulnerabilità di sicurezza, finché il limite respinge la richiesta e termina solo la richiesta corrente. In CVE-2026-109618 proprio questo presupposto era violato: secondo php.net lo stato di Out-of-Memory poteva interrompere PHP mentre i dati interni della richiesta erano inizializzati solo in parte, e la pulizia trattava allora dati non validi come validi. Non pubblichiamo dettagli sullo sfruttamento; l'inquadramento della vulnerabilità si trova in CVE-2026-109618.
Il nuovo limite di memoria flessibile e rigido
Con i rilasci di sicurezza del 21/10/2026 il progetto PHP ha cambiato il comportamento. Secondo il manuale, da allora memory_limit è un limite flessibile: una richiesta che lo supera può, nella maggior parte dei casi, essere ancora completata prima che segua l'errore fatale. Un limite rigido separato stabilisce fin dove ciò possa spingersi.
| Direttiva | Predefinito | Modificabile | Significato |
|---|---|---|---|
memory_limit |
128M | INI_ALL | Limite di memoria per richiesta, -1 = nessun limite |
memory_hard_limit_percent |
non impostata | INI_ALL | Limite rigido in percentuale di memory_limit, nuova da 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
post_max_size |
8M | INI_PERDIR | Dimensione massima dei dati POST |
upload_max_filesize |
2M | INI_PERDIR | Dimensione massima di un file caricato |
Un esempio dal manuale: con memory_limit=128M e memory_hard_limit_percent=150 il limite rigido è di 192 MB. Il manuale indica la direttiva anche come segno di riconoscimento: se una versione di PHP non la conosce, è interessata da CVE-2026-109618 e CVE-2026-109619. Un test rapido per ogni versione di PHP installata:
php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
# bool(false) significa: direttiva sconosciuta
Per i pacchetti delle distribuzioni con backport, il changelog del pacchetto resta la prova determinante, come descritto in Verificare correttamente la versione di PHP e lo stato delle patch. Verifichi inoltre separatamente il binario PHP-FPM, perché può avere una versione diversa dalla riga di comando.
Limiti di memoria e PHP2Shell: a che cosa devono fare attenzione gli amministratori
Le misure transitorie di php.net per CVE-2026-109618 ruotano quasi tutte attorno alla memoria e alle dimensioni delle richieste. Riducono il rischio, ma non sostituiscono l'aggiornamento:
; Pool FPM, ad es. /etc/php/8.3/fpm/pool.d/www.conf
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M ; 32M, se servono gli upload
php_admin_value[upload_max_filesize] = 6M ; tenere sotto post_max_size
Tre punti sono decisivi. Primo: nessuna applicazione, nessuna directory e nessun altro pool deve poter impostare di nuovo un limite finito; php.net richiede espressamente questo controllo. Secondo: un servizio con memory_limit=-1 ha bisogno di un limite esterno, tramite container o cgroup, secondo php.net preferibilmente in modo che venga terminato solo il worker interessato. RLIMIT_AS e RLIMIT_DATA sono, secondo php.net, inadatti. Terzo: php.net raccomanda limiti sul server web o sul proxy per dimensione della richiesta, numero di campi, parti multipart, header e profondità di annidamento.
memory_limit=-1 senza un limite di memoria esterno può paralizzare completamente un server in caso di carico elevato. Imposti le misure transitorie solo come pacchetto e solo fino all'aggiornamento.
Come impostare il limite con systemd è descritto in Limiti di memoria con systemd e cgroup; la soluzione duratura per il quotidiano la descrive Impostare correttamente memory_limit.
Come aiuta concretamente PHP SOS?
Con il servizio di patch PHP procediamo così:
- Verifica dell'esistente: rileviamo ogni versione di PHP sul server, distinta per CLI, FPM e modulo Apache, e verifichiamo se gira la versione corretta.
- Ordinare i limiti di memoria: controlliamo dove venga impostato
memory_limite configuriamo i limiti di pool e di sistema in modo che siano coerenti tra loro. - Mettere in sicurezza la transizione: dove l'aggiornamento è ancora bloccato, attuiamo per intero le misure di php.net, bloccate contro la sovrascrittura.
- Fornire la prova: riceve per iscritto versioni, stati dei pacchetti e configurazione prima e dopo l'intervento.
Domande frequenti
Che cosa significa il messaggio «Allowed memory size exhausted»?
Uno script ha voluto richiedere più memoria di quanta ne consenta memory_limit. PHP interrompe allora la richiesta in corso con un errore fatale. Le altre richieste e il server stesso continuano a funzionare.
memory_limit vale per l'intero server?
No, il limite vale per ogni processo PHP e per ogni richiesta. Se girano 20 worker FPM con un limite di 256 MB ciascuno, insieme possono teoricamente occupare oltre 5 GB. Il limite complessivo va quindi impostato tramite la dimensione del pool, systemd o i container.
Che cos'è memory_hard_limit_percent?
Una nuova direttiva arrivata con PHP 8.5.12, 8.4.27, 8.3.36 e 8.2.35. Imposta un limite rigido, come percentuale di memory_limit, che non viene mai superato. Da allora memory_limit stesso è un limite flessibile.
memory_limit=-1 protegge da PHP2Shell?
Solo in parte. php.net indica l'impostazione come misura transitoria contro CVE-2026-109618, espressamente senza protezione completa. Richiede obbligatoriamente un limite di memoria esterno, e l'aggiornamento resta obbligatorio.