Threat Intelligence

CVE-2026-85046 : quand Maglev inline un tri, la map d'un tableau V8 change dans le dos du compilateur

Admin CyberAfrik 05 September 2026 34 lectures
CVE-2026-85046 : quand Maglev inline un tri, la map d'un tableau V8 change dans le dos du compilateur

Rapport CTI technique du 5 septembre 2026

Résumé exécutif

Google a publié le jeudi 3 septembre 2026 une mise à jour du canal stable de Chrome qui corrige douze vulnérabilités, dont CVE-2026-85046. L'éditeur indique qu'un exploit pour cette faille circule déjà. Il s'agit d'une confusion de type dans V8, le moteur JavaScript et WebAssembly de Chrome, notée CVSS 8.8, qui permet à un attaquant distant d'exécuter du code arbitraire à l'intérieur du bac à sable via une page HTML forgée. C'est le sixième zero-day Chrome corrigé en 2026. Les versions correctives sont 152.0.7977.82 et .83 pour Windows et macOS, 152.0.7977.82 pour Linux.

La particularité de ce cas est que le chercheur à l'origine du signalement, Salvatore Gulizia (Serotav), a publié son analyse technique complète le 28 août 2026, avant même la sortie du correctif. Le bug se situe dans TryReduceArrayPrototypeSort du constructeur de graphe Maglev, où V8 remplace un appel à Array.prototype.sort par un tri par insertion inliné. La vérification de map après exécution du comparateur teste l'appartenance à un ensemble de maps possibles au lieu de tester l'égalité avec la map d'origine. Un tableau entraîné à la fois en PACKED_SMI_ELEMENTS et en PACKED_ELEMENTS peut donc basculer de l'un vers l'autre pendant le tri, ce qui donne une lecture et une écriture arbitraires sur le tas JavaScript. Gulizia a touché 1 000 dollars de prime et a chaîné la faille avec une évasion de bac à sable n-day pour valider le v8CTF. Google n'a communiqué ni sur les acteurs, ni sur les cibles, ni sur le mode opératoire des attaques observées.

Chronologie

DateÉvénement
Non daté publiquementIntroduction du bug, commit 66a3f1e94d4b681bff6476a876067a3c79a853f0
4 août 2026Signalement de la faille à Google par Salvatore Gulizia (Serotav)
28 août 2026Publication du write-up technique complet par le chercheur, CVE encore réservé et non public
Avant le 3 septembre 2026Correctif appliqué dans V8, commit e0562d87ad9c17042b581582c99237d798572e67
3 septembre 2026Mise à jour du canal stable Chrome desktop, 12 correctifs. Google reconnaît l'existence d'un exploit dans la nature
4 septembre 2026Reprise par Help Net Security, SecurityWeek, BleepingComputer, The Hacker News et SOC Prime
5 septembre 2026Déploiement progressif de la mise à jour toujours en cours sur le parc

Fiche vulnérabilité (format fiche CTI)

CVE-2026-85046 | CVSS 8.8 | V8, moteur JavaScript et WebAssembly de Chrome, versions antérieures à 152.0.7977.82

Confusion de type (CWE-843) dans les compilateurs optimisants de V8. Le chemin d'inlining de Array.prototype.sort accepte un site d'appel dont le retour de feedback contient à la fois PACKED_SMI_ELEMENTS et PACKED_ELEMENTS, les deux stockant des slots tagged dans un FixedArray. Après l'exécution du comparateur fourni par l'utilisateur, le garde de sécurité vérifie que la map courante du receveur appartient à l'ensemble receiver_maps_before_loop, sans vérifier qu'elle est identique à celle observée avant la boucle. Un comparateur qui appelle Array.prototype.fill fait migrer la map à rebours dans la chaîne des elements kinds, de PACKED_ELEMENTS vers PACKED_SMI_ELEMENTS, sans réallouer le stockage. La recopie du tableau temporaire replace ensuite des pointeurs d'objets dans un tableau que V8 considère désormais comme un tableau de petits entiers.

