# memory_limit и Out-of-Memory: какво се случва отвътре

> PHP Out of Memory, обяснен ясно: как брои memory_limit, какво се случва вътрешно при прекъсване и какво означава това за CVE-2026-109618 и Вашите сървъри.

Източник: https://phpsos.de/bg/php/out-of-memory/  
Актуалност: 25.10.2026  
Доставчик: PHP SOS, Спешна помощ за PHP и сигурност на сървъри. Незабавна помощ по WhatsApp: https://wa.me/491736494914?text=%D0%97%D0%B4%D1%80%D0%B0%D0%B2%D0%B5%D0%B9%D1%82%D0%B5%2C%20PHP%20SOS%2C%20%D0%B8%D0%BC%D0%B0%D0%BC%D0%B5%20%D0%BD%D1%83%D0%B6%D0%B4%D0%B0%20%D0%BE%D1%82%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%20%D1%81%20%D0%BD%D0%B0%D1%88%D0%B8%D1%8F%20PHP%20%D1%81%D1%8A%D1%80%D0%B2%D1%8A%D1%80.%20%D0%A4%D0%B8%D1%80%D0%BC%D0%B0%3A%20%E2%80%A6%20%D0%A3%D0%B5%D0%B1%D1%81%D0%B0%D0%B9%D1%82%3A%20%E2%80%A6

**Накратко:** Грешка PHP Out-of-Memory възниква, когато скрипт поиска повече памет, отколкото позволява настройката `memory_limit` (по подразбиране: 128 MB за заявка). PHP тогава прекратява заявката с фатална грешка и почиства. Точно този път на прекъсване и почистване е бил дефектен при CVE-2026-109618, една от двете уязвимости PHP2Shell. От PHP 8.5.12, 8.4.27, 8.3.36 и 8.2.35 `memory_limit` е мека граница, допълнена от твърдата граница `memory_hard_limit_percent`.

## Какво означава „Allowed memory size exhausted“?

Известното съобщение за грешка гласи по смисъл: „Allowed memory size of 134217728 bytes exhausted (tried to allocate …)“. Първото число е зададеното ограничение в байтове, тук 128 MB. Скриптът следователно е опитал да заеме памет над тази граница, например при зареждане на голям файл, огромна заявка към база данни или безкраен цикъл.

Важно за вземащите решения: границата важи за всеки PHP процес и за всяка заявка, а не за целия сървър. Тя предпазва сървъра от това един-единствен дефектен скрипт да изразходва цялата памет. Общото натоварване се получава едва от броя на едновременно работещите worker процеси. При 20 PHP-FPM worker процеса с граница по 256 MB това са изчислително над 5 GB.

## Как PHP брои паметта

PHP управлява паметта за заявките чрез собствен мениджър на паметта, Zend Memory Manager. Той взема по-големи блокове от операционната система и ги разпределя вътрешно на променливи, масиви и обекти. Спрямо тази резервирана стойност PHP проверява `memory_limit`. Памет, която едно разширение заявява директно от операционната система, според ръководството не се появява в това отчитане.

Стойностите могат да се покажат в кода:

```php
<?php
echo memory_get_usage(), PHP_EOL;       // заетата в момента от скрипта памет
echo memory_get_usage(true), PHP_EOL;   // резервирана от мениджъра на паметта; спрямо нея проверява memory_limit
$liste = range(1, 200000);
echo memory_get_peak_usage(), PHP_EOL;  // досегашната пикова стойност
unset($liste);
memory_reset_peak_usage();              // нулиране на пиковата стойност, от PHP 8.2
```

