Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec couche 0 — Module io_uring, Temps 3f : confinement (sandbox)

Spécification technique — Version 1.0 (cible kernel : Linux 6.12 LTS)

Position. Le Temps 3f rend opérationnelle la décision de soundness S3 du document maître : confiner un ring pour qu’il ne puisse émettre que des opérations explicitement autorisées. Sous-module air-sys-syscall::io_uring::sandbox. Repose sur le flag IORING_SETUP_R_DISABLED (Temps 1), et les register opcodes REGISTER_RESTRICTIONS (11) et ENABLE_RINGS (12). C’est la brique io_uring du modèle de capabilities d’Air (ADR-001 AirCom, ADR-010 entitlements signés), en défense en profondeur avec family-security (seccomp/Landlock).


1. Pourquoi confiner un ring ?

io_uring exécute des opérations (read, openat2, connect…) qui, soumises par le ring, ne passent pas par l’interface syscall classique. Un filtre seccomp qui bloque le syscall openat2 n’empêche pas un IORING_OP_OPENAT2 soumis via le ring. Historiquement, c’est une voie de contournement des bacs à sable. La réponse propre : restreindre le ring lui-même.

Un ring confiné ne peut soumettre que les opcodes, register-ops et drapeaux SQE explicitement mis en liste blanche, et le kernel l’impose. Même un service compromis ne peut pas élargir ce que son ring autorise.

C’est la matérialisation, au niveau io_uring, des entitlements déclarés dans le manifeste signé d’un .airservice/.airapp (ADR-010), et le pendant kernel des capabilities AirCom (ADR-001).


2. Le mécanisme en trois temps

  1. Créer désactivé : le ring est créé avec IORING_SETUP_R_DISABLED — il existe mais ne peut rien soumettre.
  2. Restreindre : REGISTER_RESTRICTIONS applique une liste de restrictions (liste blanche). Possible uniquement tant que le ring est désactivé.
  3. Activer : REGISTER_ENABLE_RINGS active le ring. Après activation, les restrictions sont immuables — on ne peut plus les assouplir.

L’ordre est imposé par le kernel et reflété par l’API : on ne peut pas restreindre un ring déjà actif, ni soumettre avant activation.


3. API

3.1 RestrictionSet

#![allow(unused)]
fn main() {
/// Liste blanche de ce qu'un ring confiné pourra faire. Default-deny : ce qui
/// n'est pas explicitement autorisé est refusé par le kernel.
#[derive(Debug, Clone, Default)]
pub struct RestrictionSet { /* ... */ }

impl RestrictionSet {
    pub fn new() -> Self;
    /// Autorise un opcode de soumission (RESTRICTION_SQE_OP).
    pub fn allow_op(self, op: IoUringOpcode) -> Self;
    /// Autorise un register opcode (RESTRICTION_REGISTER_OP).
    pub fn allow_register(self, op: RegisterOp) -> Self;
    /// Drapeaux SQE autorisés (RESTRICTION_SQE_FLAGS_ALLOWED).
    pub fn allow_sqe_flags(self, flags: SqeFlagSet) -> Self;
    /// Drapeaux SQE requis sur CHAQUE soumission (RESTRICTION_SQE_FLAGS_REQUIRED).
    pub fn require_sqe_flags(self, flags: SqeFlagSet) -> Self;

    /// Conversion depuis un jeu d'entitlements (couche supérieure).
    /// Fournie comme point d'intégration ; la politique vit en couche 5.
    pub fn from_entitlements(/* … */) -> Self;
}
}

3.2 Intégration au builder (Temps 1)

#![allow(unused)]
fn main() {
// IoUringBuilder::restrict(&[Restriction]) → R_DISABLED + REGISTER_RESTRICTIONS.
let mut ring = IoUringBuilder::new(entries)
    .restrict(restrictions.as_slice())   // ring créé désactivé
    .build()?;
ring.enable()?;                           // REGISTER_ENABLE_RINGS ; immuable ensuite
// À partir d'ici, toute soumission hors liste blanche échoue (-EACCES/-EINVAL).
}
  • build() avec restrict non vide laisse le ring désactivé ; enable() (Temps 1 §5.2) l’active. Toute tentative de submit_* avant enable() ⇒ erreur. Toute tentative de re-restreindre après enable() ⇒ erreur.
  • Sémantique default-deny : si RestrictionSet contient au moins un allow_op, seuls les opcodes listés sont permis (le reste est refusé par le kernel). Idem pour les register-ops.

4. Les quatre types de restriction

Type (io_uring_register_restriction_op)RestrictionSetEffet
RESTRICTION_SQE_OP (1)allow_opliste blanche des opcodes soumissibles
RESTRICTION_REGISTER_OP (0)allow_registerliste blanche des register-ops
RESTRICTION_SQE_FLAGS_ALLOWED (2)allow_sqe_flagsdrapeaux SQE autorisés
RESTRICTION_SQE_FLAGS_REQUIRED (3)require_sqe_flagsdrapeaux imposés à chaque SQE