Conditions d'exploitation : aucune interaction au-delà de la visite d'une page. Le tableau doit contenir au plus 16 éléments (kMaxInlineSortSize), un comparateur interprété doit être fourni, les tableaux troués et PACKED_DOUBLE sont exclus du chemin optimisé, et le site d'appel doit rester autorisé à spéculer. Le bug est présent dans Maglev comme dans TurboFan. L'exécution résultante reste confinée au bac à sable du processus de rendu, une évasion supplémentaire est nécessaire pour atteindre le système hôte.

PoC disponible : oui. Le write-up de Serotav publié le 28 août 2026 fournit le déclencheur, la primitive addrof et la technique de contournement de write barrier. Le code d'exploitation complet du v8CTF n'est pas publié en l'état, mais la chaîne est reconstructible à partir du billet et du billet antérieur du même auteur sur CVE-2026-15776.

Patch : Chrome 152.0.7977.82 ou .83 sur Windows et macOS, 152.0.7977.82 sur Linux. Le correctif consiste à ne plus inliner Array.prototype.sort sur des elements kinds mixtes. Les navigateurs dérivés de Chromium (Edge, Brave, Opera, Vivaldi) doivent intégrer la mise à jour amont, avec le délai habituel.

Diagramme de la chaîne d'attaque

Chronologie et chaîne d'exploitation de CVE-2026-85046 dans V8

Chaîne reconstituée à partir du write-up de Salvatore Gulizia du 28 août 2026 et de l'avis Chrome Releases du 3 septembre 2026.

Analyse technique

Étape 1 : entraîner le site d'appel sur deux elements kinds

Maglev tente de remplacer certains appels de builtins par des versions spécialisées. TryReduceArrayPrototypeSort remplace Array.prototype.sort par un tri par insertion inliné, ce qui évite la transition JavaScript vers builtin à chaque appel du comparateur dans TimSort. Cette optimisation n'accepte que les maps PACKED_SMI_ELEMENTS et PACKED_ELEMENTS, les tableaux troués étant écartés parce que le tri par insertion ne gère pas les trous, et PACKED_DOUBLE parce que les continuations de déoptimisation resteraient trop complexes.

L'attaquant nourrit donc le site d'appel avec les deux formes :

