Situação atual: PHP2Shell. Duas vulnerabilidades críticas do PHP (CVSS 9,2) permitem executar código malicioso sem autenticação. Patches disponíveis desde 21/10/2026.O que fazer agora →

O ciclo de vida do pedido: arranque, execução, shutdown

Como o PHP inicia processos, trata pedidos e limpa entre eles. Com as fases MINIT a MSHUTDOWN e a relação com a CVE-2026-109619.

Atualizado em 6 min de leituraConhecimento PHP

Em resumo: O ciclo de vida do pedido PHP tem dois níveis. Uma vez por processo, o PHP carrega as suas extensões (fase MINIT) e desmonta-as no fim (MSHUTDOWN). Para cada pedido individual há uma fase própria de arranque e de limpeza (RINIT e RSHUTDOWN). Na operação web, um worker trata muitos pedidos em sequência, pelo que o PHP tem de repor de forma fiável todo o estado do pedido entre dois pedidos. Foi precisamente nesta fronteira que esteve a CVE-2026-109619, a segunda vulnerabilidade do PHP2Shell.

Dois níveis: o processo e o pedido

Para quem não é técnico, ajuda uma imagem: um worker PHP é como um balcão numa repartição. O balcão é montado uma vez de manhã, com formulários, carimbos e computador. Depois chegam, um após o outro, muitos visitantes. Após cada visitante, a mesa tem de ficar vazia, para que o seguinte não veja documentos alheios.

O PHP funciona da mesma maneira. No arranque de um processo, carrega extensões e definições que são iguais para todos os pedidos. Cada pedido recebe depois a sua própria memória, as suas próprias variáveis e os seus próprios dados de pedido, como $_GET, $_POST ou $_FILES. No fim do pedido, o PHP liberta essa memória. O PHP Internals Book resume-o de forma concisa: o PHP esquece a memória do pedido no fim de cada pedido.

As fases do ciclo de vida do pedido PHP em resumo

Os nomes vêm do interior do PHP; cada extensão pode ligar-se a estas fases.

Fase Quando corre Com que frequência Tarefa típica
MINIT (Module Init) no arranque do processo uma vez por processo registar extensões, criar entradas ini, dados permanentes
GINIT ao criar as globais por processo, em threads por thread preparar variáveis globais de uma extensão
RINIT (Request Init) no início de cada pedido por pedido preparar a memória e o estado do pedido
Execução entretanto por pedido o seu código PHP corre
RSHUTDOWN no fim de cada pedido por pedido libertar recursos do pedido
PRSHUTDOWN após o RSHUTDOWN por pedido, raramente usada últimos trabalhos de limpeza
MSHUTDOWN ao terminar o processo uma vez por processo libertar dados permanentes

Uma frase importante do PHP Internals Book para quem desenvolve extensões: as globais não são esvaziadas automaticamente após cada pedido. O que tem de estar fresco em cada pedido deve ser reposto no RINIT. Esta regra mostra por que razão a fronteira entre dois pedidos é um ponto crítico para a segurança.

No fim de um pedido, a ordem também está definida: primeiro correm as funções de shutdown que registou com register_shutdown_function(), depois os destrutores, depois o buffer de saída é esvaziado. Só depois se segue o RSHUTDOWN.

Porque é que os workers são reutilizados

Na linha de comandos, um processo trata exatamente um pedido e termina. Na operação web seria demasiado lento. O PHP-FPM, o Apache com mod_php, o LiteSpeed e o FrankenPHP mantêm por isso workers prontos, que atendem muitos pedidos seguidos. O MINIT corre então uma vez, o RINIT e o RSHUTDOWN milhares de vezes. Como o FPM controla os seus workers explica-o PHP-FPM explicado.

No PHP-FPM, o pm.max_requests define após quantos pedidos um worker reinicia. A predefinição é 0, ou seja, ilimitado. O manual indica a definição como meio contra fugas de memória em bibliotecas de terceiros:

; pool FPM: reiniciar o worker após 500 pedidos (contra fugas de memória)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500

O modo worker do FrankenPHP vai ainda mais longe: aí, a aplicação inteira arranca uma vez e permanece em memória. O estado da aplicação sobrevive então também ao nível do PHP. O exemplo mostra a diferença:

<?php
function anfrageZaehler(): int
{
    static $zaehler = 0;   // na operação clássica com FPM, volta a 0 em cada pedido
    return ++$zaehler;     // no modo worker, o valor continua a contar ao longo dos pedidos
}
echo anfrageZaehler();

