Type confusion with EGLImageDescriptor in CreateTextureHostOGL
Categories
(Core :: Graphics, defect)
Tracking
()
People
(Reporter: tschuster, Assigned: jnicol)
Details
(Keywords: ai-involved, csectype-sandbox-escape, sec-high, Whiteboard: [adv-main149+r][adv-ESR140.9+r][adv-ESR115.34+r])
Attachments
(6 files, 1 obsolete file)
|
6.71 KB,
patch
|
Details | Diff | Splinter Review | |
|
905 bytes,
text/html
|
Details | |
|
48 bytes,
text/x-phabricator-request
|
dveditz
:
sec-approval+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-beta+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr115+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr140+
|
Details | Review |
Summary
Type confusion in the Firefox parent process via content-controlled pointer injection. /firefox/gfx/layers/ipc/LayersSurfaces.ipdlh defines EGLImageDescriptor with uintptr_t image and uintptr_t fence fields that cross IPC boundaries without validation. /firefox/gfx/layers/opengl/TextureHostOGL.cpp:84-89 casts these directly to EGLImage/EGLSync with no IsSameProcess() check (contrast TextureHost.cpp:310 which guards the analogous MemoryOrShmem::Tuintptr_t). When WebRender uses a GLX backend, /firefox/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp:160 calls GLContextEGL::Cast() on a GLContextGLX*, whose MOZ_ASSERT-only guard (/firefox/gfx/gl/GLContextEGL.h:64) compiles out in release builds, yielding an unchecked downcast that reads a function pointer table through type-confused memory.
Affected Code
File: /firefox/gfx/layers/opengl/TextureHostOGL.cpp, lines 84–89
case SurfaceDescriptor::TEGLImageDescriptor: {
const EGLImageDescriptor& desc = aDesc.get_EGLImageDescriptor();
result = new EGLImageTextureHost(aFlags, (EGLImage)desc.image(),
(EGLSync)desc.fence(), desc.size(),
desc.hasAlpha());
break;
}
This accepts raw uintptr_t values from an untrusted IPC peer and interprets them as pointers in the parent address space. The only legitimate sender is SharedSurface_EGLImage::ToSurfaceDescriptor() at /firefox/gfx/gl/SharedSurfaceEGL.cpp:123, which is designed for same-process sharing only — but nothing enforces this constraint.
File: /firefox/gfx/gl/GLContextEGL.h, lines 63–66
static GLContextEGL* Cast(GLContext* gl) {
MOZ_ASSERT(gl->GetContextType() == GLContextType::EGL);
return static_cast<GLContextEGL*>(gl);
}
MOZ_ASSERT is a no-op when built with --disable-debug. This becomes a raw static_cast with no runtime type check.
File: /firefox/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp, lines 157–168
bool RenderEGLImageTextureHost::WaitSync() {
bool syncSucceeded = true;
if (mSync) { // mSync = 0x4242..., non-zero
const auto& gle = gl::GLContextEGL::Cast(mGL); // TYPE CONFUSION: mGL is GLContextGLX*
const auto& egl = gle->mEgl; // reads shared_ptr at wrong offset
MOZ_ASSERT(egl->IsExtensionSupported(gl::EGLExtension::KHR_fence_sync));
if (egl->IsExtensionSupported(gl::EGLExtension::KHR_wait_sync)) {
syncSucceeded = egl->fWaitSync(mSync, 0) == LOCAL_EGL_TRUE;
} else {
syncSucceeded = egl->fClientWaitSync(mSync, 0, LOCAL_EGL_FOREVER) ==
LOCAL_EGL_CONDITION_SATISFIED; // INDIRECT CALL THROUGH CONFUSED PTR CHAIN
}
Exploit Chain
- Compromised content process calls
PCompositorBridgeChild::CreateTexture()withSurfaceDescriptor=EGLImageDescriptor{image=0x4141414141414141, fence=0x4242424242424242, size={64,64}, hasAlpha=true} - Parent
ContentCompositorBridgeParent::AllocPTextureParent→TextureHost::Create→CreateTextureHostOGLat/firefox/gfx/layers/opengl/TextureHostOGL.cpp:84constructsEGLImageTextureHostwithmImage=(void*)0x4141...,mSync=(void*)0x4242... - Content sends
OpPushExternalImageForTextureviaWebRenderBridgeChild::SendUpdateResources - Parent
WebRenderBridgeParent::RecvUpdateResources→PushExternalImageForTexture→EGLImageTextureHost::CreateRenderTextureat/firefox/gfx/layers/opengl/TextureHostOGL.cpp:1000createswr::RenderEGLImageTextureHost(mImage=0x4141..., mSync=0x4242..., ...)and registers it with the Render thread - Content injects
PushImagefor the forged image key into its display list - Parent Render thread: WebRender renderer →
wr_renderer_lock_external_image→RenderEGLImageTextureHost::Lock(aChannelIndex, aGL)at/firefox/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp:36 mGL = aGL— on GLX backend (gfx.x11-egl.force-disabled=true),aGLis aGLContextGLX*Lock()→WaitSync():mSync != 0→GLContextEGL::Cast(mGL)performs unchecked downcastgle->mEglreads 16 bytes atsizeof(GLContext)+0in aGLContextGLXobject asstd::shared_ptr<EglDisplay>. The shared_ptr's element pointer aliasesGLContextGLX::mContext(a MesaGLXContextheap pointer)egl->IsExtensionSupported(KHR_wait_sync)reads a bit from[GLXContext + offsetof(EglDisplay, mAvailableExtensions)]— succeeds (Mesa struct is large enough). Bit happens to be 0 →fClientWaitSyncbranchegl->fClientWaitSync(mSync, 0, LOCAL_EGL_FOREVER)→EglDisplay::fClientWaitSyncat/firefox/gfx/gl/GLLibraryEGL.h:863→mLib->fClientWaitSync(...):mLibread asRefPtr<GLLibraryEGL>from offset 0 of Mesa's__GLXcontextRecGLLibraryEGL::fClientWaitSyncat/firefox/gfx/gl/GLLibraryEGL.h:431→WRAP(fClientWaitSyncKHR(dpy, sync, flags, timeout))→mSymbols.fClientWaitSyncKHR(dpy, sync, flags, timeout)— indirect call through function pointer at[mLib + 0x1e0]withsync = 0x4242424242424242
IPC Path
- IPDL protocol:
PCompositorBridge(managesPTexture),PWebRenderBridge(carriesOpUpdateResource[]includingOpPushExternalImageForTexture, andSetDisplayListwith injectedPushImageitems) - Parent actor:
ContentCompositorBridgeParent/WebRenderBridgeParent— run in the main Firefox process (no GPU process on GLX-forced Linux) - Child actor:
CompositorBridgeChild/WebRenderBridgeChildin the content process - Messages:
PCompositorBridge::PTextureconstructor withSurfaceDescriptor = EGLImageDescriptorunion armPWebRenderBridge::UpdateResourceswithOpPushExternalImageForTexturePWebRenderBridge::SetDisplayListwithPushImageitem referencing the forged image key
Security Impact
Severity: High / sec-high
Attacker capability: A compromised content process can inject arbitrary pointer values that are dereferenced through a 3-level chain in the parent process, terminating in an indirect function call. The specific faulting address depends on the first 8 bytes of the GLX driver's __GLXcontextRec structure — a layout entirely outside Firefox's control. On Mesa builds where this field is zero, the result is a crash. On drivers/versions where it is a valid pointer (vtable, next-pointer, etc.), the code loads and calls a function pointer from attacker-uncontrolled-but-variable memory with the attacker-controlled 0x4242... value as an argument — a control-flow hijack primitive.
Preconditions:
- Compromised content process (e.g., via JS engine bug) — this is a sandbox escape vector
- WebRender using GLX backend on Linux X11 — requires either
gfx.x11-egl.force-disabled=trueOR natural EGL init failure (old drivers, missing libEGL, distros without EGL). GLX was the historical default and remains the fallback path. - Does NOT require any user interaction beyond the attacker already having code execution in content
Secondary surface (even on EGL backends): The content-controlled mImage/mSync values are passed directly to fEGLImageTargetTexture2D(target, mImage) and egl->fWaitSync(mSync, 0). Mesa's EGL validates handles, but NVIDIA/AMD proprietary drivers and Android EGL implementations may dereference these as raw pointers.
Suggested Fix
Primary fix — reject cross-process raw pointers (/firefox/gfx/layers/opengl/TextureHostOGL.cpp):
case SurfaceDescriptor::TEGLImageDescriptor: {
if (aDeallocator && !aDeallocator->IsSameProcess()) {
gfxCriticalError() << "EGLImageDescriptor from remote process rejected "
"(raw pointer injection)";
return nullptr;
}
const EGLImageDescriptor& desc = aDesc.get_EGLImageDescriptor();
result = new EGLImageTextureHost(aFlags, (EGLImage)desc.image(),
(EGLSync)desc.fence(), desc.size(),
desc.hasAlpha());
break;
}
The same guard should be applied to TSurfaceDescriptorSharedGLTexture in the same switch, which also casts (GLsync)desc.fence() from a cross-process uintptr_t.
Defense-in-depth (/firefox/gfx/gl/GLContextEGL.h):
static GLContextEGL* Cast(GLContext* gl) {
MOZ_RELEASE_ASSERT(gl->GetContextType() == GLContextType::EGL);
return static_cast<GLContextEGL*>(gl);
}
| Reporter | ||
Comment 1•4 months ago
|
||
| Reporter | ||
Comment 2•4 months ago
|
||
| Reporter | ||
Updated•4 months ago
|
| Reporter | ||
Updated•4 months ago
|
| Assignee | ||
Comment 3•4 months ago
|
||
Updated•4 months ago
|
| Assignee | ||
Updated•4 months ago
|
| Assignee | ||
Updated•4 months ago
|
| Assignee | ||
Comment 4•4 months ago
|
||
Comment on attachment 9551523 [details]
(secure)
Security Approval Request
- How easily could an exploit be constructed based on the patch?: The patch quite clearly shows that there is a vulnerability when sending these specific types of SurfaceDescriptor cross process, and from that it's probably fairly obvious which specific fields could be used. The GLX-specific part is probably more difficult to figure out, even with the added assertion in the patch. But as the report notes, proprietary EGL drivers may be vulnerable too.
- Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
- Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: All branches
- If not all supported branches, which bug introduced the flaw?: None
- Do you have backports for the affected branches?: No
- If not, how different, hard to create, and risky will they be?: Patch applies cleanly to all branches
- How likely is this patch to cause regressions; how much testing does it need?: Unlikely
- Is the patch ready to land after security approval is given?: Yes
- Is Android affected?: Yes
Updated•4 months ago
|
Comment 5•4 months ago
|
||
Comment on attachment 9551523 [details]
(secure)
sec-approval+ to land now and start requesting uplifts
Updated•4 months ago
|
Comment 6•4 months ago
|
||
:jnicol fyi we need the uplift requests asap in order to roll this out with 149
Comment 7•4 months ago
|
||
firefox-beta Uplift Approval Request
- User impact if declined: sandbox escape
- Code covered by automated testing: no
- Fix verified in Nightly: no
- Needs manual QE test: no
- Steps to reproduce for manual QE testing: N/A
- Risk associated with taking this patch: low
- Explanation of risk level: Fails when sending invalid textures being sent cross process. Failure path is handled safely
- String changes made/needed: N/A
- Is Android affected?: yes
| Assignee | ||
Comment 8•4 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D286944
Comment 9•4 months ago
|
||
firefox-esr115 Uplift Approval Request
- User impact if declined: sandbox escape
- Code covered by automated testing: no
- Fix verified in Nightly: no
- Needs manual QE test: no
- Steps to reproduce for manual QE testing: N/A
- Risk associated with taking this patch: low
- Explanation of risk level: Fails when sending invalid textures being sent cross process. Failure path is handled safely
- String changes made/needed: N/A
- Is Android affected?: yes
| Assignee | ||
Comment 10•4 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D286944
Comment 11•4 months ago
|
||
firefox-esr140 Uplift Approval Request
- User impact if declined: sandbox escape
- Code covered by automated testing: no
- Fix verified in Nightly: no
- Needs manual QE test: no
- Steps to reproduce for manual QE testing: N/A
- Risk associated with taking this patch: low
- Explanation of risk level: Fails when sending invalid textures being sent cross process. Failure path is handled safely
- String changes made/needed: N/A
- Is Android affected?: yes
| Assignee | ||
Comment 12•4 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D286944
Comment 13•4 months ago
|
||
firefox-release Uplift Approval Request
- User impact if declined: sandbox escape
- Code covered by automated testing: no
- Fix verified in Nightly: no
- Needs manual QE test: no
- Steps to reproduce for manual QE testing: N/A
- Risk associated with taking this patch: low
- Explanation of risk level: Fails when sending invalid textures being sent cross process. Failure path is handled safely
- String changes made/needed: N/A
- Is Android affected?: yes
| Assignee | ||
Comment 14•4 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D286944
| Assignee | ||
Updated•4 months ago
|
Comment 15•4 months ago
|
||
Comment 16•4 months ago
|
||
Updated•4 months ago
|
Updated•4 months ago
|
Comment 17•4 months ago
|
||
| uplift | ||
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Comment 18•4 months ago
|
||
| uplift | ||
Updated•4 months ago
|
Comment 19•4 months ago
|
||
| uplift | ||
Updated•4 months ago
|
Updated•4 months ago
|
Updated•1 month ago
|
Description
•