
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 utiliza 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 — de quais papéis os keyrings fs-verity um papel pode adicionar certificados,
ou se ele pode escrever em keyrings de forma alguma.Negativas e eventos de ciclo de vida são gravados em ring buffers fixados. O bpfjlog
imprime os diagnósticos BPF legíveis por humanos e os eventos estruturados e acompanha
os ring buffers através de uma substituição de política em tempo real.
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, auxiliares 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 da toolchain BPF. |
log/ | bpfjlog | Consumidor dos ring buffers fixados de diagnósticos 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 em 6.16+, e kernels
mais antigos não são suportados.bpftool, e um compilador C++20. clang 20
e 21 não emitem addr_space_cast para um __builtin_memcpy a partir de memória arena,
então o verificador rejeita o jailer que eles compilam.CONFIG_BTRFS_FS=y em vez de um módulo. Os
programas BPF leem tipos de inode do btrfs a partir de vmlinux.h, que o Makefile
gera a partir do BTF do kernel em execução.protoc, que Debian e Ubuntu fornecem como libprotobuf-dev
e protobuf-compiler.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 cada 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 with no shared object dependencies
make client # build/bpfjclient, no BPF toolchain needed
make log # build/bpfjlog
make signing-key # generate a development signing key and certificate
make signed SIGNING_KEY=... SIGNING_CERT=... # static, fs-verity signed bpfjctl
make srv SIGNING_KEY=... SIGNING_CERT=... # static, signed bpfjsrv
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 local, por
exemplo make BUILD=build-asan SANITIZE=address,undefined.
make test
Os testes precisam ser executados como root, porque cada um cria um namespace de montagem 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 em série por padrão, porque a desanexação concorrente do BPF LSM pode causar panic em kernels afetados. Use make test TEST_ARGS=Suite.Test para um caso focado, e opte por -j N ou BPFJTEST_JOBS=N apenas dentro de uma VM descartável.
sudo bpfjctl check policy.toml # parse a policy and report what it holds
sudo bpfjctl attach policy.toml # load and pin the jailer
sudo bpfjctl replace policy.toml # reload without releasing jailed tasks
sudo bpfjctl wrap ROLE POD_ID -- CMD # run CMD in a new pod
sudo bpfjctl enroll ROLE POD_ID PID [NAME=VALUE...] # enroll with variables
sudo bpfjctl show PID # pods a process is in
sudo bpfjctl list # every pod and its processes
sudo bpfjctl detach # unpin and unload
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 um --uid não-root deixa o comando
capaz de se remover da jail. Veja bpfjctl wrap --help.
Execute sudo build/bpfjlog enquanto o jailer estiver anexado para observá-lo. Os
diagnósticos do 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 ao pod,
variáveis e propriedade de recursos rastreados, e então troca atomicamente
as árvores de pins. Ambas as árvores permanecem anexadas durante a transferência, forks e
enrollment são coordenados com a migração, e as 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 se o estado não puder ser copiado com segurança.
base-role = "floor" # optional: enroll every process on the host
vars = ["vm_uuid"] # known variable names
[certs]
corp-ca = "MIIDXTCCAkWgAwIBAgIJAK..." # PEM or base64 DER certificate
[roles.floor]
any = true # open tracking-only base role