Aller au contenu

Vérifier une chaîne de compilation reproductible

Vérifier une chaîne de compilation reproductible

Section intitulée « Vérifier une chaîne de compilation reproductible »

Un binaire signé prouve qui l’a distribué, pas comment il a été fabriqué. La compilation reproductible permet de détecter une divergence entre le code examiné et l’artefact livré, mais elle ne transforme pas magiquement un compilateur compromis en citoyen modèle.

→ Lire l’explication complète de la marmotte

L’attaque décrite dans Reflections on Trusting Trust reste structurante : un compilateur peut injecter une porte dérobée lors de la compilation d’un programme ciblé, puis s’auto-propager lorsqu’il compile sa propre source. L’audit du code source du programme et celui du compilateur ne suffisent alors plus. Le comportement malveillant n’apparaît dans aucun des deux dépôts. C’est la version informatique du fantôme qui signe le registre puis brûle le stylo.

La réponse technique est la compilation diversement double, ou DDC (Diverse Double-Compiling). On compile d’abord le compilateur candidat avec un compilateur de confiance indépendant, puis on utilise le résultat pour recompiler la source du compilateur candidat. Si les binaires obtenus sont identiques après normalisation définie, la source correspond vraisemblablement au compilateur distribué. Cette méthode détecte une auto-propagation cachée, sous conditions strictes : le compilateur de départ, la machine, l’éditeur de liens et les outils de post-traitement ne doivent pas partager la même compromission.

Le compromis central est net : gagner une preuve forte de correspondance entre sources et artefacts coûte de la déterminisation, de la diversité d’outillage et une discipline de publication. Ce coût est réel. Il interdit les scripts de build qui téléchargent « la dernière version », les horodatages décoratifs et les dépendances résolues au gré d’un miroir. La recommandation est de payer ce coût pour tout composant qui exécute du code dans un périmètre de confiance, produit des artefacts distribués ou porte des secrets. Pour un outil local jetable, la DDC complète est disproportionnée, mais un build verrouillé reste le minimum syndical.

La diversité ne signifie pas « deux conteneurs identiques lancés sur deux agents ». Deux compilations exécutant la même image, le même compilateur, le même cache et le même registre de dépendances ne créent aucune indépendance utile. Elles reproduisent la même erreur avec une chorégraphie plus coûteuse. Une vérification DDC crédible varie au moins l’implémentation du compilateur initial, l’environnement système ou l’architecture matérielle, selon ce que le langage permet.

Cette racine de confiance rejoint les sujets de cryptographie appliquée : une signature n’est fiable que si la clé, le logiciel qui l’emploie et le processus qui fabrique la donnée signée le sont aussi. Le mécanisme ne garantit pas l’absence de vulnérabilité dans la source, ni l’absence de compte mainteneur compromis. Il répond à une question précise : le binaire analysé provient-il bien des entrées annoncées et des transformations déclarées ?

Un build reproductible produit des octets identiques à partir des mêmes entrées, indépendamment du moment, du chemin de travail et, autant que possible, de la machine. « Fonctionnellement identique » ne suffit pas. Deux binaires qui donnent le même résultat mais diffèrent dans une section de débogage, un ordre d’archive ou un horodatage empêchent la comparaison cryptographique. Or sans comparaison d’octets, l’écart malveillant se cache dans le bruit.

Les sources d’entropie sont répétitives et peu glorieuses : date de compilation, répertoire absolu, ordre non déterministe d’un système de fichiers, locale, fuseau horaire, identifiant utilisateur, adresse mémoire intégrée par un générateur, archive tar dont les métadonnées varient, dépendance téléchargée sans empreinte. Vingt ans de builds apprennent que le fichier qui casse la reproductibilité est souvent généré par un script de vingt lignes, gardé vivant par pure inertie administrative.

