
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 entièrement 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 les 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 que l'on peut signaler ou auxquels on peut s'attacher.bpf — les cartes et programmes eBPF de quels rôles un rôle peut ouvrir, ou s'il
peut appeler bpf(2) du tout.keyring — les keyrings fs-verity de quels rôles 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'homme 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 | Objectif |
|---|---|---|
bpfj/ | La bibliothèque principale et les programmes BPF : jailer, enforcers, analyseur 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.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 sans dépendances à des objets partagés
make client # build/bpfjclient, aucune chaîne d'outils BPF nécessaire
make log # build/bpfjlog
make signing-key # génère une clé de signature de développement et un certificat
make signed SIGNING_KEY=... SIGNING_CERT=... # bpfjctl statique, signé fs-verity
make srv SIGNING_KEY=... SIGNING_CERT=... # bpfjsrv statique, signé
make cmd SIGNING_KEY=... SIGNING_CERT=... \
CMD_ARGS="replace-compiled" CMD_POLICY=policy.toml CMD_ROLE=bpfjailer
make clean
Toute la sortie va sous build/. Définissez BUILD= pour compiler ailleurs, par
exemple make BUILD=build-asan SANITIZE=address,undefined.
make test
Les tests doivent s'exécuter 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 un détachement BPF LSM concurrent peut paniquer
les noyaux affecté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 # analyse une politique et rapporte ce qu'elle contient
sudo bpfjctl attach policy.toml # charge et épingle le jailer
sudo bpfjctl replace policy.toml # recharge sans libérer les tâches en jail
sudo bpfjctl wrap ROLE USER_ID -- CMD # exécute CMD dans un nouveau pod
sudo bpfjctl enroll ROLE USER_ID PID [NAME=VALUE...] # enrôle avec des variables
sudo bpfjctl show PID # pods dans lesquels se trouve un processus
sudo bpfjctl list # chaque pod et ses processus
sudo bpfjctl detach # désépingle et décharge
Les programmes sont épinglés sous /sys/fs/bpf/bpfj-pins par défaut. Utilisez
--bpffs-path et --pin-dir pour changer 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 cartes épinglées.
replace charge un second jailer complet à côté de celui actif, migre l'appartenance aux pods,
les variables et la propriété des ressources suivies, puis échange
atomiquement les arbres d'épinglage. Les deux arbres restent attachés pendant la passation, 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 en mode fermé 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" # optionnel : enrôle chaque processus sur l'hôte
vars = ["vm_uuid"] # noms de variables connus
[certs]
corp-ca = "MIIDXTCCAkWgAwIBAgIJAK..." # certificat PEM ou DER en base64
[roles.floor]
any = true # rôle de base ouvert uniquement pour le suivi