eBPF: observabilidade e segurança em nível de kernel sem modificar aplicações

1. O que é eBPF e por que ele transforma a observabilidade

1.1. Definição e origem: do BPF clássico ao eBPF moderno

O eBPF (extended Berkeley Packet Filter) é uma tecnologia que permite executar programas sandboxed no kernel Linux sem a necessidade de modificar o código-fonte do kernel ou carregar módulos. Originalmente, o BPF clássico foi criado para filtrar pacotes de rede com eficiência. O eBPF moderno expandiu drasticamente esse conceito, tornando-se uma máquina virtual dentro do kernel capaz de executar programas em resposta a eventos de sistema, rede, segurança e rastreamento.

1.2. Arquitetura básica: programas sandboxed no kernel

Programas eBPF são escritos em uma linguagem de alto nível (geralmente C) e compilados para bytecode. Esse bytecode é então carregado no kernel, onde passa por um verificador rigoroso (verifier) que garante que o programa não entre em loops infinitos, não acesse memória inválida e termine em tempo finito. Após a verificação, o bytecode é compilado para código nativo via JIT (Just-In-Time) compiler, garantindo desempenho próximo ao de código kernel nativo.

// Exemplo conceitual de programa eBPF em C
// (compilado com clang -target bpf)
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("kprobe/sys_execve")
int trace_execve(struct pt_regs *ctx) {
    char comm[16];
    bpf_get_current_comm(&comm, sizeof(comm));
    bpf_printk("Executando: %s\n", comm);
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

1.3. Casos de uso disruptivos

eBPF permite observabilidade, segurança e profiling sem instrumentação de aplicações. Isso significa que você pode monitorar sistemas legados, containers e ambientes de produção sem reiniciar serviços, recompilar código ou adicionar agentes pesados. Os principais casos de uso incluem: rastreamento de syscalls, monitoramento de rede, profiling de CPU, detecção de anomalias e políticas de segurança dinâmicas.

2. Mecanismos fundamentais: hooks, maps e verificação

2.1. Tipos de hooks

eBPF pode ser anexado a diversos pontos do kernel:

  • kprobes: intercepção de funções do kernel (dinâmico)
  • tracepoints: pontos de rastreamento estáveis e documentados
  • uprobes: intercepção de funções em espaço do usuário
  • XDP: processamento de pacotes no driver de rede, antes do kernel
  • sockets: filtragem e monitoramento de tráfego de rede

2.2. Estruturas de dados compartilhadas: eBPF maps

Maps são estruturas de dados que permitem comunicação entre programas eBPF e espaço do usuário. Os tipos mais comuns incluem:

// Exemplo de criação de hash map em BCC (Python)
from bcc import BPF

bpf = BPF(src_file="trace_execve.c")
bpf.attach_kprobe(event=bpf.get_syscall_fnname("execve"), fn_name="trace_execve")

# Map do tipo hash para contar execuções por comando
counts = bpf.get_table("execve_counts")
for key, value in counts.items():
    print(f"{key.comm.decode()}: {value.value}")

2.3. Verificador e JIT compiler

O verificador (verifier) analisa cada programa eBPF antes da execução, garantindo:
- Ausência de loops não limitados
- Acesso seguro à memória
- Terminação garantida em tempo finito
- Compatibilidade com a versão do kernel

O JIT compiler traduz o bytecode verificado para instruções nativas da CPU, resultando em desempenho comparável a código kernel escrito à mão.

3. eBPF para observabilidade profunda sem modificar código

3.1. Rastreamento de chamadas de sistema

Com tracepoints, é possível monitorar todas as syscalls sem modificar aplicações:

# bpftrace: rastrear todas as chamadas open()
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'

3.2. Profiling contínuo de CPU

kprobes permitem amostrar a pilha de chamadas do kernel e espaço do usuário:

# bpftrace: amostragem de CPU a cada 99 Hz
bpftrace -e 'profile:hz:99 { @[kstack, ustack] = count(); }'

3.3. Métricas personalizadas

É possível criar contadores específicos para monitorar vazamento de memória, I/O de disco ou erros de aplicação:

# BCC: contagem de erros de alocação de memória
from bcc import BPF

bpf = BPF(text="""
#include <linux/sched.h>

BPF_HASH(alloc_errors, u32, u64);

TRACEPOINT_PROBE(syscalls, sys_enter_brk) {
    u32 pid = bpf_get_current_pid_tgid();
    u64 *count = alloc_errors.lookup(&pid);
    if (count) {
        (*count)++;
    } else {
        u64 init = 1;
        alloc_errors.update(&pid, &init);
    }
    return 0;
}
""")

4. Segurança em nível de kernel com eBPF

4.1. Detecção de anomalias em tempo real

Programas eBPF podem monitorar execução de binários e acesso a arquivos sensíveis:

# Falco (baseado em eBPF): detectar execução de shell em container
- rule: Shell in Container
  desc: Detecta execução de shell interativo em container
  condition: evt.type=execve and container.id != host and proc.name in (bash, sh, zsh)
  output: "Shell executado em container (user=%user.name command=%proc.cmdline container=%container.id)"
  priority: WARNING

4.2. Implementação de políticas de segurança dinâmicas

eBPF pode ser usado com seccomp-bpf para criar filtros de syscalls granulares:

// Exemplo de filtro seccomp-bpf que bloqueia execve
struct sock_filter filter[] = {
    BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 0, 1),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};

