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

ADR-047 — Principes de construction de la libc Air

Statut : Accepté (2026-06-27, validé par le BDFL). Companion d’ADR-046 (libc Air — fondateur) : ADR-046 pose le cadre (artefact, layering, doctrine), le présent ADR pose la méthode de construction et les invariants non négociables. Décline les Principes d’ingénierie 2 (arithmétique défensive), 3 (chaînes/buffers/encodages) et 5 (optimiser après mesure), la discipline // SAFETY: d’ADR-021, et le standard de doc d’ADR-027.

Catégorie : Architecture / Méthode (transverse à toute la libc). Toute évolution passe par un RFC (ADR-015).

Contexte

La libc Air est du code userland critique : tout programme C/C++ exécuté sur Air en dépend. ADR-046 a décidé qu’on la bâtit en Rust pur, sur la couche 1 uniquement, en s’inspirant de musl. Cet ADR fixe comment on s’en inspire, sous quel regard, et les garanties qu’on ne transige jamais. Le regard est SÉCURITÉ / PROTECTION, jamais la performance d’abord.

Décision

P1 — musl = périmètre (surface), pas implémentation

On prend l’inventaire des symboles exportés de musl — épinglé à une version précise — comme liste cible de ce que la libc doit fournir. Implémentation en Rust pur, selon nos principes. Les signatures et la sémantique restent dictées par les standards (The Open Group / POSIX + ISO C + Linux man-pages section 2 + kernel/UAPI, « kernel = bible » d’ADR-046 D5). musl = le quoi, les standards = le contrat, nos principes = la manière.

P2 — Audit de musl par famille, à double tranchant, artefact vivant

Avant d’implémenter une famille, on audite la famille correspondante de musl. Double but : (i) repérer ce qui est bien fait → s’en inspirer ; (ii) être critique → noter les faiblesses. L’audit inventorie, par famille :

  • les structures internes ;
  • les variables globales publiques et privées/opaques ;
  • comment l’accès aux ressources partagées est protégé en concurrence/multi-thread ;
  • la proportion d’assembleur, , et pourquoi.

L’audit est un artefact versionné (un document par famille, gabarit en annexe A), épinglé à la version de musl auditée (reproductibilité de la référence). On audite par famille, juste avant de l’implémenter (pas tout d’un bloc — l’audit vieillirait).

P3 — Regard sécurité, jamais performance d’abord

On audite et on code sous l’angle protection / sécurité / accès partagé, en permanence. On préfère du code supplémentaireimpact performance assumé — qui garantit qu’aucun cas critique ne survient, plutôt que l’optimisation. Aucune optimisation n’est entreprise avant une implémentation robuste de TOUTE la librairie, tout en tête (Principe 5). L’optimisation, si elle a lieu, vient après, mesurée et justifiée.

P4 — Les quatre modes de défaillance (invariants vérifiables)

« Ne jamais être pris en échec » se vérifie fonction par fonction. Un context switch pur (préemption) n’est pas un échec si le verrouillage est correct ; les vrais modes sont :

  1. Async-signal-safety — un signal interrompt le thread en plein milieu : le handler ne doit ni observer ni corrompre un état incohérent.
  2. Fork-safetyfork() en multi-thread : l’enfant hérite de tous les locks mais d’un seul thread (cf. deadlock malloc/atfork) ; états cohérents via pthread_atfork/conception.
  3. Cancellation-safetypthread_cancel aux points d’annulation : ni fuite ni corruption.
  4. Réentrance / état mutable partagé non gardé — le péché capital de la libc C : buffers statiques (strtok, gmtime, getpwnam…), environ, la locale, le registre des FILE*, l’arène malloc, la liste atexit, TZ

Chaque famille produit l’inventaire de son état mutable partagé, sa protection (lock ? TLS ? rien ?), et sa sûreté face aux 4 modes.

P5 — Grille sécurité élargie

