Este artigo documenta a analise tecnica e o passo a passo para a resolucao de uma falha critica no Fail2ban em ambientes Linux. Se o seu serviço de protecao parou de responder, falhou ao iniciar ou entrou em loop infinito de erros de socket, acompanhe a sequencia exata dos eventos e como aplicar a mitigacao definitiva.
1. Cronologia da Falha e Diagnostico dos Sintomas
O problema nao ocorre de forma isolada; ele se manifesta em uma cadeia progressiva de erros que culminam na queda total do sistema de mitigacao de ataques.
Fase 1: O Sintoma Inicial — Too many open files (Errno 24)
Sob alto volume de acessos ou durante um escaneamento massivo de bots, o Fail2ban comeca a registrar falhas de conexao no arquivo de log. O primeiro sinal de alerta e o esgotamento dos descritores de arquivo do processo:
[Errno 24] Too many open files
O serviço tenta continuar rodando, mas a contagem de erros de escuta (err_count['listen']) comeca a subir rapidamente.
Fase 2: O Serviço para de Iniciar e Responder
Apos o estouro de descritores, o daemon do Fail2ban trava. Ao tentar reiniciar o serviço ou checar a comunicacao com o comando fail2ban-client ping, o cliente para de receber resposta do servidor (sem o retorno esperado de Server replied: pong). O socket IPC em /var/run/fail2ban/fail2ban.sock deixa de responder ou e corrompido.
Fase 3: A Causa Raiz no Backend — filedescriptor out of range in select()
Ao depurar o arquivo do servidor assincrono do Fail2ban (/usr/lib/python3/dist-packages/fail2ban/server/asyncserver.py), identifica-se que o motor interno do sistema esta utilizando a chamada legada select.select() do Python.
No sistema operacional Linux, a syscall select() possui um limite rigoroso e hardcoded de 1024 arquivos abertos (FD_SETSIZE). Quando o servidor web abre o descritor de arquivo de numero 1025 ou superior, o Python dispara a seguinte excecao fatal:
ValueError: filedescriptor out of range in select()
Isso faz com que o loop de eventos do Fail2ban quebre imediatamente.
Fase 4: A Falha na Re-definicao — NameError: name 'poll2' is not defined
Para contornar a limitação de 1024 arquivos do select(), a solucao teorica e alterar o motor de eventos para usar a syscall poll() do kernel (que suporta dezenas de milhares de descritores). Porem, ao tentar ajustar o codigo para utilizar a funcao poll2() do modulo asyncore, o interpretador Python dispara um novo erro:
File "/usr/lib/python3/dist-packages/fail2ban/server/asyncserver.py", line 165, in loop
poll2(timeout)
NameError: name 'poll2' is not defined. Did you mean: 'poll'?
Esse erro ocorre porque a funcao poll2 nao esta presente ou nao foi exportada no namespace padrao da biblioteca asyncore na versao do Python em execucao no sistema.
Hipotese de Adulteracao / Vetor de Ataque: Existe uma forte suspeita de que essa sequencia de alteracoes ou a inoperancia da funcao poll2 possa ter sido induzida ou explorada deliberadamente por atacantes. Ao forcar o Fail2ban a retroceder para o algoritmo select() (limitado a 1024 FDs), um atacante consegue derrubar o serviço gerando um volume controlado de conexoes concorrentes. Com o Fail2ban inativo e sem conseguir reiniciar, o servidor fica vulneravel para ataques de forca bruta e scanners automatizados sem qualquer bloqueio no firewall.
2. Solucao Passo a Passo e Mitigacao Definitiva
Para corrigir a cadeia de erros, reestabelecer o serviço e migrar a filtragem para a syscall poll() nativa, siga as etapas abaixo.
Passo 1: Corrigir o arquivo asyncserver.py
Abra o arquivo de codigo do Fail2ban:
sudo nano /usr/lib/python3/dist-packages/fail2ban/server/asyncserver.py
Localize o bloco da funcao loop por volta da linha 160. Substitua a chamada com falha para garantir a importacao da funcao poll2 diretamente do asyncore antes do laço principal:
# Codigo corrigido no asyncserver.py:
from asyncore import poll2
while active():
try:
poll2(timeout)
if err_count['listen']:
err_count['listen'] -= 1
except Exception as e:
if not active():
break
err_count['listen'] += 1
Nota tecnica: Caso o seu ambiente Python nao exporte o simbolo poll2 dentro de asyncore, utilize a chamada nativa do motor assincrono especificando o parametro poll: asyncore.loop(timeout=timeout, use_poll=True, count=1).
Passo 2: Definir a acao do Firewall para IPTables
Se o seu ambiente utiliza o IPTables tradicional e voce precisa garantir a criacao das cadeias f2b- sem conflitos com o nftables, edite o arquivo /etc/fail2ban/jail.local:
[DEFAULT]
banaction = iptables-multiport
banaction_allports = iptables-allports
backend = auto
Passo 3: Limpeza de Sockets Presos e Reinicializacao
Como o Fail2ban falhou ao iniciar anteriormente, limpe os arquivos de socket residuais em memoria antes de subir o daemon:
sudo systemctl stop fail2ban
sudo rm -rf /var/run/fail2ban/*
sudo systemctl start fail2ban
3. Procedimento de Validacao
Apos aplicar a correcao, execute o protocolo de verificacao para atestar a estabilidade do serviço:
1. Teste de resposta do Daemon:
Execute sudo fail2ban-client ping. O resultado deve ser estritamente Server replied: pong.
2. Verificacao das cadeias no IPTables:
Execute sudo iptables -L -n -v e confirme que as tabelas de bloqueio (ex: f2b-sshd, f2b-apache-400) foram criadas e estao prontas para receber regras de banimento.
3. Inspeçao do arquivo de Log:
Acompanhe os logs com tail -f /var/log/fail2ban.log. O serviço devera registrar o processamento normal dos arquivos de log sem apresentar excecoes de descritores de arquivos ou erros no loop do Python.
Conclusao: A substituicao do algoritmo de selecao de sockets do select() para o poll() no motor assincrono do Python elimina o limite de 1024 arquivos abertos, estabilizando o Fail2ban e neutralizando o vetor de ataque por esgotamento de descritores.

Nenhum comentário:
Postar um comentário