Exemple — un service AirCom « réseau seul » :

#![allow(unused)]
fn main() {
let r = RestrictionSet::new()
    .allow_op(IoUringOpcode::Socket)
    .allow_op(IoUringOpcode::Connect)
    .allow_op(IoUringOpcode::Send)
    .allow_op(IoUringOpcode::Receive)
    .allow_op(IoUringOpcode::SendZeroCopy)
    .allow_op(IoUringOpcode::Close);
// Le ring ne pourra jamais émettre openat2, unlinkat, etc. — garanti kernel.
}

5. Lien avec le modèle de sécurité d’Air

  • ADR-010 (entitlements signés) : le manifeste d’un .airservice déclare ses entitlements ; un composant de couche 5 (air-launchd/air-trust) traduit ces entitlements en RestrictionSet et fournit au service un ring déjà confiné. La couche 0 fournit le mécanisme (RestrictionSet, restrict, enable) ; la politique vit en couche supérieure — séparation nette.
  • ADR-001 (AirCom) : un service ne reçoit que les capabilities (FD) de son manifeste, et son ring ne peut émettre que les opérations correspondantes — double verrou (FD non falsifiables + ring restreint).
  • family-security (seccomp/Landlock) : les restrictions io_uring complètent seccomp/Landlock, elles ne les remplacent pas. seccomp filtre les syscalls, Landlock les accès FS, les restrictions io_uring les opérations du ring — défense en profondeur. C’est précisément ce qui ferme la voie de contournement évoquée en §1.
  • Personality (Temps 3a) : combinée aux restrictions, permet d’exécuter des ops sous une identité contrôlée — moindre privilège.

Note hors périmètre. Un filtrage encore plus fin par programme BPF (IORING_REGISTER_BPF_FILTER) est postérieur à 6.12 ; il pourra enrichir ce confinement plus tard, par détection runtime, sans amendement.


6. Récapitulatif

ÉlémentFaçade
SETUP_R_DISABLEDIoUringBuilder::restrict (crée désactivé)
REGISTER_RESTRICTIONS (11)IoUringBuilder::restrict (applique la liste)
REGISTER_ENABLE_RINGS (12)IoUring::enable
4 restriction_opRestrictionSet::{allow_op, allow_register, allow_sqe_flags, require_sqe_flags}

7. Types ajoutés / partagés

Nouveaux : RestrictionSet, RegisterOp (énumération des register-ops autorisables), SqeFlagSet. Réutilise : Restriction et IoUringOpcode (déclarés au Temps 1), IoUringBuilder::restrict, IoUring::enable. Point d’intégration from_entitlements (type d’entitlements défini en couche supérieure).


8. Stratégie de tests

  • Intégration : créer un ring R_DISABLED, appliquer une liste blanche, enable(), vérifier qu’un opcode autorisé passe et qu’un opcode non autorisé échoue (-EACCES/-EINVAL) ; idem register-ops ; require_sqe_flags imposé (une soumission sans le drapeau échoue) ; soumission avant enable refusée ; re-restriction après enable refusée (immuabilité).
  • Sécurité : test démontrant qu’un openat2 soumis via un ring « réseau seul » est refusé même si le process a le droit d’ouvrir le fichier (preuve de la défense en profondeur vs seccomp).
  • Property-based : pour tout sous-ensemble d’opcodes autorisés, exactement ceux-là passent.
  • Couverture 100 % lignes + branches.

9. Décisions de fond émergées au Temps 3f

  1. Confinement = mécanisme couche 0, politique couche 5RestrictionSet et le flux restrict → enable sont neutres ; la traduction des entitlements signés (ADR-010) vit en couche supérieure.
  2. Default-deny — dès qu’une liste blanche d’opcodes est posée, tout le reste est refusé par le kernel ; pas de demi-mesure.
  3. Immuabilité après enable — garantie par le kernel et reflétée par l’API ; impossible d’assouplir un ring confiné.
  4. Défense en profondeur explicite — les restrictions io_uring complètent seccomp/Landlock et ferment la voie de contournement du filtre syscall.
  5. BPF filtering noté mais hors périmètre (post-6.12).

10. Travail à reprendre

Spec suivante et dernière du module : io-uring-4-raw.md (accès niveau 1 au protocole d’anneau : RawSubmissionQueueEntry/RawCompletionQueueEntry, manipulation directe des SQE/CQE, opérations non encore wrappées, # Safety extensif). Traduction anglaise globale après validation des documents français.


Licence du document : MPL 2.0 Statut : Spécification technique du Temps 3f (confinement) du module air-sys-syscall::io_uring, cible kernel 6.12 LTS.