Core concepts

Backend capability matrix

Compare the exact filesystem, network, process, lifecycle and platform guarantees of every backend.

Ce document est la référence de l'Executor ABI 10 pour les garanties annoncées par les backends de libmaelys-executor. Les tableaux décrivent les capacités retournées par probe() sur un hôte où le backend est disponible. Une primitive du système d'exploitation n'est jamais une garantie Warden tant que le backend ne l'annonce pas et ne sait pas abaisser le plan exact.

État des implémentations

NomCiblePlateformeÉtat dans WardenFrontend produit actuel
posixexécutable hôtePOSIXenregistré et sélectionnableCLI, C, Python, Node
bubblewrapexécutable hôteLinux amd64/arm64enregistré et sélectionnableCLI, C, Python, Node
seatbeltexécutable hôteApple Silicon, macOS 15+enregistré et sélectionnableCLI, C, Python, Node
apple-containerimage OCI scelléeApple Silicon, macOS 26 ; linux/arm64 natif ou linux/amd64 via Rosetta attestéedriver détectable, mais inéligible au budget ABI 10 : API couplée et cgroupfs mutable par rootAPI de driver historique seulement
apple-container-bubblewrapimage OCI scellée, helper invité injectémême hôte et plateformes ; bundle TCB arm64 attestébudget ABI 10 exact : VM service-attestée, cgroup workload Warden protégé et observéCLI, C, Python, Node via TrustedRunSpec
apple-containerizationext4 OCI Warden scelléeApple Silicon, macOS 26 ; linux/arm64 natif ou linux/amd64 via Rosetta attestéedriver Swift direct, noyau/maelys-init Warden, Seatbelt hôte deny-defaultCLI, C, Python, Node via TrustedRunSpec
apple-containerization-bubblewrapmême ext4 scellée, Bubblewrap Warden injectémêmes hôte et plateformesmême VM directe et même PID 1 ; ajoute READ/WRITE récursif via le seam invité communCLI, C, Python, Node via TrustedRunSpec
krunimage OCI scellée, ext4 déterministeApple Silicon ; linux/arm64backend ABI 10 auto-découvert ; MI/6, VM exacte, cgroup-v2, réseau NONE ou proxy Egress, racine éphémèreCLI, C, Python, Node via TrustedRunSpec
krun-bubblewrapmême ext4 scellée, Bubblewrap Warden injectéApple Silicon ; linux/arm64mêmes limites ABI 10 sur tout le sous-arbre Bubblewrap ; READ/WRITE récursif et procfs neufCLI, C, Python, Node via TrustedRunSpec

maelys_executor_create_default() découvre les drivers optionnels Apple puis Krun. Pour Apple Container, il enregistre apple-container, puis le composite seulement si le bundle invité installé est sûr et attestable. Ce bundle appartient à Warden : bwrap, le relay/bootstrap arm64 et le probe amd64 ne viennent jamais de l'image OCI. Une absence ou une mutation du bundle retire le composite ou fait échouer l'artefact ; elle ne déclenche aucun fallback vers le candidat simple.

Les candidats apple-containerization et apple-containerization-bubblewrap forment une branche distincte : ils ne dépendent pas du CLI Apple Container. Il consomme le store d'artefacts OCI et l'ext4 déterministe de Warden, un noyau brut scellé et maelys-init via MI/6. Il ne prend part à la sélection que lorsque driver, noyau et init sont tous disponibles. Il livre linux/arm64 natif ou linux/amd64 traduit, captured stdio et les réseaux NONE/IP_NONE ; Egress n'est pas annoncé. Le candidat simple n'annonce pas FS_ACCESS_RULES. Le composite ajoute seulement les règles READ/WRITE récursives ; Bubblewrap, injecté dans l'ext4 et couvert par son seal, est lancé par maelys-init après un preflight borné. Son processus possède directement la VM et annonce donc PROCESS_TREE_LIFECYCLE, contrairement aux deux candidats utilisant le service Apple. Son VMM est entouré d'un profil Seatbelt hôte deny-default dont les refus fichier et réseau sont prouvés avant sélection ; l'entitlement Virtualization reste une permission d'exécution, pas cette garantie. Le planner ne lui attribue aucune préférence cachée : force et coût statiques sont alignés sur apple-container simple.