Fixer SOURCE_DATE_EPOCH traite les horodatages pris en charge par les outils compatibles. Définir TZ=UTC, LC_ALL=C et un répertoire de travail stable traite une autre part du problème. Les chemins doivent être remappés via les options du compilateur, par exemple -ffile-prefix-map pour GCC (GNU Compiler Collection) et Clang. Les archives doivent trier leurs membres et neutraliser propriétaires, groupes et dates. Les dépendances doivent être épinglées par version et par empreinte SHA-256 (Secure Hash Algorithm sur 256 bits), jamais seulement par une plage de versions.

La reproductibilité exige aussi de distinguer les entrées légitimes des entrées accidentelles. La version du compilateur, l’éditeur de liens, les bibliothèques système, les drapeaux de compilation, les variables d’environnement autorisées et les générateurs de code appartiennent à l’identité du build. Une image de conteneur identifiée par un tag mutable, même baptisé stable, n’est pas une entrée figée. C’est une promesse marketing avec accès en écriture.

Comparer deux hashes ne protège pas contre une comparaison organisée par l’attaquant. Un acteur qui contrôle le pipeline CI/CD (Continuous Integration and Continuous Delivery), les deux agents de compilation et le stockage des résultats peut substituer le même artefact à chaque étape. La séparation des rôles compte donc autant que la commande de comparaison : un build de référence doit être exécuté sur une infrastructure, une identité et des identifiants de signature distincts.

Les caches de compilation méritent une méfiance particulière. Un cache distant accélère le build en réinjectant des objets déjà compilés. S’il accepte une clé de cache insuffisamment liée à la source, aux options, au compilateur et aux en-têtes, il devient un canal de substitution. Le cache doit être traité comme un dépôt d’artefacts non fiables : vérifier les empreintes, lier chaque entrée à l’intégralité des paramètres de compilation et pouvoir le purger. Le gain de quelques minutes ne justifie pas un mécanisme qui transforme un objet empoisonné en dépendance invisible.

La génération d’une SBOM (Software Bill of Materials), inventaire des composants logiciels, apporte de la traçabilité mais ne prouve pas la provenance de l’artefact. Une attestation de provenance lie un artefact à un build, à ses entrées et à l’identité qui l’a déclenché. Le cadre SLSA (Supply-chain Levels for Software Artifacts) formalise cette montée en maturité. Il ne remplace ni la reproductibilité ni l’audit des permissions du pipeline. Une attestation produite par un agent compromis documente avec une rigueur admirable le mensonge qu’il vient de fabriquer.

Le mécanisme ne garantit pas non plus que la dépendance source soit bénigne. Une bibliothèque signée, reproductible et parfaitement attestée peut contenir une porte dérobée dans son code. La vérification de chaîne réduit les substitutions non autorisées ; l’analyse de dépendances, la revue et le cloisonnement d’exécution traitent le reste. Les pages sur les menaces et l’architecture cyber complètent ce périmètre : une preuve de provenance n’est pas une politique de confiance.

La vérification doit partir d’un artefact publié, récupérer ses sources et ses métadonnées signées, puis reconstruire hors du pipeline producteur. Le vérificateur ne doit pas accepter les paramètres de build fournis par l’artefact lui-même. La version du compilateur, l’image système, les dépendances et les options doivent venir d’un manifeste versionné et signé séparément.

Voici un contrôle minimal pour une archive source produisant un binaire C. Il ne remplace pas une attestation complète, mais il expose les propriétés qui doivent être figées.

Fenêtre de terminal
# Utiliser un instant de référence fixé dans le manifeste du projet.
export SOURCE_DATE_EPOCH=1704067200
export TZ=UTC
export LC_ALL=C
# Éviter que le chemin local soit intégré au binaire de débogage.
export CFLAGS="-O2 -ffile-prefix-map=$PWD=/usr/src/project"
# Compiler sans télécharger de dépendance ni consulter de cache distant.
make clean
make all
# Comparer l'artefact obtenu à l'empreinte publiée et signée.
sha256sum -c dist/programme.sha256
# Une différence est un échec, pas une invitation à mettre à jour l'empreinte.
cmp -s build/programme dist/programme

