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 →

Gestão de memória em PHP: contagem de referências e garbage collection

O PHP gere a memória com contagem de referências e um garbage collector para ciclos. Esta página explica o mecanismo, as principais funções de medição e por que razão ambas as vulnerabilidades do PHP2Shell têm a ver com estados de memória.

Atualizado em 6 min de leituraConhecimento PHP

Em resumo: O PHP liberta memória sobretudo através da contagem de referências: cada valor sabe quantas variáveis apontam para ele e é eliminado assim que esse contador chega a zero. Para as referências circulares, que de outro modo nunca seriam libertadas, existe um garbage collector de ciclos. No fim de cada pedido, o PHP limpa ainda tudo o que o pedido ocupou. Ambas as vulnerabilidades do PHP2Shell atuam sobre estados de memória: sobre um estado de falta de memória durante o processamento do pedido e sobre um estado interno que persiste demasiado tempo após a libertação.

Como o PHP gere a memória: visão geral

Em linguagens como o C, o programador tem de pedir e libertar a memória ele próprio. O PHP dispensa-o disso. Cada variável, cada array e cada objeto ocupa memória, que o PHP gere através do seu próprio gestor de memória. O limite para isso é definido pelo memory_limit, segundo o manual 128M por predefinição.

Para quem decide, um ponto é central: na operação clássica com PHP-FPM ou mod_php correm processos worker de longa duração, que tratam muitos pedidos em sequência. O que um pedido deixa para trás tem de estar limpo de forma fiável antes do seguinte. É precisamente aqui que está uma das vulnerabilidades do PHP2Shell.

Contagem de referências: contar quem usa um valor

O PHP guarda cada valor num contentor, o chamado zval. Além do tipo e do conteúdo, segundo o manual, mantém um contador de referências (refcount) e uma marca que indica se o valor faz parte de uma referência PHP com & (is_ref).

<?php
$a = str_repeat('x', 1000);  // um valor, refcount 1
$b = $a;                     // sem cópia, refcount 2
$b .= 'y';                   // só agora é copiado (copy-on-write)
unset($a);                   // o refcount do original desce
// Quando um contador chega a 0, o PHP liberta a memória de imediato.

O princípio chama-se copy-on-write: o PHP só copia um valor quando uma das variáveis o altera. Isto poupa muita memória. O contador de uma variável pode tornar-se visível com a extensão Xdebug e a função xdebug_debug_zval(). O manual adverte que os números exatos podem diferir consoante a versão do PHP.

Ciclos: quando a contagem de referências não basta

A contagem de referências tem uma fraqueza conhecida. Se uma estrutura aponta para si própria, o seu contador nunca desce a zero.

<?php
$a = ['eins'];
$a[] = &$a;   // o array contém uma referência a si próprio
unset($a);    // ninguém lhe consegue aceder, mas o refcount continua em 1

Segundo o manual, estas referências circulares ocorrem sobretudo em objetos, por exemplo em estruturas pai-filho ou em analisadores. Fugas isoladas são inofensivas, porque o PHP limpa no fim do pedido. Em processos de longa duração, como workers de filas ou grandes execuções de testes, milhões delas podem, porém, somar-se num verdadeiro problema de memória.

O garbage collector de ciclos

Desde o PHP 5.3.0, o PHP usa um algoritmo de Bacon e Rajan para encontrar esses ciclos. A ideia de base: um ciclo só pode surgir quando um contador desce para um valor superior a zero. Só esses valores são retidos pelo PHP como possíveis raízes.

  1. As possíveis raízes vão para um buffer. O manual indica como tamanho predefinido 10 000 entradas; o limiar atualmente em vigor é mostrado pelo gc_status() no campo threshold.
  2. Quando o buffer está cheio, o PHP percorre todos os valores alcançáveis e reduz, a título de teste, os seus contadores.
  3. Numa segunda passagem aplica-se: os valores com contador a zero são lixo; em todos os outros, a redução é revertida.
  4. As raízes são retiradas do buffer e todos os valores reconhecidos como lixo são libertados.

Isto é controlado através de zend.enable_gc (ativo por predefinição) ou, em tempo de execução, com gc_enable() e gc_disable(). O gc_collect_cycles() força uma execução e devolve o número de ciclos recolhidos.