Les vues hôte du candidat direct sont abaissées dans l'unique device VirtioFS virtiofs, sous des noms privés liés au plan. maelys-init valide puis bind-monte uniquement les entrées admises et retire la vue de staging avant READY. Comme la racine ext4 est read-only, /workspace doit déjà exister dans l'image pour utiliser --ro ou --rw; --rootfs-only reste indépendant de ce point de montage.

Le backend Krun possède l'adaptateur maelys_executor_backend_ops, le protocole MWDP/1, un artefact OCI→ext4 scellé et un lifecycle borné. Depuis 0.33.0, il charge exactement le même noyau brut arm64 que Containerization direct via krun_set_kernel. La bottle Warden external-kernel-only supprime entièrement le chemin libkrunfw depuis 0.34.0. Noyau et manifeste sont épinglés et revalidés avant la création de la VM, sans fallback. krun annonce la racine ext4 read-only, les mappings VirtioFS, tmpfs, l'absence de réseau et la frontière VM. krun-bubblewrap ajoute uniquement FS_ACCESS_RULES pour READ/WRITE récursif. Le matérialiseur remplace toujours /maelys-bwrap par le binaire du bundle signé et lie son digest au seal ; un binaire de l'image ne participe jamais. Le même contrat MWOCI/3 remplace et lie aussi le relay invité. Pour une MIR médiée, le relay ouvre uniquement AF_VSOCK CID hôte/port 4520 ; Egress conserve l'identité du principal, l'allowlist, DNS/SNI, les quotas et les receipts. Le pont AF_UNIX utilise un principal lié à l'endpoint sans secret. Les deux processus VMM Krun sont en outre confinés par un profil Seatbelt hôte deny-default attesté avant leur enregistrement.

Depuis 0.34.0, la release installe le runtime warden-headless (libkrun construit avec blk,external-kernel-only, sans libkrunfw). Le backend sait aussi relire l'ancien manifeste upstream-gpu, dont la fermeture comprend en plus libepoxy, virglrenderer et MoltenVK. Les deux variantes passent par le même compilateur Seatbelt : sa table de chemins est construite depuis la fermeture réellement admise, puis chaque entrée est canonisée, épinglée et injectée comme paramètre SBPL. Le profil GPU n'est donc pas perdu ; il n'est simplement plus la dépendance par défaut de Warden.

TrustedRunSpec n'est pas une API réservée aux VM. Il accepte deux sortes de TargetSpec : local et oci. Avec une cible locale, le planner peut choisir POSIX, Seatbelt ou Bubblewrap selon le DeploymentContext, la SelectionPolicy et les capacités requises par la MIR. Avec une cible OCI, il considère les candidats Apple et, lorsque son paquet optionnel est présent, le candidat Krun pour linux/arm64. La CLI, la façade C et les SDK utilisent le même chemin RunSpec ; seul le receipt de sortie local reste v1 par défaut pour compatibilité produit.

Architecture comparée

POSIX, Seatbelt et Bubblewrap exécutent un binaire du système hôte. Les deux candidats Apple acceptent exclusivement un TargetSpec OCI : ils ne convertissent jamais un exécutable local en image. apple-container-bubblewrap est un candidat composite unique, préparé et reçu comme tel ; ce n'est pas un fallback tardif du candidat simple.