Na operação clássica, a chamada imprime sempre 1, porque o PHP descarta a memória do pedido. No modo worker, o valor cresce enquanto o worker viver. A documentação do FrankenPHP aconselha por isso a repor deliberadamente, entre pedidos, o estado ligado ao pedido.

Onde atua a CVE-2026-109619

A CVE-2026-109619 não diz respeito ao código da sua aplicação, mas ao próprio PHP. Segundo o php.net, o estado interno pertencente ao pedido podia sobreviver à libertação da sua memória. Um pedido posterior no mesmo worker podia usá-lo antes de a inicialização o repor. Na imagem: na mesa do balcão ficou um papel que já devia ter sido arrumado. Não publicamos deliberadamente detalhes de exploração; o enquadramento está em CVE-2026-109619.

As medidas provisórias do php.net para esta vulnerabilidade atuam sobre caminhos confirmados e não são uma proteção completa:

  1. session.upload_progress.enabled=0 no php.ini, no pool FPM ou na configuração do servidor, depois reiniciar todos os workers; não através de ini_set() nem de .user.ini.
  2. No PHP 8.5, adicionalmente fatal_error_backtraces=0 e reinício.
  3. Remover, em cada pedido, definições inválidas ou obsoletas que geram mensagens antes da inicialização. Segundo o php.net, apenas ocultar as mensagens não ajuda.
  4. Corrigir na aplicação os erros fatais e as expirações de tempo que podem ser desencadeados do exterior, também em callbacks, no processamento de uploads e de importações.

O remédio verdadeiro é a atualização para o PHP 8.5.12, 8.4.27, 8.3.36 ou 8.2.35. Como o erro está no worker em execução, só produz efeito após o reinício de todos os processos; os passos estão em Reiniciar o PHP-FPM e todos os workers.

Dica

Depois do reinício, verifique com ps -eo pid,lstart,cmd | grep php se todos os processos PHP foram iniciados depois do momento da atualização.

O que o ciclo de vida significa para a operação e o desenvolvimento

Do modelo podem deduzir-se regras claras. Para administradores: uma atualização só é eficaz quando já não corre nenhum processo com código antigo. Cada modo de operação com workers de longa duração precisa de um reinício documentado. Para programadores: o código que corre em modos worker não pode deixar estado ligado ao pedido em variáveis estáticas ou singletons, por exemplo dados de utilizadores ou permissões. Para ambos vale: conservar os registos a partir de 20/10/2026, para poder avaliar mais tarde se algo aconteceu antes do patch.

Como ajuda concretamente a PHP SOS?

Com o serviço de patches PHP, aplicamos o modelo na prática:

  • Determinar o modo de operação: verificamos se corre PHP-FPM, mod_php, LiteSpeed, FrankenPHP ou php-cgi e como os workers são geridos.
  • Atualização e reinício: aplicamos a versão corrigida e reiniciamos cada worker de forma controlada.
  • Verificação dos processos: comprovamos, com base nas horas de arranque e nas versões, que não resta nenhum processo antigo.
  • Verificar os modos worker: nas aplicações de longa duração, examinamos se o estado ligado ao pedido é reposto corretamente.

Perguntas frequentes

O PHP inicia um processo novo para cada pedido?

Na linha de comandos, sim; na operação web, geralmente não. O PHP-FPM, o Apache com mod_php, o LiteSpeed ou o FrankenPHP deixam um processo tratar muitos pedidos em sequência. Entre pedidos, o PHP limpa o estado do pedido.

Qual é a diferença entre MINIT e RINIT?

O MINIT corre uma só vez, quando o processo PHP arranca, e cria estruturas permanentes. O RINIT corre no início de cada pedido individual e prepara o que só esse pedido precisa.

O pm.max_requests ajuda contra a CVE-2026-109619?

O php.net não indica reinícios mais frequentes dos workers como medida provisória. A definição limita apenas quantos pedidos um worker trata antes de reiniciar. Contra a vulnerabilidade ajuda a atualização, seguida do reinício de todos os workers.

O que é o modo worker do FrankenPHP?

No modo worker, a aplicação arranca uma só vez e permanece em memória. Poupa tempo, mas as variáveis estáticas, as globais e as caches mantêm-se entre pedidos. Segundo a documentação do FrankenPHP, o estado ligado ao pedido tem por isso de ser reposto deliberadamente.

Fontes

Contacto imediato

Cada hora conta. Escreva-nos agora.

Descreva brevemente o sistema, a versão do PHP e o que observou. Entramos em contacto consigo, esclarecemos a situação e trabalhamos remotamente até o incidente estar concluído e documentado.

Ajuda imediata