Retour à tous les articles
Publié le · par Renaud Deraison

Le service worker qui ne veut pas mourir, et la VM qui s'en moque

Google a republié par accident la semaine dernière un bug Chromium vieux de quatre ans — un service worker qui continue d'exécuter du JavaScript après la fermeture du navigateur, sur tous les principaux navigateurs Chromium, toujours non corrigé. Le proof-of-concept est désormais dans la nature. La question intéressante n'est pas comment il fonctionne. C'est ce que « persistance » veut dire sur un navigateur dont la machine sous-jacente cesse d'exister dès que vous fermez l'onglet.

La persistance n'est pas une propriété du code. C'est une propriété de l'endroit où le code est autorisé à vivre. Un service worker qui « ne meurt jamais » a un problème quand la machine sur laquelle il a été installé est détruite à chaque fois que vous fermez la fenêtre.

Le 20 mai 2026, le bug tracker de Chromium a discrètement levé les restrictions d'accès sur un bug qui restait en statut restreint depuis 2022. En quelques heures, le bug — ainsi qu'un proof-of-concept fonctionnel — était mis en miroir, archivé et commenté un peu partout sur le web. Google a tenté de refermer le ticket. C'était trop tard ; la page était déjà dans la Wayback Machine, et l'article de BleepingComputer est sorti le lendemain.

Le bug est réel, le proof-of-concept fonctionne, et quatre ans après son signalement initial il n'est toujours pas corrigé dans Chrome Dev 150 ni Edge 148. Il affecte tous les navigateurs dérivés de Chromium : Chrome, Edge, Brave, Opera, Vivaldi, Arc.

Ce qu'il permet à une page de faire est, formulé en une phrase, banal : enregistrer un service worker — un petit morceau de JavaScript que le navigateur accepte de laisser tourner en arrière-plan — qui ne s'arrête pas quand la page se ferme, quand l'onglet se ferme, quand le navigateur se ferme, quand la machine redémarre. La chercheuse Lyra Rebane, qui a initialement déclaré le bug en décembre 2022, a résumé le pire cas à BleepingComputer : sur Microsoft Edge le menu de téléchargement n'apparaît même pas, si bien que le résultat est « une RCE JS complètement silencieuse qui continue de tourner même après que vous avez fermé le navigateur. »

Le détail technique du pourquoi du bug compte moins que la classe d'attaque qu'il permet. C'est une primitive de persistance. Une page que vous avez visitée une fois, peut-être il y a des mois, peut-être une publicité que vous ne vous souvenez pas avoir vue, est autorisée à continuer d'exécuter du code sur votre ordinateur indéfiniment. Enrôlement dans un botnet, fraude publicitaire, cryptojacking, participation à des DDoS, exfiltration lente de données, récolte opportuniste d'identifiants chaque fois que vous vous reconnectez au même site — tout cela devient trivial dès que vous disposez d'un point d'appui qui survit à la fermeture du navigateur.

C'est un bon bug, au sens où il est éclairant. C'est un mauvais bug, au sens où il sera utilisé.

Ce qu'est réellement un service worker

La plateforme web autorise, depuis une dizaine d'années environ, les pages à installer de petits programmes JavaScript appelés service workers. Ils tournent en arrière-plan, séparément de tout onglet, et existent pour que les applications web puissent faire des choses raisonnables — mettre en cache des assets pour qu'un site charge quand vous êtes hors-ligne, délivrer des notifications push, synchroniser des données quand le réseau revient.