Apple Container sélectionne explicitement une variante linux/amd64 et active Rosetta dans sa VM arm64 uniquement si le driver en fournit une preuve stable. Le candidat direct applique la même règle avec son propre probe Containerization. L'absence de Rosetta rejette le candidat avant prepare ; Warden ne l'installe jamais. Krun sur Apple Silicon n'est pas une alternative amd64 : libkrun n'expose pas le partage Rosetta de Virtualization.framework.

Dans le composite visé, linux/amd64 ne signifie pas « toute la VM devient amd64 ». Le driver, le noyau Linux invité, le Bubblewrap statique injecté et le relay Egress restent arm64. Bubblewrap applique l'isolation nativement, puis binfmt_misc dirige seulement l'entrypoint amd64 vers Rosetta. La compatibilité de plateforme est évaluée séparément du masque de capacités MIR : un backend peut posséder EXECUTION tout en refusant une plateforme ou une traduction indisponible.

Masques de capacités ABI 10

Capacité ExecutorPOSIXBubblewrapSeatbeltApple ContainerApple + BwrapKrunKrun + BwrapContainerizationContainerization + Bwrap
EXECUTIONouiouiouiouiouiouiouiouioui
FILESYSTEMnonouiouiouiouiouiouiouioui
FS_ACCESS_RULESnonREAD/WRITE/DENY ; récursif, ou littéral pour fichiersREAD/WRITE littéral ou récursifnonREAD/WRITE récursif seulementnonREAD/WRITE récursif seulementnonREAD/WRITE récursif seulement
FS_DENY_READnonouinonnonnonnonnonnonnon
FS_PROTECT_CREATEnonnonnonnonnonnonnonnonnon
FS_VIEW_MAPPINGnonouinonouiouiouiouioui, /workspaceoui, /workspace
FS_TMPFSnonouinonouiouiouiouioui, /tmpoui, /tmp
NETWORK_NONEnonouiouiouiouiouiouioui, sans vue hôteoui, sans vue hôte
NETWORK_IP_NONEnonouiouinonnonnonnonouioui
NETWORK_EXTERNAL_CHANNELnonouiouinonnonnonnonnonnon
NETWORK_MEDIATED_PROXYnonouiouiouiouiouiouinonnon
SYSCALL_FILTERnonouinonnonnonnonnonnonnon
UNIX_SOCKET_CONTROLnonouinonnonnonnonnonnonnon
PROCESS_CONFINEMENTnonouiouiouiouiouiouiouioui
PROCESS_TREE_LIFECYCLEnonnonnonnonnonouiouiouioui
ROOT_EPHEMERAL_WRITEnonnonnonnonnonouiouiouioui
RESOURCE_VM_LIMITSnonnonnonnonouiouiouiouioui
RESOURCE_GUEST_CGROUPnonnonnonnonouiouiouiouioui

Le masque n'exprime pas la compatibilité de la cible, le mode d'I/O ou toutes les contraintes d'un plan. Le planner vérifie d'abord le type de cible et le masque ; prepare() refuse ensuite toute forme que le backend ne peut pas représenter exactement.

Budgets de ressources ABI 10

Chaque cible OCI porte un ExecutionBudget canonique, même lorsque l'utilisateur conserve les valeurs par défaut. krun, krun-bubblewrap, apple-containerization, apple-containerization-bubblewrap et apple-container-bubblewrap appliquent exactement le nombre de vCPU et la mémoire VM, puis installent dans l'invité un cgroup-v2 privé pour le workload :

CODE
maelys-init hors cgroup workload
  ├── relay Egress éventuel, hors comptabilité workload
  └── cgroup workload
       ├── memory.max + memory.swap.max=0
       ├── cpu.max
       ├── pids.max
       └── workload ou bwrap + tout son sous-arbre

