Krótko: Błąd PHP Out-of-Memory powstaje, gdy skrypt chce zająć więcej pamięci, niż pozwala ustawienie memory_limit (domyślnie 128 MB na żądanie). PHP przerywa wtedy żądanie błędem krytycznym i sprząta. Właśnie ta droga przerwania i sprzątania była błędna w CVE-2026-109618, jednej z dwóch luk PHP2Shell. Od PHP 8.5.12, 8.4.27, 8.3.36 i 8.2.35 memory_limit jest limitem miękkim, uzupełnionym o limit twardy memory_hard_limit_percent.
Co oznacza „Allowed memory size exhausted”?
Znany komunikat błędu brzmi w przybliżeniu: „Allowed memory size of 134217728 bytes exhausted (tried to allocate …)”. Pierwsza liczba to ustawiony limit w bajtach, tu 128 MB. Skrypt próbował więc zająć pamięć ponad tę granicę, na przykład przy wczytywaniu dużego pliku, ogromnego zapytania do bazy danych albo w pętli nieskończonej.
Dla osób podejmujących decyzje ważne jest to, że limit obowiązuje dla każdego procesu PHP i każdego żądania, a nie dla całego serwera. Chroni serwer przed tym, by pojedynczy błędny skrypt zużył całą pamięć. Całkowite obciążenie wynika dopiero z liczby jednocześnie działających workerów. Przy 20 workerach PHP-FPM z limitem 256 MB każdy daje to rachunkowo ponad 5 GB.
Jak PHP liczy pamięć
PHP zarządza pamięcią dla żądań przez własnego menedżera pamięci, Zend Memory Manager. Pobiera on większe bloki od systemu operacyjnego i rozdziela je wewnętrznie na zmienne, tablice i obiekty. Względem tej zarezerwowanej wartości PHP sprawdza memory_limit. Pamięć, którą rozszerzenie zamawia bezpośrednio od systemu operacyjnego, według podręcznika w tym liczeniu nie występuje.
Wartości można uwidocznić w kodzie:
<?php
echo memory_get_usage(), PHP_EOL; // pamięć aktualnie zajęta przez skrypt
echo memory_get_usage(true), PHP_EOL; // zarezerwowana przez menedżera pamięci; względem tego sprawdza memory_limit
$liste = range(1, 200000);
echo memory_get_peak_usage(), PHP_EOL; // dotychczasowa wartość szczytowa
unset($liste);
memory_reset_peak_usage(); // reset wartości szczytowej, od PHP 8.2
Jak zliczanie referencji i garbage collection ponownie zwalniają pamięć, wyjaśnia strona Zarządzanie pamięcią w PHP.
Co dzieje się w środku przy Out-of-Memory
W uproszczeniu przerwanie z powodu pamięci przebiega w tych krokach:
- Skrypt zamawia pamięć, na przykład na nowy element tablicy lub przy wczytywaniu danych żądania.
- Menedżer pamięci sprawdza, czy zamówienie przekracza limit.
- Jeśli tak, PHP wywołuje błąd krytyczny i natychmiast opuszcza bieżące przetwarzanie, także w połowie niedokończonego zadania.
- Zarejestrowane funkcje shutdown jeszcze się wykonują; wiele aplikacji zapisuje w ten sposób błąd w logu.
- Na końcu PHP sprząta żądanie i zwalnia jego pamięć. Worker jest potem gotowy na następne żądanie.
Newralgicznym punktem jest krok 5. Sprzątanie musi radzić sobie ze strukturami danych, które w chwili przerwania były założone dopiero w połowie. Zwykłe przerwanie z powodu pamięci nie jest według polityki bezpieczeństwa projektu PHP luką bezpieczeństwa, dopóki limit odrzuca zamówienie i kończy się tylko bieżące żądanie. W CVE-2026-109618 to założenie zostało naruszone: według php.net stan Out-of-Memory mógł przerwać PHP, gdy wewnętrzne dane żądania były zainicjalizowane tylko częściowo, a sprzątanie traktowało wtedy nieprawidłowe dane jako prawidłowe. Szczegółów wykorzystania nie publikujemy; ocena luki znajduje się pod hasłem CVE-2026-109618.
Nowy limit miękki i twardy
Wraz z wydaniami bezpieczeństwa z 21.10.2026 projekt PHP zmienił zachowanie. Według podręcznika memory_limit jest od tego czasu limitem miękkim: zamówienie, które go przekracza, może w większości przypadków jeszcze zostać zakończone, zanim nastąpi błąd krytyczny. Osobny limit twardy ogranicza, jak daleko może to zajść.
| Dyrektywa | Domyślnie | Zmienialna | Znaczenie |
|---|---|---|---|
memory_limit |
128M | INI_ALL | limit pamięci na żądanie, -1 = brak limitu |
memory_hard_limit_percent |
nieustawiona | INI_ALL | limit twardy w procentach memory_limit, nowość od 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
post_max_size |
8M | INI_PERDIR | maksymalny rozmiar danych POST |
upload_max_filesize |
2M | INI_PERDIR | maksymalny rozmiar przesłanego pliku |
Przykład z podręcznika: przy memory_limit=128M i memory_hard_limit_percent=150 limit twardy wynosi 192 MB. Podręcznik podaje tę dyrektywę jednocześnie jako cechę rozpoznawczą: jeśli wersja PHP jej nie zna, jest dotknięta przez CVE-2026-109618 i CVE-2026-109619. Szybki test dla każdej zainstalowanej wersji PHP:
php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
# bool(false) oznacza: dyrektywa nieznana
W przypadku pakietów z dystrybucji z backportem rozstrzygającym dowodem pozostaje changelog pakietu, jak opisano w artykule Prawidłowe sprawdzanie wersji PHP i stanu poprawek. Proszę ponadto osobno sprawdzić binarkę PHP-FPM, ponieważ może mieć inną wersję niż wiersz poleceń.
Limity pamięci a PHP2Shell: na co administratorzy zwracają teraz uwagę
Środki przejściowe php.net dotyczące CVE-2026-109618 niemal wszystkie dotyczą pamięci i rozmiarów żądań. Zmniejszają ryzyko, ale nie zastępują aktualizacji:
; pula FPM, np. /etc/php/8.3/fpm/pool.d/www.conf
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M ; 32M, jeśli potrzebne są przesyłane pliki
php_admin_value[upload_max_filesize] = 6M ; utrzymywać poniżej post_max_size
Decydujące są tu trzy punkty. Po pierwsze, żadna aplikacja, żaden katalog i żadna inna pula nie mogą ponownie ustawić skończonego limitu; php.net wyraźnie wymaga tej kontroli. Po drugie, usługa z memory_limit=-1 potrzebuje limitu z zewnątrz, przez kontener lub cgroup, według php.net najlepiej tak, aby kończony był tylko dotknięty worker. RLIMIT_AS i RLIMIT_DATA są według php.net nieodpowiednie. Po trzecie, php.net zaleca limity na serwerze WWW lub proxy dla rozmiaru żądania, liczby pól, części multipart, nagłówków i głębokości zagnieżdżenia.
memory_limit=-1 bez limitu pamięci z zewnątrz może przy dużym obciążeniu całkowicie sparaliżować serwer. Proszę wprowadzać środki przejściowe tylko jako pakiet i tylko do czasu aktualizacji.
Jak ustawić limit za pomocą systemd, opisuje artykuł Limity pamięci z systemd i cgroups; rozwiązanie trwałe na co dzień opisuje Prawidłowe ustawianie memory_limit.
Jak konkretnie pomaga PHP SOS?
W ramach usługi poprawek PHP postępujemy tak:
- Sprawdzenie stanu: Rejestrujemy każdą wersję PHP na serwerze, osobno dla CLI, FPM i modułu Apache, i testujemy, czy działa wersja z poprawką.
- Uporządkowanie limitów pamięci: Sprawdzamy, gdzie wszędzie ustawiany jest
memory_limit, i konfigurujemy limity puli oraz systemu tak, aby do siebie pasowały. - Zabezpieczenie okresu przejściowego: Tam, gdzie aktualizacja jest jeszcze zablokowana, wprowadzamy środki php.net w całości i zablokowane przed nadpisaniem.
- Dostarczenie dowodu: Otrzymują Państwo na piśmie wersje, stany pakietów i konfigurację przed ingerencją i po niej.
Częste pytania
Co oznacza komunikat „Allowed memory size exhausted”?
Skrypt chciał zająć więcej pamięci, niż pozwala memory_limit. PHP przerywa wtedy bieżące żądanie błędem krytycznym. Inne żądania i sam serwer działają dalej.
Czy memory_limit obowiązuje dla całego serwera?
Nie, limit obowiązuje dla każdego procesu PHP i żądania. Jeśli działa 20 workerów FPM z limitem 256 MB każdy, teoretycznie mogą razem zająć ponad 5 GB. Limit całkowity trzeba więc ustawić przez rozmiar puli, systemd lub kontener.
Czym jest memory_hard_limit_percent?
To nowa dyrektywa, która pojawiła się w PHP 8.5.12, 8.4.27, 8.3.36 i 8.2.35. Ustawia twardy limit jako wartość procentową memory_limit, który nigdy nie jest przekraczany. Sam memory_limit jest od tego czasu limitem miękkim.
Czy memory_limit=-1 chroni przed PHP2Shell?
Tylko częściowo. php.net podaje to ustawienie jako środek przejściowy przeciw CVE-2026-109618, wyraźnie bez pełnej ochrony. Wymaga ono koniecznie limitu pamięci z zewnątrz, a aktualizacja pozostaje obowiązkowa.