Как броенето на препратки и garbage collection освобождават отново паметта, обяснява страницата [Управление на паметта в PHP](https://phpsos.de/bg/php/speicherverwaltung/).

## Какво се случва вътре при Out-of-Memory

Опростено, прекъсването поради памет протича в тези стъпки:

1. Скриптът заявява памет, например за нов елемент на масив или при четене на данните от заявката.
2. Мениджърът на паметта проверява дали заявката надхвърля границата.
3. Ако е така, PHP предизвиква фатална грешка и напуска незабавно текущата обработка, дори по средата на наполовина свършена задача.
4. Регистрираните shutdown функции още се изпълняват; с тях много приложения записват грешката в лог.
5. Накрая PHP почиства заявката и освобождава нейната памет. Worker процесът след това е готов за следващата заявка.

Деликатната точка е стъпка 5. Почистването трябва да се справя със структури от данни, които при прекъсването са били създадени само наполовина. Обикновеното прекъсване поради памет според политиката за сигурност на проекта PHP не се счита за уязвимост, докато ограничението отхвърля заявката и приключва само текущата заявка. При CVE-2026-109618 именно това допускане е било нарушено: според php.net състоянието на недостиг на памет е могло да прекъсне PHP, докато вътрешните данни от заявката са били само частично инициализирани, и почистването тогава е третирало невалидни данни като валидни. Подробности за експлоатацията не публикуваме; оценката на уязвимостта е на страницата [CVE-2026-109618](https://phpsos.de/bg/php2shell/cve-2026-109618/).

## Новата мека и твърда граница на паметта

С изданията за сигурност от 21.10.2026 г. проектът PHP промени поведението. Според ръководството оттогава `memory_limit` е мека граница: заявка, която я надхвърля, в повечето случаи може още да завърши, преди да последва фаталната грешка. Отделна твърда граница ограничава докъде може да стигне това.

| Директива | По подразбиране | Променяема | Значение |
|---|---|---|---|
| `memory_limit` | 128M | INI_ALL | граница на паметта за заявка, `-1` = без граница |
| `memory_hard_limit_percent` | не е зададена | INI_ALL | твърда граница в процент от `memory_limit`, ново от 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
| `post_max_size` | 8M | INI_PERDIR | максимален размер на POST данните |
| `upload_max_filesize` | 2M | INI_PERDIR | максимален размер на качен файл |

Пример от ръководството: при `memory_limit=128M` и `memory_hard_limit_percent=150` твърдата граница е 192 MB. Ръководството същевременно посочва директивата като отличителен белег: ако една версия на PHP не я познава, тя е засегната от CVE-2026-109618 и CVE-2026-109619. Бърз тест за всяка инсталирана версия на PHP:

```bash
php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
# bool(false) означава: директивата е непозната
```

При пакети от дистрибуции с backport решаващо доказателство остава changelog на пакета, както е описано в [Правилна проверка на версията на PHP и състоянието на пачовете](https://phpsos.de/bg/php2shell/php-version-pruefen/). Проверете освен това отделно двоичния файл на PHP-FPM, защото той може да е с друга версия от командния ред.

## Граници на паметта и PHP2Shell: на какво да внимават администраторите

Временните мерки на php.net за CVE-2026-109618 почти всички се въртят около паметта и размерите на заявките. Те намаляват риска, но не заменят актуализацията:

```ini
; FPM пул, напр. /etc/php/8.3/fpm/pool.d/www.conf
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M        ; 32M, ако са нужни качвания
php_admin_value[upload_max_filesize] = 6M  ; да се държи под post_max_size
```

Три точки са решаващи. Първо, никое приложение, никоя директория и никой друг пул не бива отново да задава крайна граница; php.net изрично изисква този контрол. Второ, услуга с `memory_limit=-1` се нуждае от външна граница, чрез контейнер или cgroup, според php.net най-добре така, че да се прекратява само засегнатият worker. `RLIMIT_AS` и `RLIMIT_DATA` според php.net са неподходящи. Трето, php.net препоръчва граници на уеб сървъра или прокси за размера на заявката, броя на полетата, multipart частите, хедърите и дълбочината на влагане.

> **Важно:**
> `memory_limit=-1` без външна граница на паметта може при високо натоварване да блокира напълно един сървър. Прилагайте временните мерки само като пакет и само до актуализацията.

Как се задава границата със systemd, е описано в [Граници на паметта със systemd и cgroups](https://phpsos.de/bg/blog/speichergrenzen-systemd-cgroups/); трайното решение за ежедневието описва [Правилно задаване на memory_limit](https://phpsos.de/bg/blog/memory-limit-richtig-setzen/).

## Как помага PHP SOS конкретно?

Чрез [услугата за PHP пачове](https://phpsos.de/bg/leistungen/php-patch-service/) действаме така:

- **Проверка на наличното:** Отчитаме всяка версия на PHP на сървъра, поотделно за CLI, FPM и модул на Apache, и тестваме дали работи коригираната версия.
- **Подреждане на границите на паметта:** Проверяваме къде навсякъде се задава `memory_limit` и настройваме границите на пула и на системата така, че да си пасват.
- **Осигуряване на преходния период:** Където актуализацията още е блокирана, прилагаме мерките на php.net изцяло и заключени срещу презаписване.
- **Предоставяне на доказателство:** Получавате писмено версиите, състоянията на пакетите и конфигурацията преди и след намесата.


## Често задавани въпроси

**Какво означава съобщението „Allowed memory size exhausted“?**  
Скрипт е поискал повече памет, отколкото позволява memory_limit. PHP тогава прекратява текущата заявка с фатална грешка. Останалите заявки и самият сървър продължават да работят.

**memory_limit важи ли за целия сървър?**  
Не, границата важи за всеки PHP процес и всяка заявка. Ако работят 20 FPM worker процеса с граница по 256 MB, теоретично заедно могат да заемат над 5 GB. Затова общата граница трябва да се зададе чрез размера на пула, systemd или контейнер.

**Какво е memory_hard_limit_percent?**  
Нова директива, появила се с PHP 8.5.12, 8.4.27, 8.3.36 и 8.2.35. Тя задава твърда граница като процент от memory_limit, която никога не се надхвърля. Оттогава самият memory_limit е мека граница.

**memory_limit=-1 защитава ли от PHP2Shell?**  
Само отчасти. php.net посочва настройката като временна мярка срещу CVE-2026-109618, изрично без пълна защита. Тя задължително изисква външна граница на паметта, а актуализацията остава задължителна.

## Източници

- [Ръководство на PHP: Основни настройки (memory_limit, memory_hard_limit_percent)](https://www.php.net/manual/en/ini.core.php)
- [Ръководство на PHP: memory_get_usage()](https://www.php.net/manual/en/function.memory-get-usage.php)
- [php.net: Съобщения за сигурност от 21.10.2026 г.](https://www.php.net/)