Un service worker est enregistré contre une origine (par exemple, la combinaison de https, du nom d'hôte example.com et du port par défaut). Une fois enregistré, le navigateur le stocke sur disque et le réveille la fois suivante où vous visitez quoi que ce soit sur cette origine — ou, dans le cas de fonctionnalités d'arrière-plan comme les messages push, le réveille de lui-même périodiquement. Du point de vue de l'utilisateur, le worker est invisible. Du point de vue du système d'exploitation, le worker fait partie du navigateur, tournant avec les permissions qu'a le processus du navigateur.

1. La page enregistre le workernavigator.serviceWorker.register('/sw.js')2. Le navigateur le persisteécrit dans le profil sur disque,indexé par origine,limité à un chemin d'URL,survit aux redémarrages.3. Le navigateur le réveilleà la revisite, sur push,sur synchronisation d'arrière-planLE PROFIL DU NAVIGATEUR SUR DISQUEcookies · localStorage · IndexedDB · enregistrements service-worker— l'endroit où la « persistance » vit vraiment —
La vie normale d'un service worker. Une page l'enregistre, le navigateur l'écrit sur disque contre l'origine de la page, et à partir de là le navigateur est responsable de réveiller le worker — quand l'utilisateur revisite l'origine, quand un push arrive, quand une synchronisation d'arrière-plan se déclenche. L'adresse du worker est votre disque local ; la durée de vie du worker est celle du profil de navigateur qui l'héberge.

Le bug rapporté par Rebane est, en gros, qu'une page hostile peut abuser d'une API de téléchargement en arrière-plan pour enregistrer un worker que le navigateur refuse ensuite de terminer. D'autres bugs Chromium de service-worker de la même famille ont été livrés avant ; celui-ci est au moins le deuxième des douze derniers mois. Le motif — la page plante quelque chose, le navigateur le stocke, le navigateur le ramène plus tard de lui-même — est la partie qui mérite d'être retenue. C'est aussi le motif qui se heurte à un mur dès lors que le navigateur cesse de posséder un morceau de disque à longue durée de vie.

Ce qu'exige la persistance

La persistance — au sens sécurité, pas au sens plateforme web — est le mouvement qu'un attaquant fait immédiatement après l'accès initial. Une page ou un binaire atterrit sur votre machine et, avant de faire quoi que ce soit d'autre d'intéressant, s'arrange pour qu'une partie de lui-même survive aux étapes de nettoyage évidentes : fermer l'onglet, quitter le navigateur, redémarrer, mettre le portable en veille dans le train du retour. Sur un OS de bureau, la persistance a tout un vocabulaire — launch agents sur macOS, tâches planifiées sur Windows, unités systemd utilisateur sur Linux, extensions de navigateur, entrées de démarrage automatique.

Un service worker est, de fait, un launch agent intégré au navigateur. Il est enregistré, stocké et ramené à la vie selon un planning que le navigateur contrôle. C'est l'une des primitives de persistance les plus élégantes qu'offre la plateforme web, ce qui explique en partie pourquoi elle ne cesse d'attirer des bugs.

Crucialement, toutes les formes de persistance de cette liste ont la même exigence, énoncée en trois mots : quelque chose doit persister. Un launch agent persiste parce qu'il existe un système de fichiers sur lequel le .plist de l'agent est écrit, et l'agent est relu depuis ce système de fichiers à chaque démarrage. Un service worker persiste parce qu'il existe un profil de navigateur sur lequel l'enregistrement du worker est écrit, et l'enregistrement est relu à chaque démarrage du navigateur. Retirez la couche de stockage et la « persistance » part avec.

Cette phrase — retirez la couche de stockage et la persistance part avec — est la partie de l'architecture autour de laquelle Bromure a été écrit.

Comment Bromure fait tourner un navigateur

Une session Bromure n'est pas un processus. C'est une machine virtuelle. Quand vous ouvrez une fenêtre de navigateur, l'hôte lance un petit guest Linux jetable sur l'hyperviseur d'Apple Silicon. Le processus de navigateur à l'intérieur de ce guest est celui dans lequel vos onglets tournent. Le guest a son propre système de fichiers, sa propre mémoire, son propre noyau. Aucune de ces choses n'est celle de l'hôte.

Il y a deux morceaux de cette architecture qui comptent en particulier pour le bug de service-worker.

Premièrement, le profil sur disque dans lequel l'enregistrement d'un service worker vivrait normalement est à l'intérieur du système de fichiers du guest, pas du système de fichiers de votre Mac. Quand le guest disparaît, ce système de fichiers part avec. Deuxièmement, le guest lui-même a une durée de vie que l'utilisateur définit — par défaut, un guest est lié à un profil (travail, personnel, banque, bac à sable de coding LLM) et est effacé quand l'utilisateur le choisit, mais pour les onglets éphémères le guest est détruit à la fermeture de la fenêtre. Même en mode profil, le guest est un instantané de volume de disque que l'utilisateur peut faire revenir à tout moment.

Navigateur traditionnelVOTRE UTILISATEUR · VOTRE DISQUEL'onglet visite une page hostileenregistre un service workerWorker écrit dans le profil~/Library/Application Support/...sur votre vrai disqueVous fermez l'onglet…le worker reste sur disque…Le navigateur le ranime plus tardau prochain lancement, sur push, sur sync —le JS continue de tourner. Pendant des mois.BromureVM INVITÉE JETABLESYSTÈME DE FICHIERS INVITÉL'onglet visite une page hostileenregistre un service workerWorker écrit sur le disque invitéà l'intérieur de la VM — pas sur l'hôtene touche jamais le système de fichiers du MacVous fermez l'ongletLa VM est détruitele disque du guest part avec elle.rien à ranimer.
Le même service worker hostile enregistré contre deux navigateurs. Dans un navigateur traditionnel, l'enregistrement est écrit dans un profil sur le disque hôte de l'utilisateur ; fermer le navigateur ne le supprime pas, et le prochain lancement ramène le worker. Dans Bromure, l'enregistrement est écrit dans le système de fichiers d'une VM invitée ; fermer l'onglet détruit la VM, et l'enregistrement part avec.

Dans le cas jetable, le worker « qui ne meurt jamais » n'a nulle part où ne pas mourir. La ligne sur disque depuis laquelle il aurait été relu au prochain lancement du navigateur était à l'intérieur d'une machine virtuelle qui n'existe plus. Le JavaScript qui « continuait de tourner après la fermeture du navigateur » ne continuait à tourner que parce qu'il y avait quelque chose sur quoi le faire tourner. Quand la fermeture du navigateur ferme aussi la machine sous-jacente, le worker s'arrête en même temps que le navigateur — exactement le comportement que la spec originale supposait.

Dans le cas profil — disons que vous gardez un profil « réseaux sociaux » à longue durée de vie qui est censé se souvenir de vous entre les sessions — le worker survit comme il l'a toujours fait, limité à la VM de ce seul profil. Il ne peut toujours pas voir vos Documents, votre trousseau, vos autres profils, ni le reste de votre ordinateur. Et si jamais vous soupçonnez que ce profil a attrapé quelque chose qu'il ne devrait pas avoir, vous pouvez ramener l'ensemble de la VM à son dernier snapshot propre, ce qui prend à peu près autant de temps que de relancer un navigateur.

Le point général

Il serait facile de lire ce qui précède comme « Bromure se trouve par hasard vaincre ce bug de service-worker spécifique. » C'est vrai, mais ce n'est pas la prétention intéressante. La prétention intéressante est plus générale, et cet incident en est une petite pièce à conviction.

Les zero-days de navigateurs continueront d'atterrir. C'est au moins le deuxième bug de service-worker Chromium des douze derniers mois. Les grands de 2024 et 2025 étaient des confusions de types V8 et des évasions de sandbox Mojo, payés à six chiffres, utilisés par des éditeurs commerciaux de spyware et des groupes alignés sur des États. Il y a eu des compromissions de renderer avant que les service workers n'existent ; il y aura des compromissions de renderer longtemps après que ce bug en particulier sera corrigé. L'économie de la découverte de nouveaux — surtout avec les auditeurs IA de classe Mythos maintenant dans la boucle des deux côtés de la fenêtre de divulgation — va contre le défenseur, pas dans son sens.

La question pertinente, quand un zero-day part, n'est pas le navigateur était-il sans faille. Il ne l'était pas. La question pertinente est ce que l'exploit a réellement atteint.

Mur = sandbox in-processRenderer (V8, Blink, codecs…)SANDBOX (Mojo IPC, seccomp)même généalogie d'espace d'adressage que le bugProcessus navigateur (broker)VOTRE HÔTEDocuments · Trousseau · clés SSH · ~/.awsiCloud · webcam · microphone · réseau local— ce qu'une évasion de sandbox atteint —Mur = VM séparéeRenderer (V8, Blink, codecs…)Processus navigateur (broker) — dans le guestFRONTIÈRE HYPERVISEURnoyau séparé, espace d'adressage séparé,aucun octet partagé avec le bugVOTRE HÔTEDocuments, Trousseau, clés SSH, ~/.aws —aucun de ceux-ci n'est visible au guest.— ce qu'une évasion de sandbox atteint : encore du guest —
Deux façons pour un attaquant d'étendre sa portée après qu'un bug de navigateur a tiré. Sur un navigateur traditionnel, la sandbox in-process est le mur entre le code du renderer et l'hôte ; quand quelqu'un trouve un bug mémoire dans V8 ou dans un parseur, c'est ce mur qui craque. Sur Bromure, le mur est une VM séparée qui ne partage pas d'espace d'adressage avec le navigateur, ne parse pas d'octets web, et ne se casse pas quand un renderer se casse.

Une évasion de sandbox Chromium — le « zero-day de navigateur » canonique dont vous avez lu parler tous les quelques mois depuis dix ans — est une chaîne qui prend un bug mémoire dans un morceau du renderer (V8, Skia, WebP, Dawn, libxml2) et l'utilise pour convaincre le processus broker du navigateur de faire quelque chose que le renderer n'était pas censé pouvoir faire. Ces bugs sont payés à six chiffres parce qu'ils franchissent le mur que les ingénieurs de Chromium passent l'essentiel de leur temps à maintenir : la sandbox in-process.

Ce mur est une remarquable pièce d'ingénierie. C'est aussi, généalogiquement, fait du même genre de matière que le bug qui l'a franchi — du C++, des espaces d'adressage partagés, des primitives IPC comme Mojo. Quand un nouveau bug mémoire apparaît dans V8 ou dans l'un des cent parseurs livrés avec Chromium, c'est le genre de chose qui peut, avec assez de travail, faire levier sur la sandbox.

Une VM est un autre genre de mur. Le navigateur qui tourne à l'intérieur n'a pas d'adresse mémoire virtuelle qui mappe vers le noyau de l'hôte ; le noyau de l'hôte ne parse pas la page que le navigateur parse ; la pile de virtualisation est une surface minuscule comparée à un navigateur, et ce qu'elle parse (états de registres vCPU, hypercalls paravirt, descripteurs de buffers virtio) n'est pas des octets adversaires arrangés par une page web. Des bugs à cette frontière existent. Ils sont dans une autre catégorie de difficulté, sont plusieurs ordres de grandeur plus rares, et quand ils apparaissent ils sont en général annoncés par l'éditeur de l'hyperviseur avec une CVE.

Ce que cela change pour l'utilisateur

Dans le cas spécifique du bug de service-worker exposé la semaine dernière, un utilisateur Bromure n'a pas besoin d'attendre un correctif. Le bug existe aussi dans son navigateur — Bromure embarque Chromium upstream — mais le comportement que le bug permet est le comportement que l'architecture de Bromure désactive déjà. Un worker qui « vit pour toujours » ne vit qu'aussi longtemps que la VM invitée dans laquelle il a été enregistré, ce qui est au plus aussi long que le profil que vous gardez, et au moins aussi long que l'onglet est ouvert.

Dans le cas plus général de « un zero-day de navigateur va atterrir quelque part dans le mois prochain », un utilisateur Bromure obtient une forme d'issue différente. L'exploit tire, dans le pire des cas, contre le contenu du guest : la session de navigateur, les cookies des sites actuellement connectés à l'intérieur de ce profil, tout ce que l'utilisateur a tapé dans ce navigateur. C'est une vraie perte et nous n'essayons pas de la cacher. Ce qui n'est pas perdu, c'est l'hôte : pas de clés SSH, pas de ~/.aws, pas de trousseau, pas de Documents, pas d'arbre source, pas d'espace de travail d'agent LLM.

Ce compromis — la session de navigateur peut être compromise, l'hôte non, et la session elle-même est courte — est le compromis qui continue d'avoir du sens à mesure que le taux de nouveaux bugs de navigateur augmente.

Ce que nous ne prétendons pas

Deux mises en garde, énoncées ouvertement :

Les VM jetables ne patchent pas Chromium

Le bug de service-worker est un bug Chromium. Les bonnes personnes pour le corriger sont les ingénieurs de Chromium, et ils devraient le faire. L'architecture de Bromure change le coût du bug non corrigé ; ce n'est pas un substitut à sa correction. Nous remontons les choses en amont quand nous en trouvons, et nous lisons les mêmes advisories que tout le monde.

Une évasion de VM est une catégorie réelle

Un bug dans l'hyperviseur lui-même — le framework Virtualization d'Apple, ou l'une des plus petites surfaces autour — permettrait, en principe, à un attaquant d'atteindre l'hôte. Ces bugs existent. Ils sont aussi plusieurs ordres de grandeur plus rares que les bugs mémoire de navigateur, et la surface est dramatiquement plus petite. Bromure rend l'exposition de l'hôte à une compromission de renderer conditionnée à cette classe de bug, plutôt qu'à la classe qui livre toutes les quelques semaines.

Le titre que vous lirez le mois prochain

Il y aura un autre bug de service-worker Chromium. Il y aura une autre confusion de types V8. Il y aura un autre heap overflow libwebp. Il y aura un autre zero-day qu'un adversaire extrêmement bien doté a utilisé contre des personnes extrêmement spécifiques pendant extrêmement longtemps, qui devient public un mardi matin avec une advisory de correctif d'urgence.

La question utile, au matin de l'un quelconque de ces événements, n'est pas si vous avez déjà redémarré votre navigateur. La question utile est ce qui se trouvait réellement à l'intérieur de la boîte dans laquelle le bug a atterri. Pour la plupart des utilisateurs, aujourd'hui, cette boîte est la même boîte qui contient leurs fichiers et leur trousseau et leurs identifiants enregistrés. Pour un utilisateur Bromure, la boîte est une machine virtuelle invitée dont la perte dans le pire des cas est la fenêtre qu'il s'apprêtait de toute façon à fermer.

La persistance exige quelque chose sur quoi persister. Rendez ce quelque chose jetable, et toute une classe d'attaques cesse d'être une classe d'attaques.