Sur les backends Warden directs, le workload reçoit un nouveau namespace cgroup et une vue où la hiérarchie est absente. Sur le composite Apple service, le bootstrap reste hors du groupe, puis Bubblewrap expose cgroupfs en lecture seule et sans capabilities. Dans les deux cas, le workload rejoint son groupe avant execve. Le receipt v7 atteste valeurs demandées/appliquées, pics/événements observés, suppression du groupe et, pour une policy médiée, quotas/compteurs Egress. Le PID 1, le relay et les écritures d'une vue VirtioFS RW ne sont pas inclus dans les compteurs cgroup. Le receipt conserve une entrée de contrôleur hôte candidat-spécifique, mais elle reste explicitement non appliquée : les gates physiques macOS 26 ont démontré qu'un RLIMIT_CPU fini bloque libkrun 1.19.4.

Le candidat simple Apple Container n'annonce aucun des deux bits : son workload root peut modifier le cgroup du service. Le composite annonce les deux. Son driver relit le dimensionnement VM dans inspect; le bootstrap Warden applique les limites workload, observe les pics/événements, détruit le groupe et remet le PID 1 dans le domaine suivi par Apple avant de produire MWAR/2. Le spike physique documente la preuve qui a conduit à cette séparation, sans fork du service Apple.

Rapport de sélection structuré

Le maelys_candidate_report_t opaque sépare désormais les quatre informations qu'un intégrateur doit pouvoir traiter sans analyser une chaîne d'erreur :

CODE
required capabilities
├── required by MIR
├── required by target
└── required by resource budget

candidate
├── available capabilities
└── missing = required & ~available

Les getters maelys_candidate_report_required_capabilities(), maelys_candidate_report_required_capabilities_from(), maelys_candidate_report_available_capabilities_at() et maelys_candidate_report_missing_capabilities_at() conservent les masques pour les outils. maelys_backend_capability_name() fournit leur nom canonique. Une contrainte de cible qui n'est pas un bit reste exprimée par le code de rejet target-kind ou target-platform ; elle n'est pas artificiellement transformée en capacité.

Filesystem

SémantiqueBubblewrap hôteSeatbelt hôteApple ContainerApple + BwrapKrunKrun + BwrapContainerization direct
racine d'exécutionvue vide reconstruitefilesystem hôte filtré par SBPLracine OCI read-onlyracine OCI read-only, puis vue Bubblewrap invitéeext4 scellée RO ou overlay tmpfs éphémèreext4 scellée puis overlay tmpfs, avant la vue Bubblewrapmême ext4 Warden scellée RO ou overlay tmpfs éphémère
READ/WRITE portablerécursif ; littéral pour fichiers régulierslittéral ou récursifrefusérécursif seulementrefusérécursif seulementrefusé
chemin absentERROR/SKIPERROR/SKIPsans objetERROR/SKIPsans objetERROR/SKIPsans objet
DENY prioritaireoverlay final mode 000refusérefusérefusérefusérefusérefusé
mapping RO/RWoui, bind mount épinglérefuséoui, VirtioFS épingléoui, VirtioFS extérieuroui, VirtioFS épingléoui, avant le sandbox intérieurun répertoire épinglé vers /workspace
tmpfsouirefuséouiouiouioui/tmp uniquement
protection de création d'un chemin absentrefuséerefuséerefuséerefuséerefuséerefuséerefusée

Le candidat Apple simple sait construire la vue OCI avec des mappings VirtioFS et des tmpfs, mais il refuse toute liste fs_rule. Le composite ajoute des bind mounts Bubblewrap dans l'invité. Un bind mount porte sur un sous-arbre, donc seules les règles READ/WRITE de portée RECURSIVE ont aujourd'hui un abaissement exact ; une règle LITERAL prétendrait protéger le chemin sans accorder ses descendants, ce que cette construction ne garantit pas.

La case FS_ACCESS_RULES est un préfiltre de famille, pas la promesse que toutes les variantes sont acceptées. Après le masque de capacités, prepare() vérifie chaque règle et refuse LITERAL, DENY et PROTECT_CREATE. Le composite installe bien un procfs neuf dans son PID namespace, mais cette garantie de visibilité des processus est indépendante de la compilation des règles filesystem et du filtrage d'appels système. Warden n'annonce donc toujours ni DENY, ni FS_PROTECT_CREATE, ni filtre seccomp intérieur.

