HeadlinesBriefing HeadlinesBriefing.com

Bug d'exécution Go : plantage Linux embarqué 32 bits

Hacker News •
×

Notre travail quotidien tourne généralement autour de sujets liés à Linux et à la sécurité, en profondeur dans la pile logicielle. Néanmoins, plus souvent que vous ne le pensez, nous finissons par déboguer des applications qui s'exécutent beaucoup plus haut dans la pile. Parfois, un tel problème trouve son origine dans le noyau Linux, parfois ailleurs.

Dans cet article de blog, nous montrons comment nous avons trouvé et corrigé un bug à l'intérieur du runtime Go. Récemment, un client a signalé qu'une application écrite en Go plante de temps en temps sur l'un de leurs systèmes Linux embarqués. Le plantage était toujours la même erreur fatale avec la signature : runtime: netpoll: eventfd ready for 5.

Au départ, nous avons supposé que l'application elle-même était boguée. Mais après avoir examiné l'erreur de plus près, cela ressemblait davantage à une hypothèse interne dans le mécanisme netpoll de Go qui ne tenait plus. Le message d'erreur provient de netpoll() dans src/runtime/netpoll_epoll.go : si ev.

Events != linux. EPOLLIN { throw("runtime: netpoll: eventfd ready for something unexpected") }. Dans ce chemin de code, le code netpoll s'attend à ce que EPOLLIN soit l'unique événement déclenché, mais il a reçu 5, qui est EPOLLIN|EPOLLOUT.

Pourquoi epoll signalerait-il soudainement plus que EPOLLIN alors que le code ne demandait que EPOLLIN ? Avant de creuser davantage, nous avons introduit le message d'erreur dans un moteur de recherche, espérant que quelqu'un d'autre avait déjà rencontré le même problème. Cela nous a directement menés à un rapport dans le suivi des problèmes du projet Go : runtime: netpoll: eventfd ready for something unexpected. Ce problème décrit exactement l'erreur fatale que nous avons observée.

De plus, cela s'est produit sur un système Linux embarqué ARM 32 bits ! Les rapporteurs ont également noté que le plantage se produit dans des applications qui s'exécutent depuis longtemps. Cela correspondait également à la description de notre client. Bingo ! Le problème était ouvert et non résolu depuis mars 2025.

Les mainteneurs de Go avaient également rejeté une tentative de résoudre le problème. À partir des commentaires sur le problème, nous avons appris que cette erreur fatale n'apparaissait jamais que sur les systèmes Linux ARM et i386 32 bits, avec toutes sortes de versions de noyau. Certains noyaux étaient assez anciens, d'autres récents. Aucun rapporteur ne l'a observé sur un système x86_64 ou arm64.

Au départ, nous avons soupçonné que epoll se comporte différemment sur ARM 32 bits ou i386. Nous avons rapidement écarté cette idée car epoll est du code noyau générique. Pourquoi retournerait-il un ensemble d'événements spurieux uniquement sur ARM 32 bits ou i386 ? Néanmoins, le fait que l'erreur n'apparaisse que sur les systèmes 32 bits nous troublait.

Comme étape suivante, nous avons examiné l'utilisation d'epoll dans le code netpoll de Go. Avec l'aide d'un LLM, nous avons parcouru src/runtime/netpoll_epoll.go, en nous concentrant sur les pièges 32 bits tels que les conversions d'entiers. L'examen a révélé que le code netpoll de Go utilise le champ data de la structure epoll_event.

Linux epoll peut stocker un cookie de 8 octets dans le noyau et le renvoyer comme partie de l'événement déclenché vers l'espace utilisateur. Les applications utilisent ce cookie pour attacher des métadonnées à un événement, par exemple pour distinguer différentes sources d'événements. struct epoll_event { __poll_t events; /* ev. Events in Go netpoll */ __u64 data; /* ev.

Data in Go netpoll */ }. En profondeur dans le runtime Go, le gestionnaire principal d'événements doit savoir si un événement appartient à un descripteur de fichier d'événement ou à un descripteur de fichier de socket. Il le détermine en comparant ev.

Data à l'adresse de son objet interne de descripteur de fichier d'événement : if *(**uintptr)(unsafe. Pointer(&ev. Data)) == &netpoll Event Fd {...}.

En poursuivant la lecture du code, nous avons vu que ev. Data contient soit un pointeur brut vers netpoll Event Fd, soit un pointeur marqué vers un objet poll Desc par socket. L'étiquette du pointeur est un compteur, fdseq, qui distingue les objets poll Desc recyclés.

Jusqu'à présent, tout allait bien. Mélanger des pointeurs bruts et marqués dans le même champ nous a paru douteux. Mais comment cela se rapporte-t-il au plantage n'était pas encore clair.

L'inspection de la manière dont Go dispose les pointeurs marqués en mémoire a finalement révélé la cause profonde. Sur les systèmes 32 bits, un pointeur occupe 4 octets tandis que le champ data attend 8 octets. Cet inadéquation de taille provoque une troncature lors du stockage d'une valeur de pointeur.

Les 4 octets supérieurs sont mis à zéro lors de la conversion. Lorsque le runtime compare ensuite le pointeur tronqué à l'adresse originale de l'objet descripteur de fichier d'événement, le...

Source: Hacker News · Résumé par HeadlinesBriefing