Un client IRC intégré à la carte mère
(axleos.com)- UEFIRC est un client IRC graphique qui s’exécute dans l’environnement de préamorçage UEFI du firmware de la carte mère, avant même le lancement du système d’exploitation, et montre qu’il est possible d’implémenter une interface proche d’une application classique ainsi que des fonctions réseau même dans un environnement de bootloader
- L’implémentation exploite les pilotes de carte réseau et la pile TCP fournis par l’UEFI pour le démarrage réseau, et le backend réseau
vmnetpour QEMU rend le développement possible - Le point le plus délicat a été la gestion du protocole TCP UEFI en Rust, avec des états globaux, des callbacks réentrants, des buffers scatter-gather, et un enchevêtrement complexe d’événements, de tokens, de handles et de protocoles
- L’interface graphique reprend la boîte à outils GUI Rust d’axle et son moteur de rendu TrueType adaptés à l’UEFI, et comprend aussi des améliorations de libgui pour la gestion de la souris, des barres de défilement et du rendu de texte dans les vues défilantes
- Le résultat ressemble davantage à un projet humoristique très sophistiqué qu’à un client IRC réellement pratique, mais il permet au moins d’exprimer dans l’UEFI son agacement envers la pile TCP/IP UEFI via IRC
Ce que fait UEFIRC
- UEFIRC est un client IRC graphique qui fonctionne dans l’UEFI
- Il est écrit en Rust et s’appuie sur la boîte à outils GUI et le moteur de rendu TrueType développés pour l’espace utilisateur d’axle
- Il peut se connecter à un serveur IRC, discuter et lire des messages
- Le développement s’est appuyé sur le backend réseau
vmnetpour QEMU
L’UEFI comme environnement d’exécution
- Le bootloader du système d’exploitation est chargé avec l’aide du firmware stocké dans la ROM de la carte mère
- L’ancien BIOS avait de nombreuses limitations, ce qui a conduit à la création du standard UEFI pour le remplacer
- Le BIOS impose au bootloader de démarrer en mode 16-bit
- Il existe aussi une contrainte selon laquelle le chargeur de première étape doit tenir dans 512 octets
- L’UEFI place d’emblée le bootloader dans un environnement 64-bit et fournit des API pour changer la résolution d’affichage VESA, allouer de la mémoire ou accéder au système de fichiers EFI
- C’est un grand progrès par rapport au BIOS, même s’il est aussi parfois jugé excessivement complexe
Réutiliser le démarrage réseau pour IRC
- Certains bootloaders peuvent charger le système d’exploitation via le réseau plutôt que depuis un périphérique bloc local
- Pour prendre en charge ce cas d’usage, le firmware UEFI doit embarquer une pile réseau
- Des pilotes NIC
- Une implémentation TCP
- Une API permettant aux applications exécutées dans l’environnement de préamorçage d’accéder à cette pile
- Comme un bootloader n’est pas obligé de charger un système d’exploitation, il est aussi possible d’exécuter un client IRC dans ce même environnement
La difficulté de gérer le TCP UEFI en Rust
- La partie la plus difficile du projet a été l’implémentation en Rust d’un client du protocole TCP UEFI
- Le protocole TCP UEFI impose des durées de vie de données et des interactions difficiles à modéliser en Rust
- État global
- Callbacks réentrants
- Buffers scatter-gather
- Événements, tokens, handles et protocoles
- Le code Rust a été testé pendant plusieurs jours pour éliminer les fuites mémoire et les use-after-free dans les buffers de réception TCP
La confusion entre NOTIFY_SIGNAL et NOTIFY_WAIT
- L’API d’événements UEFI est difficile à anticiper rien qu’à partir de ses noms
- Si l’on spécifie
NOTIFY_SIGNAL, le callback est appelé lorsque l’événement se produit et l’utilisation dewait()devient une erreur - Si l’on spécifie
NOTIFY_WAITpuis que l’on appellewait(), l’UEFI peut invoquer le callback plusieurs fois avant que l’événement ne survienne, puiswait()se débloque quand il se produit - Les deux modes donnent donc des significations totalement différentes à un même callback
NOTIFY_SIGNAL: l’événement s’est produit, c’est le moment de passer à l’étape suivanteNOTIFY_WAIT: l’événement ne s’est pas encore produit, c’est le moment de favoriser sa progression
- Pour mettre en place un buffering asynchrone des paquets reçus, le projet a finalement combiné une boucle
NOTIFY_WAITavec un timer de timeout très court
Prise en charge de la souris et du curseur
- Une souris n’est pas indispensable pour un client IRC, mais elle rend l’application plus interactive
- Le Simple Pointer Protocol de l’UEFI est utilisé pour lire les mouvements de souris et les clics, et pour fournir un retour visuel sur la position du curseur dans l’interface
- Le Simple Pointer Protocol ne prend pas en charge la molette de défilement
- Dans UEFIRC, il faut donc utiliser les touches fléchées ou faire glisser la barre de défilement avec le curseur
- Le firmware UEFI OVMF standard ne permettait pas de récupérer les événements de souris, donc un firmware UEFI personnalisé a été compilé avec les pilotes et protocoles nécessaires, comme
UsbMouseDxe - Ce firmware UEFI a aussi été publié dans les releases pour permettre de tester UEFIRC dans QEMU
Mise à l’échelle des mouvements de souris
- Le pilote de souris ne renvoie pas une position absolue, mais des deltas de déplacement
- Ajouter simplement
delta_xetdelta_ytels quels avec une mise à l’échelle linéaire donne une sensation de lourdeur - Les systèmes d’exploitation utilisent une mise à l’échelle plus proche d’un compromis entre grands déplacements rapides et ajustements fins
- Dans l’exemple d’implémentation, on applique
log2()à la somme des valeurs absolues du déplacement, puis on utilise ce résultat comme multiplicateur du mouvement du curseur - Un curseur en déplacement linéaire donne facilement l’impression que tout l’environnement est lent et peu réactif
Modéliser les messages IRC
- La modélisation des messages IRC a été relativement simple et agréable
- IRC utilise un format texte ligne par ligne, ce qui le rend facile à parser
- Mais plusieurs décennies d’extensions ont aussi laissé un ensemble dont seule une partie est standardisée
Utiliser libgui dans l’UEFI
- La boîte à outils GUI Rust d’axle avait déjà été en grande partie préparée pour fonctionner hors du contexte d’axle, ce qui a rendu son exécution dans l’UEFI relativement directe
- Le travail principal a consisté à fournir une implémentation de
AwmWindowutilisable dans l’UEFI - Il a ensuite été possible de réutiliser directement plusieurs fonctionnalités de libgui
- Gestion des événements
- Rendu de polices
- Composition de couches
- Décoration des vues
- Composants complexes comme les vues défilantes
Barres de défilement et rendu de texte dans les vues défilantes
- La libgui en C d’axle disposait déjà d’une fonctionnalité de barre de défilement, mais la version Rust n’en avait pas encore toutes les capacités
- Comme l’interaction principale d’UEFIRC se fait dans une vue défilante remplie de texte, la fonctionnalité de barre de défilement a été réimplémentée dans la libgui Rust
- Une vue défilante coûte plus cher en rendu pixel qu’une vue de taille fixe
- Pour une vue fixe, il suffit de raisonner sur un buffer RGB de taille
width * height - Une vue défilante doit gérer un canevas extensible à l’infini
- Pour une vue fixe, il suffit de raisonner sur un buffer RGB de taille
- La boîte à outils GUI Rust d’axle traite les vues défilantes par tuiles
- Chaque tuile est un buffer de pixels carré de quelques centaines de pixels de côté
- Seules les tuiles nécessaires à la zone effectivement rendue sont allouées
- Les tuiles visibles sont calculées puis assemblées dans l’image finale
- Si le moteur de rendu TrueType appelle
putpixel()pour chaque pixel d’un glyphe, la vue défilante ne connaît pas à l’avance l’étendue complète du rendu, ce qui est inefficace - Pour résoudre ce problème, une pile de polygones a été ajoutée aux primitives de dessin de base comme les lignes, cercles et rectangles
- La vue défilante sait alors à l’avance qu’un grand polygone va être dessiné et peut allouer les tuiles nécessaires
- L’auteur n’apprécie pas particulièrement d’avoir le remplissage de polygones arbitraires comme primitive de base, mais dans la pratique cela fonctionne bien
Les améliorations de libgui apportées en créant UEFIRC
- Chaque nouvelle application graphique met en lumière de petites contraintes et irritations dans la boîte à outils GUI, l’IPC, les interfaces de pilotes et les fonctionnalités du noyau
- Le développement d’UEFIRC a entraîné plusieurs modifications dans libgui
- Ajout d’un événement de relâchement du clic gauche
- Prise en charge du redimensionnement dynamique de
Label - Rendu TrueType dans
Labelà la place de l’ancienne police bitmap - Rendu TrueType dans
Buttonà la place de l’ancienne police bitmap - Le moteur de rendu TrueType expose davantage d’informations de mise en page lors du rendu des glyphes
- Le parseur TrueType modélise et expose davantage de métriques de police
- Correction de bugs subtils de mise en page et d’espacement dans le moteur de rendu TrueType
TextInputViewprend en charge des callbacks de saisie clavier fournis par l’utilisateur
Un résultat complètement superflu
- Le client IRC en lui-même est un projet humoristique sophistiqué, d’une utilité pratique limitée
- Il peut néanmoins servir d’outil pour exprimer sa frustration contre la pile TCP/IP UEFI quand elle devient agaçante
- Et pour finir, l’auteur s’est connecté depuis l’UEFI au canal IRC UEFI
#edk2pour y laisser un message de salut
1 commentaires
Avis sur Hacker News
J’ai créé, pour le fun, un client IRC graphique qui ne fonctionne que dans un environnement pré-boot UEFI, et j’y ai ajouté des fonctionnalités excessives comme des polices TrueType, un curseur et des décorations d’interface
À l’origine, c’était un projet pour faire quelque chose de rapide et léger après en avoir eu assez de fabriquer un récepteur GPS à partir de zéro, mais comme toujours, ça a pris beaucoup plus de temps que prévu
J’ai aussi passé pas mal de temps sur les visualisations de l’article, qui montrent comment modéliser une vue défilante et la rendre dans un viewport statique ; j’espère que vous les apprécierez
Au début, avec l’idée de « forcer quelque chose qui n’a rien à faire dans l’UEFI », j’avais pensé à un client Twitter, mais quelqu’un en avait déjà fait un bon avec le protocole HTTP de l’UEFI, donc j’ai décidé d’éviter HTTP
J’ai donc choisi IRC, qui fonctionne au-dessus de TCP et a aussi ce côté réseau social qui n’a rien à voir avec un environnement pré-boot
Bon, je plaisante. En partie
En tant que minimaliste, je n’ai besoin ni d’interface graphique ni de souris, et même l’UEFI me semble déjà en faire plus que nécessaire
Le client Twitter mentionné est ici : https://github.com/arata-nvm/mitnal
Ça pourrait être une assez bonne façon, peut-être sûre, d’autoriser l’auto-réparation de systèmes distants dont l’installation est complètement cassée et qui ne démarrent plus normalement
C’est vraiment excellent. Cela montre aussi très bien qu’il existe, sous ce que la plupart des gens considèrent comme le système, des logiciels bien plus complexes et puissants qu’on ne l’imagine
On pense souvent à tort que le système d’exploitation est la « couche la plus basse » de la pile logicielle, alors qu’en réalité il existe du code de type firmware qui possède véritablement la machine
Parfois il disparaît après avoir fait son travail, parfois il reste présent tant que le système est allumé, de manière si transparente que même l’OS ne s’en rend pas compte
Il y a cette attitude consistant à dire que « ce n’est que du code bas niveau pour piloter les périphériques, rien de grave ne peut arriver là », mais si l’on peut y mettre jusqu’à un client IRC, on peut facilement imaginer d’autres choses malveillantes
« Pourquoi ? » Mais enfin, quel genre de question est-ce que « pourquoi » ? C’est pour voir cet état d’esprit que je viens sur HN
« La prise de conscience la plus effrayante m’a frappé : il n’y avait aucune raison à ce que j’avais fait. Je savais pourquoi je l’avais fait. Je l’avais fait parce que ça me semblait amusant. Mais ils allaient me demander : “Pourquoi diable as-tu fait ça ?”, et si je n’avais pas une raison suffisamment plausible, ils allaient me faire interner. » — Boyd Rice
Pas besoin de te dévaloriser. Ici, il s’agit d’un projet de client de commande et contrôle de botnet
L’interface est assez drôle, cela dit
Vraiment impressionnant. Je ne savais pas que l’API UEFI était aussi accessible et aussi bien documentée
Je me demande à quoi ressemblait le cycle de développement. J’imagine que ça tournait dans une VM, mais je me demande s’il fallait vraiment « démarrer » à chaque fois pour lancer le client
Le script d’exécution principal recréait un système de fichiers EFI contenant le nouveau build d’UEFIRC, puis le passait à QEMU
Mais pour créer l’interface graphique, cette surcharge devenait assez pénible, donc j’ai configuré l’application pour qu’elle puisse être compilée à la fois pour l’UEFI pur et pour un environnement hôte tournant sur Mac
En changeant les flags de build, le toolkit graphique dessine soit directement dans le framebuffer fourni par l’UEFI, soit se connecte au système de fenêtres du Mac pour échanger des événements
La surcharge de cette approche à double cible se voit aussi dans le point d’entrée : https://github.com/codyd51/uefirc/blob/main/src/main.rs
Le parsing des messages IRC ne nécessitait pas de fioritures particulières, donc je l’ai développé avec un ensemble de tests unitaires tournant directement sur Mac ; certains sont ici : https://github.com/codyd51/uefirc/blob/main/src/irc/response...
J’aimerais un jour finir d’écrire le système d’exploitation pour mon bot IRC qui tourne encore
C’est peut-être la remarque la plus inutile qui soit, mais le déplacement non linéaire de la souris, c’est-à-dire l’accélération, est le premier réglage que je désactive quand je démarre un nouvel OS. Curieusement, ça me fait vraiment mal à la main
Par exemple, sur Mac il y a linearmouse, qui est gratuit, et sous Windows il suffit de désactiver l’accélération. Sous Linux, évidemment, c’est facile
Avec l’accélération de la souris, il est difficile d’acquérir la sensation de correspondance entre la distance parcourue par la souris et le déplacement à l’écran, et je pense qu’à long terme il est plus efficace de s’en passer
C’est une méthode que j’ai apprise des gamers, et je pense qu’ils continuent à avoir de bonnes raisons de le faire
Dans un cas comme dans l’autre, je pense que le ressenti finit par s’apprendre. Comme la pédale d’accélérateur d’une voiture, qui n’est généralement pas directement mappée à la vitesse
Si l’on demande « pourquoi ? », c’est parce que ce genre d’applications bas niveau faisait partie des promesses lors de la présentation initiale de l’UEFI
Les créateurs de l’UEFI rêvaient même de remplacer ces mini-systèmes d’exploitation Linux dédiés à Internet auxquels certains fabricants donnaient accès en appuyant sur une touche précise au démarrage. Je ne me souviens plus du nom
J’ai vu une vidéo YouTube qui traitait le sujet en profondeur ; si je me souviens bien, au début c’était un Linux réduit ou un autre OS personnalisé, puis c’est passé à des apps UEFI, avant de finir par se démoder
Bon article. Ça me rappelle le poisson d’avril du bootloader barebox il y a deux ans : si toutes les autres cibles de démarrage échouaient, il se connectait à #barebox[1]
Là-bas, l’accent était mis sur l’ajout du support TCP à barebox, sans les éléments d’interface graphique impressionnants qu’on voit ici
L’interface était uniquement en ligne de commande, et si l’on compilait barebox comme payload EFI, il pouvait dessiner sur EFI GOP
[1]: https://lore.barebox.org/barebox/20220401145902.GF4351@telli...
La récente vidéo de Cathode Ray Dude m’est tout de suite venue à l’esprit. Elle parlait de QuickLook, le « client e-mail » de HP, qui était en réalité un plugin Outlook, lui aussi implémenté de cette façon dans un produit commercialisé : https://www.youtube.com/watch?v=ssob-7sGVWs
La vidéo montre aussi d’autres choses encore plus étranges faites par HP. Cela dit, ce projet-ci va jusqu’à gérer la partie difficile que QuickLook évitait : le réseau
Les visualisations de l’article sont étonnamment réussies et impressionnantes