Medir e observar a memória

Função Finalidade
memory_get_usage() memória atualmente ocupada pelo script
memory_get_usage(true) memória reservada pelo gestor de memória junto do sistema; é contra este valor que se verifica o memory_limit
memory_get_peak_usage() valor máximo da memória ocupada
gc_status() execuções, ciclos recolhidos, limiar, raízes; desde o PHP 8.3.0 também tempos de execução
gc_collect_cycles() desencadear de imediato a recolha de ciclos
WeakMap associação a objetos sem impedir a sua libertação, desde o PHP 8.0

Segundo o manual, o memory_get_usage() regista apenas a memória que o PHP pede através do seu próprio gestor de memória. O que as extensões obtêm diretamente junto do sistema operativo não consta destes números. O limite de memória do processo inteiro cabe, por isso, ao sistema operativo ou ao contentor.

Gestão de memória e PHP2Shell

As descrições de ambas as vulnerabilidades levam diretamente a este tema. Na CVE-2026-109618, um estado de falta de memória pode interromper o PHP enquanto os dados do pedido estão apenas parcialmente inicializados; na limpeza, dados inválidos podem então ser tratados como válidos. O que acontece por dentro num excesso de memória explica-o a página memory_limit e Out-of-Memory. Na CVE-2026-109619, o estado interno de um pedido pode persistir após a libertação da memória e ser usado por um pedido posterior no mesmo worker antes de ser reposto. O percurso do arranque à limpeza é descrito na página O ciclo de vida do pedido.

Como medida provisória, o php.net recomenda definir memory_limit=-1 de modo que as aplicações não o possam sobrepor, por exemplo com php_admin_value no pool FPM, e limitar a memória através de contentor ou cgroups, e não através de RLIMIT_AS ou RLIMIT_DATA. Como fazê-lo com o systemd mostra-o o artigo Definir limites de memória com systemd e cgroups.

; Pool FPM: medida provisória segundo o php.net, depois reiniciar todos os workers
php_admin_value[memory_limit] = -1
Importante

Sem um limite através de contentor ou cgroup, um único processo pode esgotar o servidor. E nenhuma definição substitui a atualização para o PHP 8.5.12, 8.4.27, 8.3.36 ou 8.2.35, com o reinício de todos os workers, pois só o reinício remove os processos antigos da memória.

Como ajuda concretamente a PHP SOS?

  • Verificamos as definições de memória efetivas em cada pool e se as aplicações as podem sobrepor.
  • Aplicamos as medidas provisórias com um limite de memória ao nível do contentor ou do cgroup e testamos a carga.
  • Aplicamos a atualização, reiniciamos todos os workers e comprovamos que já não correm processos antigos, no âmbito do nosso serviço de patches PHP.
  • Avaliamos erros de memória e workers interrompidos nos registos a partir de 20/10/2026.

Perguntas frequentes

O que é a contagem de referências em PHP?

O PHP conta, para cada valor, quantas variáveis ou elementos apontam para ele. Quando o contador desce a zero, a memória é libertada de imediato. Assim, a maior parte da memória é devolvida sem uma execução de limpeza própria.

Para que precisa então o PHP de um garbage collector?

Para os ciclos. Se um array aponta para si próprio ou dois objetos se conhecem mutuamente, o contador nunca desce a zero, mesmo que ninguém lhes aceda. O garbage collector de ciclos encontra e remove essas estruturas.

O PHP liberta a memória no fim de um pedido?

Sim, a memória que um script ocupa durante um pedido, o PHP limpa-a no fim do pedido. No PHP2Shell, porém, a CVE-2026-109619 diz respeito precisamente ao caso em que um estado interno persiste após a libertação e pode ser usado por um pedido posterior no mesmo worker.

Porque é que o php.net recomenda memory_limit=-1 como medida provisória?

A vulnerabilidade CVE-2026-109618 está relacionada com um estado de falta de memória. O php.net recomenda definir memory_limit=-1 de modo que as aplicações não o possam sobrepor e limitar a memória através de contentor ou cgroups. Não é uma proteção completa e não substitui a atualização.

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