function confuse(arr){    function compare(){        return 0    }    arr.sort(compare); } for (let i = 0; i < 1000; i++){    confuse([6,9,4,2,0]);   // PACKED_SMI_ELEMENTS    confuse([{},{},{}]);    // PACKED_ELEMENTS }

Après un millier d'itérations, Maglev compile confuse avec un ensemble possible_maps contenant les deux maps. Les deux utilisent des slots tagged dans un FixedArray, donc Maglev les accepte ensemble sans réserve.

Étape 2 : le garde qui vérifie la mauvaise chose

Le tri ne se fait pas en place. V8 recopie les éléments dans un FixedArray temporaire, parce que le comparateur fourni par le développeur est du code JavaScript arbitraire qui peut modifier le tableau qu'il est en train de trier. C'est le snapshot _items_ prévu par ECMA-262 23.1.3.30. Une fois le tri terminé, les éléments du tableau temporaire sont recopiés dans le tableau d'origine.

Avant cette recopie, un garde est censé s'assurer que le comparateur n'a rien cassé :

// Guard: comparefn side effects may have changed the receiver's map or // length. if (receiver_maps_were_unstable) {    RETURN_IF_ABORT(AddNewNode<CheckMaps>(        {receiver}, receiver_maps_before_loop,        CheckType::kOmitHeapObjectCheck)); }

Le commentaire annonce une vérification que la map a changé. Le code fait autre chose : CheckMaps vérifie que la map courante figure dans receiver_maps_before_loop. Comme cet ensemble contient les deux maps sur lesquelles la fonction a été entraînée, une transition de l'une vers l'autre passe le contrôle sans lever de déoptimisation. C'est le cœur du bug, une confusion entre appartenance à un ensemble et égalité.

Étape 3 : forcer la migration de map avec fill

Reste à faire migrer un tableau de PACKED_ELEMENTS vers PACKED_SMI_ELEMENTS, c'est-à-dire dans le sens inverse de la chaîne habituelle des elements kinds. V8 spécialise vers le plus général et ne revient normalement pas en arrière : écrire a[2] = 3 dans [1,2,{}] ne rétrograde pas le tableau, et réassigner a ne change pas l'objet sur lequel sort a été appelé.

Array.prototype.fill est l'exception. Dans src/builtins/builtins-array.cc, la branche is_replacing_all_elements porte un commentaire explicite : quand tous les éléments sont remplacés, aucune ancienne valeur ne survit, V8 peut donc choisir l'elements kind optimal pour la nouvelle valeur et migrer la map à rebours dans la chaîne. Comme les deux layouts partagent un FixedArray de slots tagged, le stockage n'a même pas besoin d'être réalloué, un simple JSObject::SetMapAndElements suffit.

Le comparateur devient donc :

function confuse(arr){    function compare(){        arr.fill(0);     // PACKED_ELEMENTS -> PACKED_SMI_ELEMENTS        return 0    }    arr.sort(compare); }

À la sortie, le receveur porte la map PACKED_SMI_ELEMENTS alors que la recopie depuis le tableau temporaire y a réinstallé des pointeurs d'objets.

Étape 4 : addrof, puis contournement de la write barrier

Le comportement observable est immédiat. Les fonctions qui s'appuient sur la map traitent le contenu comme des Smi et exposent l'offset de tas des objets, tandis qu'un accès direct par index renvoie l'objet normalement. La primitive addrof tient en quatre lignes :

function addrof(target) {  let sacrificial = [target,target];  confuse(sacrificial);  return (Number(String(sacrificial).split(',')[0]) << 1) | 1 }

Le fakeobj symétrique ne fonctionne pas ici, parce qu'écrire un nombre dans un slot confondu produit un Smi légitime et que le bit de poids faible reste hors d'atteinte. Gulizia contourne le problème autrement, en faisant sauter une write barrier. Toutes les opérations dépendant de la map du tableau confondu s'exécutent comme sur un tableau de Smi, donc déplacer un pointeur tagged dans ce contexte n'émet aucune barrière. Array.prototype.unshift décale tous les éléments sans réallouer si la capacité le permet :

function trash() {for (let i = 0; i < 36; ++i) new Array(0x1000).fill(1.1);} let bad = [{},{},69]; bad.pop();          // [{},{}, <slot libre>] trash(); trash();   // promotion de bad en Old Space bad[1] = {x:420};   // reference old -> young confuse(bad) bad.unshift(0);     // decalage sans write barrier

À partir de là, un GC mineur puis une réclamation du slot par un faux tableau donnent la lecture et l'écriture arbitraires sur le tas, selon la même mécanique que le précédent travail de l'auteur sur CVE-2026-15776.

Étape 5 : ce qu'il reste à faire pour sortir du bac à sable

CVE-2026-85046 donne l'exécution de code dans le processus de rendu, pas sur la machine. Le chercheur a chaîné son bug avec une évasion de bac à sable n-day pour valider le v8CTF. C'est le modèle attendu pour toute exploitation en conditions réelles : un bug V8 pour le contrôle du renderer, puis une seconde faille, souvent dans Mojo, dans le GPU process ou dans un pilote du noyau, pour franchir la frontière. Google n'a rien publié sur la chaîne réellement employée par les attaquants, ce qui est la pratique habituelle tant que la mise à jour n'est pas largement déployée.

Indicateurs de compromission

Google n'a publié aucun indicateur associé à l'exploitation observée. Les éléments ci-dessous sont des points de détection dérivés du mécanisme et du modèle d'exploitation navigateur, à traiter comme des pistes de chasse et non comme des IOC confirmés.

TypeValeur
VersionChrome antérieur à 152.0.7977.82 sur Linux, 152.0.7977.82 ou .83 sur Windows et macOS
ComportementProcessus renderer Chrome créant un processus enfant inattendu
ComportementCrash répétés du renderer sur un même domaine, signature de tentatives de heap grooming ratées
ComportementPic d'allocation mémoire dans le renderer suivi d'un GC mineur forcé, motif typique du grooming new Array(0x1000).fill(1.1)
FichierÉcriture dans %LOCALAPPDATA% ou ~/.cache par un processus fils du navigateur
RéseauPage HTML servant un script contenant à la fois .sort(, .fill( dans un comparateur et .unshift( sur un tableau court
RéseauChargement de JavaScript fortement obfusqué depuis un domaine tiers sur une page par ailleurs légitime, cohérent avec un watering hole

MITRE ATT&CK

Aucune cartographie officielle n'est publiée pour cette CVE, Google n'ayant pas décrit les attaques. Ce qui suit est notre lecture technique.

TactiqueTechniqueIDJustification
Initial AccessDrive-by CompromiseT1189La visite d'une page HTML forgée suffit à déclencher l'exploitation.
ExecutionExploitation for Client ExecutionT1203La confusion de type donne l'exécution de code dans le processus de rendu.
Defense EvasionExploitation for Defense EvasionT1211Une seconde faille est nécessaire pour franchir le bac à sable du renderer.
Privilege EscalationExploitation for Privilege EscalationT1068Chaînage attendu vers une évasion de bac à sable, comme validé par le chercheur sur le v8CTF.
Credential AccessSteal Web Session CookieT1539Objectif classique d'un contrôle du renderer, l'accès aux cookies et sessions du profil.
CollectionData from Local SystemT1005Post-exploitation attendue après évasion, sur le profil navigateur et le disque.

Remédiation : checklist opérationnelle

  1. Forcer la mise à jour de Chrome vers 152.0.7977.82 ou supérieur sur l'ensemble du parc, sans attendre le déploiement progressif de Google. Le déploiement automatique s'étale sur plusieurs jours ou semaines.
  2. Redémarrer effectivement les navigateurs après mise à jour. Un Chrome à jour sur disque mais non relancé continue de faire tourner le binaire vulnérable, cas très fréquent sur les postes laissés ouverts en permanence.
  3. Vérifier la version réellement en usage plutôt que la version installée, via chrome://version en poste à poste ou via l'inventaire logiciel, en filtrant sur les processus en cours.
  4. Appliquer les mises à jour équivalentes sur les navigateurs dérivés de Chromium présents dans le parc : Edge, Brave, Opera, Vivaldi, ainsi que les clients embarquant un CEF ou un Electron à jour.
  5. Utiliser la stratégie de groupe ou la console d'administration Chrome pour imposer un délai de relance maximal et bloquer le report de mise à jour par l'utilisateur.
  6. Activer l'isolation de site et vérifier que le mode de protection renforcée du navigateur est en place, ce qui n'empêche pas l'exploitation mais réduit la surface post-compromission du renderer.
  7. Cibler en priorité les populations les plus exposées aux campagnes de zero-day navigateur : direction, juridique, journalistes, équipes de développement, administrateurs disposant de sessions cloud privilégiées.
  8. Surveiller la création de processus enfants par les processus renderer de Chrome dans l'EDR, ainsi que les crashs répétés du renderer sur une même origine.
  9. Rappeler que le correctif ferme un seul maillon. Si un poste sensible a pu être exposé avant mise à jour, révoquer les sessions et cookies du profil navigateur concerné plutôt que de s'en tenir à la mise à jour.
  10. Pour les équipes de recherche offensive, retenir que la même erreur de garde peut exister ailleurs dans Maglev et TurboFan. Le motif à chercher est un CheckMaps sur un ensemble de maps là où une égalité avec la map observée serait requise.

Sources

Tags : CVE-2026-85046ChromeV8Maglevtype confusionCWE-843zero-daySerotav
Partager cet article