Au-delà de la concurrence, l’audit et l’implémentation traquent aussi : overflow entier (Principe 2 — ex. calloc(n,size)), bornes & format (%n, format-string, Principe 3), TOCTOU sur les chemins, injection via environ/locale, effacement des secrets (memzero à la libération des données sensibles).

P6 — Doctrine de l’assembleur : minimiser ; chaque bloc = trou de confiance

Notre architecture absorbe la plupart des raisons d’asm de musl : les syscalls sont en couche 0, les atomics = core::sync::atomic, la TLS = la cible/std, les str/mem = Rust sûr. On minimise donc l’asm. Résidu réellement incompressible attendu : setjmp/longjmp (sauvegarde des registres callee-saved + adresse de retour), le crt0/_start (entrée avant que le Rust ne tourne), éventuellement ucontext. Chaque bloc asm est : justifié (// SAFETY:, ADR-021), minimal, audité face aux 4 modes, et ne touche aucun état partagé non gardé.

P7 — La règle de la fonction-sœur SAFE

Quand POSIX impose un prototype qui manipule une ressource mais retourne void / ne fournit aucun canal d’erreur (ex. void rewind(FILE*) jette l’erreur de fseek ; void setbuf(...) jette celle de setvbuf ; tzset, perror…) :

  • (a) on implémente la version POSIX pure, fidèle au contrat — donc avec son DÉFAUT de reportingmais robuste en dessous : elle est muette, pas fragile (elle ne se corrompt jamais elle-même) ;
  • (b) on implémente une fonction-sœur SAFE qui remonte l’erreur (in-band, ADR-046 D4), même hors-standard, nommée air_<nom> (préfixe air_) ;
  • (c) on documente le défaut qu’on a identifié et le risque pour le développeur, et on suggère la version SAFE (doc ADR-027 : pourquoi + piège) ;
  • (d) règle systématique et homogène (même modèle d’erreur in-band partout), pas ad hoc.

Le développeur C garde le choix : POSIX pur (compatible, avec le risque documenté) ou la version Air SAFE.

Conséquences

  • Cycle par famille : audit (artefact)implémentation Rustdoc (standard faisant autorité cité + défauts POSIX relevés + sœurs SAFE).
  • Doublement partiel de la surface (les sœurs SAFE) assumé : la valeur est le choix informé et « la libc fait ce qu’elle dit ».
  • Aucune optimisation avant complétude robuste (P3).
  • La couche 1 grandit au besoin d’un tier kernel-faithful (ADR-046 D2) au fil des familles.

Alternatives écartées

  • Copier l’implémentation de musl (C, asm, état global) : rejeté (P1 ; ADR-046 D1).
  • Optimiser au fil de l’eau : rejeté (P3 / Principe 5).
  • Se limiter à POSIX strict (pas de sœurs SAFE) : rejeté (P7 — laisserait le développeur sans recours sûr face aux défauts connus de POSIX).
  • Auditer toute la surface puis tout implémenter : rejeté (P2 — l’audit vieillirait avant usage).

Annexe A — Gabarit d’audit par famille

Famille : <ex. stdio>            Version musl auditée : <tag/commit>
Fonctions de la famille (surface cible) : <liste>

1. Structures internes : <…>
2. Globales publiques : <…>   |   Globales privées/opaques : <…>
3. État mutable partagé + protection :
   | ressource | protection (lock/TLS/rien) | async-sig | fork | cancel | réentrance |
4. Assembleur : où / pourquoi / peut-on l'éviter chez nous (couche 0 / core / std) ?
5. Grille sécurité élargie : overflow | bornes/format | TOCTOU | injection | secrets
6. Fonctions `void`→ sœur SAFE `air_*` à créer : <liste + défaut POSIX constaté>
7. Bien fait (à reprendre) : <…>
8. Faiblesses (à ne pas reproduire) : <…>
9. Décisions Air pour cette famille : <…>