Exemples concrets :

CODE
READ RECURSIVE /workspace

autorise /workspace, /workspace/src et tout ce qui se trouve dessous. Un --ro-bind /workspace /workspace représente naturellement ce sous-arbre.

CODE
READ LITERAL /workspace

autoriserait l'objet répertoire /workspace, mais pas /workspace/src. Un bind mount du répertoire expose nécessairement ses descendants : le composite le refuse donc. Pour un fichier régulier, cette distinction n'existe pas, mais le compilateur composite est volontairement uniforme et n'accepte aujourd'hui que RECURSIVE.

CODE
READ RECURSIVE /workspace
DENY RECURSIVE /workspace/.secrets

demande qu'une interdiction plus spécifique gagne sur l'autorisation parente, y compris face aux renommages. Le compilateur Bubblewrap invité ne construit pas encore l'overlay final nécessaire et refuse DENY.

CODE
DENY /workspace/future-secret  missing=PROTECT_CREATE

demande de protéger un chemin qui n'existe pas encore contre sa création future. On ne peut pas bind-mounter un objet absent ; il faut protéger ou reconstruire son parent. Cette garantie n'est pas implémentée.

Réseau

NETWORK_INHERIT est un choix explicite de réseau direct et ne requiert aucun bit de capacité, mais reste soumis aux contraintes de prepare(). Les deux candidats Krun le refusent tant que leur contrat ne fournit aucune carte réseau. Il ne doit jamais être utilisé comme fallback d'un mode plus strict.

Mode / frontendPOSIXBubblewrapSeatbeltApple ContainerApple + BwrapKrunKrun + BwrapContainerizationContainerization + Bwrap
direct (INHERIT)ambiantnamespace hôte partagérègle (allow network*)réseau Apple Containerréseau Apple Containerrefusérefusérefusérefusé
aucun (NONE)refusénetns + seccompSBPL denyaucune attache réseauaucune attache + netns intérieuraucune carte réseau, TSI désactivémême frontière VM ; pas d'unshare réseau intérieuraucune interface ; aucun mapping hôtemême VM sans interface
aucun IP (IP_NONE)refusénetnsSBPL denyrefusérefusérefusérefuséaucune interface ; /workspace admisidem
médié fd4refusécanal Unix datagram + SCM_RIGHTSmême contrat sous SBPLrefusérefusérefusérefusérefusérefusé
médié proxyrefusérelay loopback privé → AF_UNIX → Egressport loopback Egress exactrelay invité → UDS/vsock → Egress hôtemême pont, relay hors Bubblewrap intérieurrelay invité → AF_VSOCK 4520 → Egress hôtemême pont, relay hors Bubblewrap intérieurnon livrénon livré

NONE et IP_NONE sont deux contrats différents :

  • NONE interdit le réseau IP et les sockets Unix adressées par chemin ;
  • IP_NONE interdit le réseau IP mais conserve les sockets Unix explicitement rendues accessibles par le plan, par exemple un canal de contrôle local.

Apple Container sait satisfaire NONE en créant le conteneur sans attache réseau. Il n'annonce pas IP_NONE, car un socket Unix hôte ne peut pas être transporté comme un socket vivant par un mapping VirtioFS à travers la VM. Le composite ajoute un namespace réseau Linux intérieur, mais cela ne crée pas le canal Unix trans-VM exigé par IP_NONE; il est donc refusé lui aussi plutôt que d'être silencieusement remplacé par le contrat plus restrictif NONE.

