
Controle de Acesso Obrigatório e jailer baseados em eBPF LSM
Controle de Acesso Obrigatório baseado em eBPF para Linux.
Este projeto é uma reescrita completa do BpfJailer de código fechado e é completamente experimental. Ele aproveita recursos mais recentes, como bpf arena, que não estavam disponíveis quando o BpfJailer interno foi escrito. Problemas são esperados e não são elegíveis para bug bounty nem considerados achados de segurança. Uma vez devidamente avaliado, ele substituirá a versão interna de código fechado.
O BpfJailer usa programas eBPF LSM para colocar processos em jaulas, chamadas pods, cada
uma vinculada a um papel de uma política TOML. Um pod é herdado através de fork e
exec. Recursos opcionais de política:
kill e ptrace — papéis/pods alvo que podem receber sinais ou aos quais se pode anexar.bpf — de quais papéis os mapas e programas eBPF um papel pode abrir, ou se ele
pode chamar bpf(2) de forma alguma.keyring — a quais keyrings fs-verity de quais papéis um papel pode adicionar certificados,
ou se ele pode escrever em keyrings de forma alguma.Negações e eventos de ciclo de vida são gravados em ring buffers fixados (pinned). bpfjlog
imprime os diagnósticos BPF legíveis por humanos e eventos estruturados e acompanha
os ring buffers através de uma substituição de política ao vivo.
Um binário pode reivindicar um papel através do xattr user.bpfj.policy.exec e é
inscrito nele no momento do exec. Processos em execução também podem ser inscritos diretamente,
e um processo sem privilégios pode se inscrever através de bpfjsrv/bpfjclient.
| Diretório | Binário | Propósito |
|---|---|---|
bpfj/ | A biblioteca principal e os programas BPF: jailer, enforcers, parser de política, helpers C++ do libbpf. | |
ctl/ | bpfjctl | Ferramenta de uso geral para anexar, recarregar, inspecionar e desanexar o jailer, e para inscrever processos. |
cmd/ | bpfjcmd | bpfjctl com seus argumentos, e opcionalmente sua política, compilados. Ele ignora argv, então pode ser estaticamente linkado e assinado com fs-verity como uma única unidade. |
srv/ | bpfjsrv | Servidor ativado por socket que inscreve chamadores sem privilégios em papéis que o permitem. |
client/ | bpfjclient | Cliente mínimo para bpfjsrv, sem dependência de libbpf ou toolchain BPF. |
log/ | bpfjlog | Consumidor dos ring buffers fixados de diagnóstico e eventos estruturados. |
tests/ | bpfjtest | Suíte de testes. |
CONFIG_BPF_LSM=y e bpf no
parâmetro de boot lsm=). O BpfJailer é testado apenas no 6.16+, e kernels mais antigos
não são suportados.bpftool, e um compilador C++20.openssl, fsverity e setfattr, além dos arquivos estáticos
listados no Makefile (STATIC=1).Defina LIBBPF_CFLAGS / LIBBPF_LIBS se o pkg-config não conseguir encontrar o libbpf. Aponte
LIBARENA para o checkout do libarena em todo build:
Todo make abaixo também precisa de LIBARENA (veja Requisitos), definido na
linha de comando ou exportado no ambiente.
make # build/bpfjctl
make STATIC=1 # bpfjctl sem dependências de objetos compartilhados
make client # build/bpfjclient, sem necessidade de toolchain BPF
make log # build/bpfjlog
make signing-key # gera uma chave de assinatura de desenvolvimento e certificado
make signed SIGNING_KEY=... SIGNING_CERT=... # bpfjctl estático, assinado com fs-verity
make srv SIGNING_KEY=... SIGNING_CERT=... # bpfjsrv estático e assinado
make cmd SIGNING_KEY=... SIGNING_CERT=... \
CMD_ARGS="replace-compiled" CMD_POLICY=policy.toml CMD_ROLE=bpfjailer
make clean
Toda a saída vai para build/. Defina BUILD= para compilar em outro lugar, por
exemplo make BUILD=build-asan SANITIZE=address,undefined.
make test
Os testes precisam ser executados como root, porque cada um cria um mount namespace e
monta um bpffs. make test compila como o usuário que o invoca e executa apenas o binário de teste
sob sudo.
Os testes são executados serialmente por padrão porque o detach concorrente do BPF LSM pode causar panic
em kernels afetados. Use make test TEST_ARGS=Suite.Test para um caso focado, e
só opte por -j N ou BPFJTEST_JOBS=N dentro de uma VM descartável.
sudo bpfjctl check policy.toml # analisa uma política e relata o que ela contém
sudo bpfjctl attach policy.toml # carrega e fixa o jailer
sudo bpfjctl replace policy.toml # recarrega sem liberar tarefas encarceradas
sudo bpfjctl wrap ROLE USER_ID -- CMD # executa CMD em um novo pod
sudo bpfjctl enroll ROLE USER_ID PID [NAME=VALUE...] # inscreve com variáveis
sudo bpfjctl show PID # pods em que um processo está
sudo bpfjctl list # todos os pods e seus processos
sudo bpfjctl detach # desfixa e descarrega
Os programas são fixados em /sys/fs/bpf/bpfj-pins por padrão. Use
--bpffs-path e --pin-dir para alterar isso. Eles permanecem carregados até que detach
seja executado.
bpfjctl wrap sem --drop-cap e com um --uid não-root deixa o comando
capaz de se remover da jaula. Veja bpfjctl wrap --help.
Execute sudo build/bpfjlog enquanto o jailer estiver anexado para observá-lo. Os diagnósticos
BPF são gravados em stderr e os eventos estruturados em stdout. O logger
se reconecta automaticamente quando replace troca por um novo conjunto de mapas fixados.
replace carrega um segundo jailer completo ao lado do ativo, migra a associação de pods,
variáveis e propriedade de recursos rastreada, e então troca atomicamente
as árvores de pin. Ambas as árvores permanecem anexadas durante a transferência, forks e
inscrição são coordenados com a migração, e mudanças de propriedade são registradas em journal e reproduzidas. A substituição falha de forma segura (fail closed) se as versões de layout persistidas
forem incompatíveis ou o estado não puder ser copiado com segurança.
base-role = "floor" # opcional: inscreve todo processo no host
vars = ["vm_uuid"] # nomes de variáveis conhecidos
[certs]
corp-ca = "MIIDXTCCAkWgAwIBAgIJAK..." # certificado PEM ou DER em base64
[roles.floor]
any = true # papel base aberto apenas para rastreamento