
Contrôle d'accès obligatoire et jailer basés sur eBPF LSM
Contrôle d'accès obligatoire basé sur eBPF pour Linux.
Ce projet est une réécriture complète du BpfJailer à code source fermé et est totalement expérimental. Il exploite des fonctionnalités plus récentes comme bpf arena qui n'étaient pas disponibles lorsque le BpfJailer interne a été écrit. Des problèmes sont attendus et ne sont pas éligibles au bug bounty ni considérés comme des découvertes de sécurité. Une fois correctement évalué, il remplacera la version interne à code source fermé.
BpfJailer utilise des programmes eBPF LSM pour placer des processus dans des jails, appelées pods, chacune
liée à un rôle issu d'une politique TOML. Un pod est hérité à travers fork et
exec. Fonctionnalités optionnelles de la politique :
kill et ptrace — les rôles/pods cibles auxquels il est possible d'envoyer des signaux ou de s'attacher.bpf — les maps et programmes eBPF des rôles qu'un rôle peut ouvrir, ou s'il
peut appeler bpf(2) du tout.keyring — les keyrings fs-verity des rôles auxquels un rôle peut ajouter des certificats,
ou s'il peut écrire dans des keyrings du tout.Les refus et les événements de cycle de vie sont écrits dans des ring buffers épinglés. bpfjlog
affiche les diagnostics BPF lisibles par l'humain et les événements structurés et suit
les ring buffers à travers un remplacement de politique à chaud.
Un binaire peut revendiquer un rôle via l'xattr user.bpfj.policy.exec et est
enrôlé dans celui-ci au moment de l'exec. Les processus en cours d'exécution peuvent également être enrôlés directement,
et un processus non privilégié peut s'enrôler lui-même via bpfjsrv/bpfjclient.
| Répertoire | Binaire | Rôle |
|---|---|---|
bpfj/ | La bibliothèque principale et les programmes BPF : jailer, enforcers, parseur de politique, helpers C++ libbpf. | |
ctl/ | bpfjctl | Outil polyvalent pour attacher, recharger, inspecter et détacher le jailer, et pour enrôler des processus. |
cmd/ | bpfjcmd | bpfjctl avec ses arguments, et éventuellement sa politique, compilés dedans. Il ignore argv, il peut donc être lié statiquement et signé fs-verity comme une seule unité. |
srv/ | bpfjsrv | Serveur activé par socket qui enrôle les appelants non privilégiés dans les rôles qui l'autorisent. |
client/ | bpfjclient | Client minimal pour bpfjsrv, sans dépendance à libbpf ni à la chaîne d'outils BPF. |
log/ | bpfjlog | Consommateur des ring buffers épinglés de diagnostics et d'événements structurés. |
tests/ | bpfjtest | Suite de tests. |
CONFIG_BPF_LSM=y et bpf dans
le paramètre de démarrage lsm=). BpfJailer n'est testé que sur 6.16+, et les noyaux
plus anciens ne sont pas pris en charge.bpftool, et un compilateur C++20. clang 20
et 21 n'émettent pas d'addr_space_cast pour un __builtin_memcpy depuis la mémoire arena,
donc le vérificateur rejette le jailer qu'ils produisent.CONFIG_BTRFS_FS=y plutôt qu'un module. Les
programmes BPF lisent les types d'inode btrfs depuis vmlinux.h, que le Makefile
génère à partir du BTF du noyau en cours d'exécution.protoc, que Debian et Ubuntu fournissent sous les noms libprotobuf-dev
et protobuf-compiler.openssl, fsverity et setfattr, plus les archives statiques
listées dans le Makefile (STATIC=1).Définissez LIBBPF_CFLAGS / LIBBPF_LIBS si pkg-config ne trouve pas libbpf. Pointez
LIBARENA vers le checkout de libarena à chaque build :
Chaque make ci-dessous nécessite également LIBARENA (voir Prérequis), défini sur la
ligne de commande ou exporté dans l'environnement.
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
Toute la sortie se trouve sous build/. Définissez BUILD= pour compiler ailleurs, par exemple make BUILD=build-asan SANITIZE=address,undefined.
make test
Les tests doivent être exécutés en tant que root, car chacun crée un namespace de montage et monte un bpffs. make test compile en tant qu'utilisateur appelant et exécute uniquement le binaire de test sous sudo.
Les tests s'exécutent en série par défaut, car le détachement concurrent du BPF LSM peut provoquer un panic sur les noyaux concernés. Utilisez make test TEST_ARGS=Suite.Test pour un cas ciblé, et n'activez -j N ou BPFJTEST_JOBS=N qu'à l'intérieur d'une VM jetable.
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
Les programmes sont épinglés sous /sys/fs/bpf/bpfj-pins par défaut. Utilisez
--bpffs-path et --pin-dir pour modifier cela. Ils restent chargés jusqu'à ce que detach
s'exécute.
bpfjctl wrap sans --drop-cap et avec un --uid non-root laisse la commande
capable de se retirer elle-même de la jail. Voir bpfjctl wrap --help.
Exécutez sudo build/bpfjlog pendant que le jailer est attaché pour l'observer. Les diagnostics
BPF sont écrits sur stderr et les événements structurés sur stdout. Le logger
se reconnecte automatiquement lorsque replace échange un nouvel ensemble de maps épinglées.
replace charge un second jailer complet à côté de celui actif, migre l'appartenance
des pods, les variables et la propriété des ressources suivies, puis échange atomiquement
les arbres de pins. Les deux arbres restent attachés pendant le transfert, les forks et
l'enrôlement sont coordonnés avec la migration, et les changements de propriété sont
journalisés et rejoués. Le remplacement échoue de manière sûre (fail closed) si les versions
de disposition persistées sont incompatibles ou si l'état ne peut pas être copié en toute sécurité.
base-role = "floor" # optional: enroll every process on the host
vars = ["vm_uuid"] # known variable names