Le frontend fd 4 natif repose sur un datagramme Unix de contrôle puis sur SCM_RIGHTS, qui transmet un descripteur ouvert entre processus du même noyau. Un descripteur de socket macOS ne peut pas être installé de cette façon dans la table de descripteurs d'un processus Linux invité. Les deux backends Apple refusent donc NETWORK_EXTERNAL_CHANNEL.

Bubblewrap hôte fonctionne parce qu'un namespace n'est pas un noyau :

Le workload Bubblewrap voit une autre racine, d'autres PID et éventuellement un autre namespace réseau, mais ses descripteurs sont toujours des objets du noyau Linux hôte. Warden peut donc installer le datagramme de contrôle sur son fd 4 et Egress peut lui transmettre un stream avec SCM_RIGHTS.

Apple Container ajoute au contraire une frontière de noyau :

Le pont proxy transporte des octets, pas un fd macOS. Même avec Bubblewrap dans l'invité, le passage macOS→Linux reste nécessaire avant que des opérations SCM_RIGHTS internes au noyau invité deviennent possibles.

Un futur frontend Maelys-aware invité devra demander la connexion par vsock à un broker invité, puis recevoir un stream Linux relayé dont Egress garde le pair hôte. Ce sera un nouveau pont explicite ; ce n'est pas le contrat SCM_RIGHTS natif actuel. Les deux backends Apple Container proposent le frontend proxy : le relay invité est aveugle au protocole et à la policy, tandis que Egress reste l'unique moteur d'authentification, DNS/pinning, connexion, relay et receipts. Le candidat Containerization direct refuse aujourd'hui toute médiation plutôt que de prétendre réutiliser ce pont sans l'avoir physiquement prouvé.

Processus, syscalls et lifecycle

  • Bubblewrap et Seatbelt annoncent que leur politique suit les descendants.
  • Apple Container applique la décision au domaine conteneur/VM et le composite ajoute l'héritage de la politique Bubblewrap dans l'invité.
  • Seul Bubblewrap hôte annonce actuellement un filtre syscall : filtre BPF vérifié par architecture et adapté au mode réseau.
  • Les deux candidats Krun et apple-containerization annoncent PROCESS_TREE_LIFECYCLE. Le candidat direct le prouve avec son propre maelys-init PID 1, ses process groups et la destruction de sa VM. POSIX, Bubblewrap, Seatbelt et les deux candidats Apple Container ne l'annoncent pas ; un arrêt borné existe, mais Warden ne prétend pas y prouver la disparition de tout descendant hostile ni, après la mort forcée du driver Apple, la suppression du domaine VM par le service Apple.

PROCESS_CONFINEMENT et PROCESS_TREE_LIFECYCLE sont donc volontairement distincts : propagation de politique n'est pas supervision exhaustive.

I/O et environnement des candidats VM

Les candidats Apple Container, Containerization direct et Krun refusent actuellement :

  • l'environnement hôte hérité ; utilisez EMPTY ou EXPLICIT ;
  • ISOLATED_DUPLEX et son protocole workload fd 3 ;
  • le frontend réseau workload fd 4.

Le candidat Containerization direct n'accepte en plus que CAPTURED_STDIO : stdin, stdout et stderr empruntent trois connexions vsock distinctes. Sa console noyau est privée et ne rejoint stderr qu'en tant que diagnostic de boot échoué.

Le contrôle Warden↔driver utilise lui aussi un fd numéroté 4, mais dans le processus driver hôte. Warden y installe une extrémité d'un AF_UNIX/SOCK_STREAM avant exec du driver. Il ne transmet pas ce fd au guest. Le fd 3 applicatif et le fd 4 réseau sont, eux, des conventions de la table de descripteurs du workload natif, activées seulement par les modes qui les demandent. Le schéma complet est dans warden-driver-protocol.md.

Comment lire une sélection

Une absence de runtime, une preuve différente, une capacité manquante ou une forme non représentable produit une erreur explicite. Aucun chemin confiné ne retombe sur POSIX, sur le réseau direct ou sur le candidat Apple simple.