<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Talos on Zwindler's Reflection</title><link>https://blog.zwindler.fr/tags/talos/</link><description>Recent content in Talos on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr-fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Tue, 03 Nov 2026 18:00:00 +0100</lastBuildDate><atom:link href="https://blog.zwindler.fr/tags/talos/index.xml" rel="self" type="application/rss+xml"/><item><title>Talos : un certificat en lecture seule qui reboot votre serveur</title><link>https://blog.zwindler.fr/2026/11/03/talos-timecheck-watchdog-os-reader-reboot/</link><pubDate>Tue, 03 Nov 2026 18:00:00 +0100</pubDate><guid>https://blog.zwindler.fr/2026/11/03/talos-timecheck-watchdog-os-reader-reboot/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/10/talos.webp" alt="Featured image of post Talos : un certificat en lecture seule qui reboot votre serveur" /&gt;&lt;h2 id="tldr"&gt;TL;DR
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Sur Talos Linux &amp;lt;= 1.14.2, l&amp;rsquo;API &lt;code&gt;TimeService/TimeCheck&lt;/code&gt; vérifiait les chemins de devices PTP avec un simple &lt;code&gt;strings.HasPrefix(server, &amp;quot;/dev/&amp;quot;)&lt;/code&gt;. Un &lt;code&gt;..&lt;/code&gt; suffisait donc à faire ouvrir &lt;strong&gt;n&amp;rsquo;importe quel fichier&lt;/strong&gt; par &lt;code&gt;machined&lt;/code&gt;, en &lt;strong&gt;root&lt;/strong&gt;. On ne peut pas exfiltrer le fichier, juste le faire ouvrir.&lt;/li&gt;
&lt;li&gt;Cette API était accessible au rôle &lt;code&gt;os:reader&lt;/code&gt;, le rôle &lt;strong&gt;lecture seule&lt;/strong&gt; qu&amp;rsquo;on distribue aux outils de monitoring.&lt;/li&gt;
&lt;li&gt;Ouvrir &lt;code&gt;/dev/watchdog&lt;/code&gt; arme le timer matériel. Refermer le fichier sans l&amp;rsquo;octet magique &lt;code&gt;V&lt;/code&gt; le laisse armé. Si personne ne le nourrit : &lt;strong&gt;hard reset&lt;/strong&gt; du serveur. Un compte read-only peut donc rebooter un serveur physique.&lt;/li&gt;
&lt;li&gt;Publié le 19 octobre 2026 sous &lt;a class="link" href="https://github.com/siderolabs/talos/security/advisories/GHSA-vpfm-35qx-jhvw" target="_blank" rel="noopener"
&gt;GHSA-vpfm-35qx-jhvw&lt;/a&gt; (Moderate, 5.9), corrigé dans &lt;strong&gt;1.13.11&lt;/strong&gt; et &lt;strong&gt;1.14.3&lt;/strong&gt;. Et c&amp;rsquo;est mon premier advisory où je suis le seul finder crédité.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Dans &lt;a class="link" href="https://blog.zwindler.fr/2026/09/23/finding-vulnerabilities-oss-not-security-specialist/" &gt;mon article précédent&lt;/a&gt;, je racontais comment je traque des vulnérabilités dans l&amp;rsquo;open source avec des LLM depuis cet été. Depuis, certaines de ces trouvailles sont devenues publiques : deux CVE Argo CD co-créditées avec 7 autres reporters chacune, une CVE KEDA en co-crédit. Il y en a d&amp;rsquo;autres en attente, mais la saveur n&amp;rsquo;est pas la même quand on n&amp;rsquo;est pas le &amp;ldquo;premier&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Celle-ci est ma première GHSA &lt;strong&gt;en solo&lt;/strong&gt; : un seul nom dans les credits, le mien (même si c&amp;rsquo;est Devin/SWE qui a en réalité fait la découverte initiale, cf. l&amp;rsquo;article précédent). Au-delà de la fierté personnelle, elle est aussi super marrante à raconter. Martial me fera les gros yeux car c&amp;rsquo;est &amp;ldquo;encore&amp;rdquo; un DoS (il m&amp;rsquo;a surnommé DoSnis), mais moi j&amp;rsquo;aime bien les DoS.&lt;/p&gt;
&lt;p&gt;Elle est sortie d&amp;rsquo;un audit complet de Talos (la distribution Linux immuable dédiée à Kubernetes), pendant une passe où je cherchais une chose précise : quelles méthodes de l&amp;rsquo;API sont accessibles aux rôles les plus faibles, et qu&amp;rsquo;est-ce qu&amp;rsquo;elles font &lt;em&gt;vraiment&lt;/em&gt; ?&lt;/p&gt;
&lt;h2 id="talos-et-ses-rôles-en-deux-minutes"&gt;Talos et ses rôles en deux minutes
&lt;/h2&gt;&lt;p&gt;Talos n&amp;rsquo;a pas de SSH, pas de shell, pas de package manager. Toute l&amp;rsquo;administration passe par une API gRPC (&lt;code&gt;apid&lt;/code&gt;, port 50000), authentifiée par certificats clients. Le rôle est embarqué dans le certificat (champ &lt;code&gt;O:&lt;/code&gt;). Du plus puissant au plus faible :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;os:admin&lt;/code&gt; : tout, y compris reboot, wipe, machine config&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os:operator&lt;/code&gt; : gestion courante (upgrade, reboot&amp;hellip;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os:reader&lt;/code&gt; : &lt;strong&gt;lecture seule&lt;/strong&gt;. État du noeud, logs, métriques, configuration. C&amp;rsquo;est le rôle qu&amp;rsquo;on file aux dashboards, aux sondes, aux outils d&amp;rsquo;observabilité, en se disant &amp;ldquo;au pire, ils regardent&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sauf que &lt;code&gt;TimeService/TimeCheck&lt;/code&gt;, l&amp;rsquo;API qui demande au noeud de vérifier sa synchro temporelle contre une source donnée, était ouverte aux trois rôles, reader compris (&lt;code&gt;internal/app/machined/pkg/system/services/machined.go:160&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Sur le papier, ça se tient : interroger une source de temps, c&amp;rsquo;est de la lecture. Le hic, c&amp;rsquo;est que cette &amp;ldquo;source de temps&amp;rdquo; peut être soit un serveur NTP, soit &lt;strong&gt;un chemin de device PTP&lt;/strong&gt; (Precision Time Protocol, les horloges matérielles &lt;code&gt;/dev/ptp*&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="le-bug--un-hasprefix-qui-ne-protège-rien"&gt;Le bug : un &lt;code&gt;HasPrefix&lt;/code&gt; qui ne protège rien
&lt;/h2&gt;&lt;p&gt;Voici l&amp;rsquo;intégralité du contrôle :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// internal/pkg/ntp/ntp.go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;IsPTPDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oui, c&amp;rsquo;est &lt;strong&gt;tout&lt;/strong&gt;. Pas de &lt;code&gt;filepath.Clean&lt;/code&gt;, pas de rejet de &lt;code&gt;..&lt;/code&gt;, pas de restriction à &lt;code&gt;/dev/ptp*&lt;/code&gt;. Le handler (&lt;code&gt;v1alpha1_time.go&lt;/code&gt;) passe ensuite la chaîne telle quelle à &lt;code&gt;QueryPTPDevice&lt;/code&gt;, qui fait :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// machined tourne en root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Donc &lt;code&gt;/dev/../dev/watchdog&lt;/code&gt;, &lt;code&gt;/dev/../etc/shadow&lt;/code&gt; ou &lt;code&gt;/dev/../dev/sda&lt;/code&gt; passent tous le filtre et sont ouverts en root par &lt;code&gt;machined&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Première question qui vient : &amp;ldquo;OK, et il lit quoi ?&amp;rdquo;. Bah&amp;hellip; &lt;strong&gt;rien&lt;/strong&gt;. Le descripteur part dans des ioctls PTP, qui échouent sur tout ce qui n&amp;rsquo;est pas une horloge PTP, et le contenu n&amp;rsquo;est jamais renvoyé au client. On n&amp;rsquo;a &lt;strong&gt;pas&lt;/strong&gt; de lecture de fichiers arbitraires.&lt;/p&gt;
&lt;p&gt;Le problème est ailleurs : &lt;code&gt;open()&lt;/code&gt; n&amp;rsquo;est pas une opération anodine.&lt;/p&gt;
&lt;h2 id="open-nest-pas-une-lecture--le-cas-du-watchdog"&gt;&lt;code&gt;open()&lt;/code&gt; n&amp;rsquo;est pas une lecture : le cas du watchdog
&lt;/h2&gt;&lt;p&gt;Un watchdog, c&amp;rsquo;est un minuteur matériel présent sur la plupart des serveurs : il redémarre la machine si l&amp;rsquo;OS ne donne plus signe de vie, typiquement en cas de freeze du kernel.&lt;/p&gt;
&lt;p&gt;Sous Linux, ouvrir &lt;code&gt;/dev/watchdog&lt;/code&gt; &lt;strong&gt;arme le watchdog matériel&lt;/strong&gt;. C&amp;rsquo;est le contrat du driver : l&amp;rsquo;open lance le compte à rebours, et si personne ne vient écrire dedans régulièrement (&amp;ldquo;nourrir&amp;rdquo; le chien de garde), le matériel reset la machine. Pas de log applicatif, pas de shutdown propre, et l&amp;rsquo;OS n&amp;rsquo;a pas son mot à dire : c&amp;rsquo;est le hardware qui coupe.&lt;/p&gt;
&lt;p&gt;Il existe une porte de sortie : écrire le caractère magique &lt;code&gt;V&lt;/code&gt; dans le fichier avant de le fermer (le &amp;ldquo;magic close&amp;rdquo;), ce qui désarme le timer. Ici, Talos fait &lt;code&gt;open()&lt;/code&gt;, les ioctls PTP échouent, puis &lt;code&gt;close()&lt;/code&gt;&amp;hellip; sans &lt;code&gt;V&lt;/code&gt;. Le timer continue de tourner, personne ne le nourrit. Reboot.&lt;/p&gt;
&lt;p&gt;Note : ça ne marche que si Talos n&amp;rsquo;a &lt;strong&gt;pas&lt;/strong&gt; déjà ouvert le watchdog lui-même (pas de &lt;code&gt;WatchdogTimerConfig&lt;/code&gt; dans la machine config, donc la grande majorité des cas). Autrement dit, ce sont précisément les noeuds qui n&amp;rsquo;utilisent pas le watchdog qui sont vulnérables : l&amp;rsquo;attaque l&amp;rsquo;arme, et personne n&amp;rsquo;est là pour le nourrir (pauvre chien-chien 🐶).&lt;/p&gt;
&lt;h2 id="repro-sur-mon-lab"&gt;Repro sur mon lab
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai tout vérifié de bout en bout le 29 septembre 2026, sur mes noeuds Talos de lab, avec un &lt;code&gt;talosconfig&lt;/code&gt; dont le certificat client est strictement &lt;code&gt;O: os:reader&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;abord, on vérifie que ce rôle ne peut pas rebooter par la voie normale :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n &amp;lt;node-vm&amp;gt; reboot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rpc error: code = PermissionDenied desc = not authorized&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ensuite, un test inoffensif pour prouver que la traversée passe :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n &amp;lt;node-vm&amp;gt; &lt;span class="nb"&gt;time&lt;/span&gt; --check &lt;span class="s2"&gt;&amp;#34;/dev/../proc/version&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rpc error ... error querying PTP device &amp;#34;/dev/../proc/version&amp;#34;: invalid argument&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# open() a reussi (le fichier existe), c&amp;#39;est l&amp;#39;ioctl PTP qui echoue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n &amp;lt;node-vm&amp;gt; &lt;span class="nb"&gt;time&lt;/span&gt; --check &lt;span class="s2"&gt;&amp;#34;/dev/../nonexistent&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rpc error ... &amp;#34;/dev/../nonexistent&amp;#34;: no such file or directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# open() a echoue : le fichier n&amp;#39;existe pas&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Deux messages d&amp;rsquo;erreur différents, ça fait un oracle : on peut savoir si n&amp;rsquo;importe quel chemin existe sur le noeud, y compris hors de &lt;code&gt;/dev&lt;/code&gt;. Et le &lt;code&gt;..&lt;/code&gt; qui apparaît tel quel dans l&amp;rsquo;erreur confirme au passage qu&amp;rsquo;il n&amp;rsquo;y a pas de &lt;code&gt;filepath.Clean&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Maintenant, sur un serveur &lt;strong&gt;bare-metal&lt;/strong&gt; avec un vrai watchdog matériel :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n &amp;lt;node-bm&amp;gt; &lt;span class="nb"&gt;time&lt;/span&gt; --check &lt;span class="s2"&gt;&amp;#34;/dev/../dev/watchdog&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rpc error ... invalid argument (open() en root OK, ioctl PTP en echec)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et dans la console kernel, au même moment :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kern: crit: watchdog: watchdog0: watchdog did not stop!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;C&amp;rsquo;est exactement le message qu&amp;rsquo;on obtient quand le fd est refermé sans le &lt;code&gt;V&lt;/code&gt; : le watchdog reste armé, et le noeud fait un hard reset au timeout. Un certificat censé ne faire que de la lecture vient de rebooter physiquement un serveur.&lt;/p&gt;
&lt;h2 id="les-autres-impacts"&gt;Les autres impacts
&lt;/h2&gt;&lt;p&gt;Pour être honnête, ça reste limité : ni lecture de contenu, ni écriture. Ce qu&amp;rsquo;on obtient vraiment :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reboot forcé&lt;/strong&gt; via le watchdog, qu&amp;rsquo;on peut relancer à chaque redémarrage du noeud.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Oracle d&amp;rsquo;existence&lt;/strong&gt; de fichiers sur le noeud (les deux messages d&amp;rsquo;erreur). Mais en vrai, il n&amp;rsquo;y a quasiment rien sur un noeud Talos Linux (c&amp;rsquo;est toute la beauté de la chose).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Goroutines bloquées&lt;/strong&gt; : certains devices et FIFO bloquent dans &lt;code&gt;open(2)&lt;/code&gt;, chaque appel peut donc figer un handler de &lt;code&gt;machined&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Des effets de bord plus exotiques d&amp;rsquo;&lt;code&gt;open()&lt;/code&gt; sur certains drivers (rembobinage de bande, état de terminaux&amp;hellip;), mais là on est complètement dans la théorie.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="le-maintainer-ma-mieux-noté-que-moi"&gt;Le maintainer m&amp;rsquo;a mieux noté que moi
&lt;/h2&gt;&lt;p&gt;Petite anecdote sur la subjectivité du CVSS.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;avais estimé la faille à &lt;strong&gt;5.4&lt;/strong&gt; (&lt;code&gt;AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L&lt;/code&gt;) : &lt;code&gt;A:L&lt;/code&gt; pour le reboot, et &lt;code&gt;AC:L&lt;/code&gt; parce qu&amp;rsquo;une fois le certificat &lt;code&gt;os:reader&lt;/code&gt; en poche, l&amp;rsquo;attaque est déterministe.&lt;/p&gt;
&lt;p&gt;Le maintainer (smira, de Sidero Labs) a publié &lt;strong&gt;5.9&lt;/strong&gt; (&lt;code&gt;AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:H&lt;/code&gt;) : &lt;code&gt;A:H&lt;/code&gt; parce qu&amp;rsquo;un hard reset de serveur physique, c&amp;rsquo;est plus qu&amp;rsquo;une simple dégradation, et &lt;code&gt;AC:H&lt;/code&gt; parce qu&amp;rsquo;il faut un watchdog matériel présent et non géré par Talos.&lt;/p&gt;
&lt;p&gt;Et franchement, les deux choix se défendent. Le CVSS 3.1 laisse beaucoup de place à l&amp;rsquo;interprétation, surtout sur &lt;code&gt;AC&lt;/code&gt; (les conditions que l&amp;rsquo;attaquant ne contrôle pas) et sur la manière de compter l&amp;rsquo;impact quand l&amp;rsquo;attaquant a déjà une partie des droits. Un score CVSS, c&amp;rsquo;est une opinion argumentée, pas une mesure. Et un maintainer qui prend le temps de justifier son vecteur dans l&amp;rsquo;advisory, je ne vais pas m&amp;rsquo;en plaindre.&lt;/p&gt;
&lt;h2 id="le-fix--trois-couches-defense-in-depth"&gt;Le fix : trois couches, defense in depth
&lt;/h2&gt;&lt;p&gt;Le correctif (mergé dans main, backporté en &lt;strong&gt;1.13.11&lt;/strong&gt; et &lt;strong&gt;1.14.3&lt;/strong&gt;) reprend les trois pistes que j&amp;rsquo;avais suggérées dans le rapport, en mieux :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Vérification du device&lt;/strong&gt; : avant tout &lt;code&gt;open()&lt;/code&gt;, le chemin doit pointer vers une vraie horloge PTP &lt;strong&gt;et&lt;/strong&gt; le kernel doit confirmer que le device appartient bien au sous-système PTP. Ouvrir autre chose n&amp;rsquo;a donc plus aucun effet de bord. Les messages d&amp;rsquo;erreur sont aussi devenus génériques, ce qui supprime l&amp;rsquo;oracle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation de la machine config&lt;/strong&gt; : les chemins PTP doivent être des chemins propres, directement sous &lt;code&gt;/dev&lt;/code&gt;, avec un nom qui commence par &lt;code&gt;ptp&lt;/code&gt; (&lt;code&gt;/dev/ptp0&lt;/code&gt;, &lt;code&gt;/dev/ptp_kvm&lt;/code&gt;, &lt;code&gt;/dev/ptp_hyperv&lt;/code&gt;). Si vous avez des configs exotiques, attention : elles risquent d&amp;rsquo;être refusées après l&amp;rsquo;upgrade.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RBAC&lt;/strong&gt; : &lt;code&gt;TimeService/TimeCheck&lt;/code&gt; est désormais réservé à &lt;code&gt;os:admin&lt;/code&gt; et &lt;code&gt;os:operator&lt;/code&gt;. &lt;code&gt;TimeService/Time&lt;/code&gt; (la lecture de l&amp;rsquo;heure configurée, sans &lt;code&gt;--check&lt;/code&gt;) reste accessible à &lt;code&gt;os:reader&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Versions affectées : à partir de &lt;code&gt;1.10.0&lt;/code&gt; (première release où &lt;code&gt;TimeCheck&lt;/code&gt; accepte des chemins PTP), jusqu&amp;rsquo;à &lt;code&gt;1.13.11&lt;/code&gt; / &lt;code&gt;1.14.3&lt;/code&gt; exclus.&lt;/p&gt;
&lt;p&gt;Si vous ne pouvez pas upgrader tout de suite :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ne distribuez pas de certificats &lt;code&gt;os:reader&lt;/code&gt; à des clients en qui vous n&amp;rsquo;avez pas totalement confiance ;&lt;/li&gt;
&lt;li&gt;filtrez l&amp;rsquo;accès réseau au port &lt;code&gt;apid&lt;/code&gt; (50000) ;&lt;/li&gt;
&lt;li&gt;sur les noeuds avec un watchdog matériel, déclarez un &lt;code&gt;WatchdogTimerConfig&lt;/code&gt; pour que Talos garde lui-même le device ouvert.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="takeaways"&gt;&amp;ldquo;Takeaways&amp;rdquo;
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;open()&lt;/code&gt; n&amp;rsquo;est pas une lecture.&lt;/strong&gt; Un appel qui &amp;ldquo;ne retourne rien&amp;rdquo; peut quand même armer du hardware, bloquer une goroutine ou servir d&amp;rsquo;oracle. Quand on audite une API &amp;ldquo;read-only&amp;rdquo;, la question n&amp;rsquo;est pas &amp;ldquo;qu&amp;rsquo;est-ce qu&amp;rsquo;elle renvoie ?&amp;rdquo; mais &amp;ldquo;qu&amp;rsquo;est-ce qu&amp;rsquo;elle fait ?&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un &lt;code&gt;HasPrefix&lt;/code&gt; n&amp;rsquo;est pas une validation de chemin.&lt;/strong&gt; Sans &lt;code&gt;filepath.Clean&lt;/code&gt;, sans rejet de &lt;code&gt;..&lt;/code&gt; et sans allowlist, c&amp;rsquo;est purement décoratif. Si vous voulez chercher le même pattern ailleurs, grep &lt;code&gt;HasPrefix(x, &amp;quot;/dev/&amp;quot;)&lt;/code&gt;, &lt;code&gt;HasPrefix(x, &amp;quot;/proc/&amp;quot;)&lt;/code&gt; ou &lt;code&gt;HasPrefix(x, &amp;quot;/sys/&amp;quot;)&lt;/code&gt;. J&amp;rsquo;ai retrouvé ça dans de nombreux autres projets OSS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Est-ce que ça donne à l&amp;rsquo;attaquant quelque chose qu&amp;rsquo;il n&amp;rsquo;a pas déjà ?&amp;rdquo;&lt;/strong&gt; C&amp;rsquo;est la question que je pose systématiquement pendant la contre-expertise (cf. l&amp;rsquo;article précédent), et ici la réponse ne fait pas débat : le rôle &lt;code&gt;reader&lt;/code&gt; ne peut pas rebooter (&lt;code&gt;PermissionDenied&lt;/code&gt;), mais il peut armer le watchdog qui, lui, reboot. Difficile pour un maintainer de répondre &amp;ldquo;working as intended&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Les rôles &amp;ldquo;lecture seule&amp;rdquo; valent le détour.&lt;/strong&gt; Tout le monde audite les chemins admin. Les API d&amp;rsquo;observabilité sont moins scrutées, distribuées beaucoup plus largement, et personne ne regarde leurs effets de bord. Dans le même audit, ce pattern &amp;ldquo;reader + effet de bord&amp;rdquo; m&amp;rsquo;a d&amp;rsquo;ailleurs donné d&amp;rsquo;autres pistes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sidero Labs a été exemplaire.&lt;/strong&gt; Rapport accepté en moins d&amp;rsquo;une heure, fix en trois couches mergé moins de 3h après mon rapport (avec des durcissements que je n&amp;rsquo;avais pas suggérés), CVE demandée, et un advisory transparent publié moins de trois semaines plus tard. Si tous les projets traitaient les rapports comme ça, la vie de hunter serait beaucoup plus simple.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tout est parti d&amp;rsquo;un subagent qui faisait exactement ce qu&amp;rsquo;on lui avait demandé : lire le code fichier par fichier et noter tout ce qui paraissait &amp;ldquo;même vaguement bizarre&amp;rdquo;. La suite (repro, rédaction, soumission) suit le pipeline décrit dans &lt;a class="link" href="https://blog.zwindler.fr/2026/09/23/finding-vulnerabilities-oss-not-security-specialist/" &gt;l&amp;rsquo;article précédent&lt;/a&gt;. Ça fait plaisir de pouvoir enfin raconter un cas concret de bout en bout !&lt;/p&gt;</description></item></channel></rss>