La vérification doit échouer sur toute divergence, y compris une divergence jugée « cosmétique ». Accepter une liste d’exceptions non bornée ruine la propriété recherchée : une différence de métadonnées aujourd’hui devient une zone de camouflage demain. Les exceptions nécessaires doivent être documentées par type de section binaire, isolées dans un outil de normalisation versionné, et limitées à des champs dont l’absence d’effet exécutable est démontrée.

Les identités qui publient les sources, exécutent les builds et signent les attestations doivent être distinctes. Une clé de signature d’artefact ne doit pas pouvoir modifier le pipeline, et le compte qui modifie le pipeline ne doit pas pouvoir approuver seul une publication. Les clés privées de production résident dans un HSM (Hardware Security Module) lorsque le niveau de menace justifie cette protection, ou dans un service de signature à permissions minimales. Exporter une clé de signature dans une variable d’environnement est une solution temporaire qui a la longévité d’un cafard.

La rotation doit accepter une période de chevauchement : publier la nouvelle clé, signer les nouvelles métadonnées avec l’ancienne et la nouvelle clé, puis révoquer l’ancienne selon une procédure connue. Une révocation ne retire pas les artefacts déjà téléchargés ni les clés déjà copiées. Les consommateurs doivent vérifier une politique de validité datée et conserver les journaux de transparence ou les preuves de signature.

La supervision doit alerter sur trois événements : un build non reproductible, une modification du manifeste de build et une signature émise hors fenêtre ou identité attendue. Les journaux d’audit doivent être exportés vers un stockage append-only, c’est-à-dire où l’ajout est autorisé mais la modification détectable. Les sauvegardes couvrent les sources, manifestes, attestations, configurations de pipeline et clés publiques. Sauvegarder seulement le binaire, c’est conserver le plat et jeter la recette, le four et la liste des allergènes.

Sans compilation reproductible, rien ne relie l’artefact livré au code réellement examiné. Une signature prouve qui distribue le binaire, pas comment il a été fabriqué : un compilateur, une dépendance ou une étape de construction compromise peut insérer du code absent des sources sans que la signature n’en trahisse rien. L’absence de reconstruction indépendante retire donc le seul moyen de détecter cette divergence en comparant deux résultats bit à bit. La conséquence est qu’une attaque sur la chaîne de fabrication reste invisible : les consommateurs continuent de croire l’étiquette sur parole, exécutant un artefact qui ne correspond plus au code audité. La confiance repose alors entièrement sur l’intégrité de l’atelier de build, sans preuve vérifiable par un tiers, et une porte dérobée introduite à la compilation traverse toute la distribution sans laisser de trace exploitable.

Un programme écrit par des humains est transformé en fichier exécutable par un programme appelé compilateur. Pour vérifier ce résultat, il faut garder la liste exacte des sources, des bibliothèques utilisées, des options de fabrication et de l’environnement de travail. Ces éléments sont fixés dans un manifeste, puis on relance la fabrication dans un environnement séparé.

Si les deux fichiers exécutables ont exactement les mêmes octets, leurs empreintes cryptographiques, c’est-à-dire des nombres calculés à partir de leur contenu, sont identiques. Une différence signale qu’une entrée a changé ou qu’un outil ajoute des informations variables, comme une date ou un chemin local. Il faut supprimer ces variations avant de pouvoir faire confiance à la comparaison.

Cette méthode détecte aussi certains compilateurs modifiés pour ajouter discrètement du code malveillant. Pour cela, on fabrique le compilateur avec un autre compilateur déjà contrôlé, puis on vérifie que le compilateur reconstruit correspond à celui distribué. Si les résultats divergent, la chaîne est suspecte et l’artefact ne doit pas être publié.

La vérification échoue si les deux fabrications utilisent la même infrastructure compromise, si une bibliothèque dangereuse est déjà présente dans les sources, ou si les signatures sont volées. Les clés de signature, les journaux et les manifestes doivent donc être protégés, surveillés, sauvegardés et renouvelés.

À lire aussi

Pages voisines