Dabartinė padėtis: PHP2Shell. Du kritiniai PHP pažeidžiamumai (CVSS 9,2) leidžia vykdyti kenkėjišką kodą be prisijungimo. Pataisos nuo 2026-10-21.Ką dabar daryti →

Atminties ribų nustatymas su systemd ir cgroups

php.net kaip PHP2Shell pereinamąją priemonę rekomenduoja memory_limit=-1 ir atminties ribą už PHP ribų. Sužinokite, kaip šią ribą nustatyti su systemd ir cgroups, kad vienas darbinis procesas nenutemptų visos paslaugos.

Būsena 5 min. skaitymoSutvirtinimas ir konfigūracija

Trumpai: PHP2Shell pereinamajai priemonei memory_limit=-1 PHP-FPM reikia atminties ribos už PHP ribų. Su systemd ją nustatote naudodami MemoryHigh= kaip stabdį ir MemoryMax= kaip kietą viršutinę ribą FPM paslaugos papildymo faile. Pasak php.net, ulimit, LimitAS= ar LimitDATA= tam netinka. Be to, patikrinkite OOMPolicy=, kad vienas darbinis procesas (worker) nenutrauktų visos paslaugos.

Rekomendacija, kuri iš pradžių stebina

Tarp pereinamųjų priemonių, kurias php.net paskelbė 2026 m. spalio 21 d. dėl PHP2Shell, yra viena, privertusi daugelį administratorių sustoti: memory_limit=-1, taigi jokios atminties ribos pačiame PHP. Daug metų gera praktika buvo laikoma priešingai.

Ryšys su pažeidžiamumo aprašymu akivaizdus. CVE-2026-109618 atveju atminties trūkumo (Out-of-Memory) būsena gali nutraukti PHP, kol užklausos duomenys inicializuoti tik iš dalies. Pereinamoji priemonė atminties ribą iš PHP perkelia į operacinę sistemą. php.net ją aiškiai sieja su dviem sąlygomis: reikšmė turi būti užrakinta nuo perrašymo, o riba turi ateiti per konteinerį ar cgroup, o ne per RLIMIT_AS ar RLIMIT_DATA.

Kas įgyvendina tik pirmąją pusę, turi serverį be jokios atminties ribos. Šis straipsnis parodo antrąją pusę. Net ir kartu priemonė lieka pereinamoji: spraga ištaisyta tik su PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 arba patvirtintu atgaliniu pataisų perkėlimu (backport).

cgroups dviem sakiniais

Valdymo grupės (Control Groups), trumpai cgroups, yra Linux branduolio funkcija, kuri grupuoja procesus ir riboja jų resursus. systemd kiekvienai paslaugai automatiškai sukuria savo cgroup, ir visi paslaugos procesai, FPM paslaugos atveju master ir visi darbiniai procesai, jai priklauso.

systemd atminties parinktys nustato cgroup v2 atminties valdiklio reikšmes. Ar jūsų sistema naudoja cgroup v2, parodo trumpa komanda:

# "cgroup2fs" reiškia: cgroup v2 aktyvus
stat -fc %T /sys/fs/cgroup/

Dabartinės populiarių distribucijų versijos numatytai naudoja cgroup v2. Jei komanda rodo ką nors kita, tai išsiaiškinkite prieš perėjimą, nes systemd dokumentacija senas v1 parinktis apibūdina kaip pasenusias.

systemd MemoryMax PHP-FPM nustatymas

Riba įrašoma papildymo faile, vadinamajame drop-in. Taip paketo Unit failas lieka nepakeistas, o atnaujinimas jūsų nustatymo neperrašo. Paslaugų pavadinimai skiriasi pagal distribuciją; pavyzdyje naudojamas Debian ir Ubuntu su PHP 8.3.

# Atidaro redaktorių drop-in failui: sudo systemctl edit php8.3-fpm
# Rezultatas: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue

Parinkčių apžvalga pagal systemd dokumentaciją:

Parinktis Poveikis Pastaba PHP-FPM
MemoryHigh= slopinimo riba: procesai stipriai stabdomi, atmintis susigrąžinama tikrasis valdymo dydis, šiek tiek žemiau MemoryMax
MemoryMax= absoliuti riba: jei jos neįmanoma išlaikyti, paslaugoje įsijungia OOM-killer paskutinė gynybos linija
MemorySwapMax= iškėlimo riba 0 neleidžia paslaugai vietoj to trauktis į swap
OOMPolicy= elgsena po OOM įvykio: continue, stop ar kill continue leidžia likusiems darbiniams procesams veikti toliau
LimitAS=, LimitDATA= rlimits kiekvienam procesui pasak php.net, šiai priemonei netinka

OOMPolicy= nusipelno ypatingo dėmesio. Esant stop ar kill, systemd po OOM įvykio nutraukia visą paslaugą, ir visos šios FPM paslaugos svetainės neveikia. Kuri reikšmė galioja be savo nurodymo, nustato sistemos numatytoji DefaultOOMPolicy=. Todėl veiksmingą reikšmę patikrinkite, o ne ją prisiimkite.

Po to paslaugą paleiskite iš naujo ir baseine užrakinkite memory_limit:

sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
; Baseino failas, pvz., /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1

Kaip veikia užrakinimas, aprašo php_admin_value: nustatymų užrakinimas nuo perrašymo.

Tinkamos reikšmės radimas

Per siaura riba sukelia nutraukimus įprastu režimu, per dosni beveik neapsaugo. Atspirties taškas yra tikrasis naudojimas.

  1. Kelias dienas stebėti dabartinį visų FPM procesų atminties poreikį, taip pat apkrovos pikų metu.
  2. Nustatyti tipinį vieno darbinio proceso poreikį ir padauginti iš visų paslaugos baseinų pm.max_children.
  3. Numatyti priedą master procesui, OPcache ir pikams.
  4. MemoryHigh= nustatyti šiek tiek virš išmatuotos piko reikšmės, MemoryMax= su šiek tiek didesniu atstumu virš jos.
  5. Po savaitės reikšmes patikslinti pagal matavimus ir OOM įvykius.