4.3. Casos práticos

  • Bloqueio de syscalls perigosos: uso de eBPF para bloquear ptrace, mount e reboot em containers
  • Detecção de rootkits: monitoramento de alterações em arquivos críticos do sistema
  • Contenção de containers: políticas de rede baseadas em identidade com Cilium

5. Ferramentas e ecossistema prático

5.1. Principais frameworks

  • BCC (BPF Compiler Collection): ferramenta madura com exemplos prontos para rastreamento
  • bpftrace: linguagem de alto nível para one-liners de observabilidade
  • libbpf: biblioteca C para desenvolvimento de programas eBPF portáveis (CO-RE)

5.2. Integração com stacks de observabilidade

eBPF pode exportar métricas e traces para Prometheus, Grafana e OpenTelemetry:

# Exemplo de exportação de métricas eBPF para Prometheus via node_exporter
# (usando bpf_exporter do iovisor)
bpf_exporter --config=syscalls.yaml --port=9435

5.3. Exemplo prático: rastreamento de latência HTTP

Sem modificar o servidor web (Nginx, Apache), podemos medir latência de requisições:

# bpftrace: medir tempo entre accept() e close() em servidor web
bpftrace -e '
kprobe:accept {
    @start[tid] = nsecs;
}

kretprobe:close /@start[tid]/ {
    $delta = nsecs - @start[tid];
    @latency_hist = hist($delta / 1000);
    delete(@start[tid]);
}
'

6. Desafios e boas práticas para times pequenos

6.1. Limitações

  • Compatibilidade de kernel: recursos mais novos exigem kernel 5.x ou superior
  • Overhead de coleta: programas mal escritos podem impactar desempenho
  • Complexidade de debugging: programas eBPF são difíceis de depurar sem ferramentas adequadas

6.2. Estratégias de implantação

  • Uso de sidecars (como Cilium) para ambientes Kubernetes
  • Agentes leves (Falco, Tetragon) que já incluem programas eBPF otimizados
  • Pipelines de dados com bufferização para evitar perda de eventos

6.3. Segurança do próprio eBPF

  • Restringir quem pode carregar programas eBPF via CAP_BPF e CAP_SYS_ADMIN
  • Usar assinatura de programas eBPF para evitar carregamento de código malicioso
  • Monitorar logs de carregamento de programas eBPF

7. Casos de uso reais e tendências futuras

7.1. Exemplos em produção

  • Netflix: profiling de CPU e memória em produção com eBPF
  • Facebook: otimização de rede e roteamento com XDP
  • Cilium: segurança de rede para Kubernetes baseada em eBPF

7.2. eBPF como base para Service Mesh

Projetos como Cilium e Istio (com eBPF) estão substituindo sidecars tradicionais por programas eBPF que oferecem observabilidade e segurança com menor latência.

7.3. O futuro

  • eBPF no Windows: Microsoft está portando eBPF para Windows
  • CO-RE (Compile Once – Run Everywhere): programas eBPF portáveis entre versões de kernel
  • Adoção em edge computing: dispositivos com recursos limitados se beneficiam da leveza do eBPF

Referências