# Visų PHP-FPM procesų atmintis MB (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Nuolatinė visų cgroups apžvalga, surikiuota pagal atmintį
systemd-cgtop -m

memory_limit reikšmę prieš perėjimą turėtumėte užsirašyti. Po atnaujinimo galite grįžti prie fiksuotos ribos PHP; kaip ją prasmingai pasirinkti, aprašyta skyriuje memory_limit teisingas nustatymas.

Stebėsena: kada riba suveikia

Atminties riba, kurios suveikimo niekas nepastebi, yra tylus klaidų šaltinis. Branduolys skaičiuoja OOM įvykius kiekvienai cgroup, o systemd ir branduolio žurnalas juos užfiksuoja.

# Paslaugos cgroup OOM skaitiklis (kelias esant cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# OOM-killer pranešimai branduolio žurnale
journalctl -k --since "-1 day" | grep -i "out of memory"

Jei skaitiklis oom_kill auga, branduolys nutraukia darbinius procesus. Tai gali rodyti per siaurą ribą, klaidingą programą arba neįprastas užklausas. Dabartinėje padėtyje verta tokius įvykius palyginti su prieigos žurnalais tuo pačiu laiku.

Svarbu

Atminties riba yra pereinamoji priemonė ir nepakeičia PHP atnaujinimo. Atnaujinimą planuokite lygiagrečiai ir po jo iš naujo paleiskite visus PHP procesus. Visų pereinamųjų priemonių apžvalga pateikta skyriuje Skubios priemonės, kai dar negalite pataisyti.

Kas vyksta PHP viduje trūkstant atminties, paaiškina memory_limit ir Out-of-Memory. Su Apache ir mod_php ta pati technika galioja Apache paslaugai, tačiau PHP tada ribą dalijasi su pačiu žiniatinklio serveriu.

Išvada: nepamirškite antros pusės

memory_limit=-1 yra pateisinama pereinamoji priemonė tik kartu su išorine riba. systemd šią ribą įgalina keliomis eilutėmis: MemoryHigh kaip stabdį, MemoryMax kaip kietą viršutinę ribą, MemorySwapMax nuo traukimosi į swap ir sąmoningai pasirinktą OOMPolicy. Išmatuokite prieš tai, stebėkite po to ir turėkite omenyje, kad visi paslaugos baseinai ribą dalijasi.

Visų pirma: suplanuokite priemonės pabaigą. Po atnaujinimo į ištaisytą PHP versiją grįžkite prie fiksuoto memory_limit. cgroup ribą galite palikti kaip papildomą apsaugą nuo išsiplečiančių procesų.

Kaip PHP SOS padeda konkrečiai?

  • Matuoti: užfiksuojame jūsų PHP paslaugų atminties poreikį ir iš jo išvedame tinkamas ribas.
  • Įgyvendinti: nustatome drop-in failus su MemoryHigh, MemoryMax ir tinkama OOMPolicy, užrakiname memory_limit baseine ir kontroliuojamai iš naujo paleidžiame paslaugas.
  • Stebėti: OOM įvykius įtraukiame į stebėseną ir koreguojame reikšmes.
  • Nuvesti iki pataisos: per PHP pataisų paslaugą įdiegiame atnaujinimą ir po to tvarkingai atšaukiame pereinamąsias reikšmes.

Dažni klausimai

Kodėl memory_limit turi būti -1? Ar tai nepavojinga?

Vienas pats būtų. php.net memory_limit=-1 rekomenduoja kaip pereinamąją priemonę aiškiai kartu su atminties riba per konteinerį ar cgroup. Apribojimas taip iš PHP persikelia į operacinę sistemą. Be šios išorinės ribos reikšmės nenustatykite.

Ar vietoj to galiu naudoti ulimit ar LimitAS=?

Ne. php.net aiškiai atkalba nuo RLIMIT_AS ir RLIMIT_DATA, o būtent šias ribas nustato ulimit -v, LimitAS= ir LimitDATA=. systemd dokumentacija taip pat pažymi, kad LimitAS= riboja adresų erdvę, o ne atminties naudojimą.

Kas atsitinka, kai PHP-FPM pasiekia ribą?

Esant MemoryHigh branduolys stabdo procesus ir susigrąžina atmintį. Esant MemoryMax paslaugos viduje įsijungia OOM-killer ir nutraukia procesą, dažniausiai didelį darbinį procesą (worker). Ar po to sustoja visa paslauga, nustato OOMPolicy=.

Ar riba galioja kiekvienam baseinui atskirai?

Ne. Visi vienos FPM paslaugos baseinai dalijasi viena cgroup ir todėl bendra riba. Atskiroms ribos kiekvienai svetainei reikia atskirų FPM paslaugų ar konteinerių.

Kaip nustatyti ribą Docker ar Kubernetes?

Per konteinerio atminties apribojimą, pavyzdžiui, parinktį --memory su docker run arba resources.limits.memory Kubernetes. Abu taip pat naudoja cgroups. Principas tas pats: riba už PHP ribų, memory_limit PHP užrakintas.

Šaltiniai

Skubus kontaktas

Kiekviena valanda svarbi. Parašykite mums dabar.

Trumpai aprašykite sistemą, PHP versiją ir pastebėjimus. Mes atsakysime, išsiaiškinsime padėtį ir dirbsime nuotoliu, kol incidentas bus užbaigtas ir dokumentuotas.

Skubi pagalba