Closed Bug 2017108 (CVE-2026-4725) Opened 6 months ago Closed 6 months ago

Use-after-free in CanvasTranslator::SetDataSurfaceBuffer via dangling reference after map clear, reachable from content process through PCanvas

Categories

(Core :: Graphics: Canvas2D, defect)

defect

Tracking

()

RESOLVED FIXED
149 Branch
Tracking Status
firefox-esr115 --- unaffected
firefox-esr140 --- unaffected
firefox147 --- wontfix
firefox148 --- wontfix
firefox149 + fixed

People

(Reporter: juny24602, Assigned: lsalzman)

References

(Regression)

Details

(6 keywords, Whiteboard: [client-bounty-form][adv-main149+])

Attachments

(3 files)

Attached patch repro.patchSplinter Review

Root Cause

CanvasTranslator::SetDataSurfaceBuffer (gfx/layers/ipc/CanvasTranslator.cpp, line 420-430) takes a reference to an entry in std::map mDataSurfaceShmems, then on Map() failure clears the entire map via DataSurfaceBufferWillChange(0, false), and continues to use the now-dangling reference.

auto& dataSurfaceShmem = mDataSurfaceShmems[aId];   // line 420: ref
dataSurfaceShmem.mShmem = aBufferHandle.Map();      // line 421: fails
if (!dataSurfaceShmem.mShmem) {
  DataSurfaceBufferWillChange(0, false);            // line 424: clear()
  dataSurfaceShmem.mShmem = aBufferHandle.Map();    // line 426: UAF write
  if (!dataSurfaceShmem.mShmem) {                   // line 427: UAF read
    return false;
  }
}

DataSurfaceBufferWillChange(0, false) with aId=0 and aKeepAlive=false enters the else branch at line 339, iterates entries, and calls mDataSurfaceShmems.clear() (line 361).

This destroys the DataSurfaceShmem entry that dataSurfaceShmem still references. Lines 426-427 then operate on freed memory.

The move-assignment on line 426 invokes MappingBase::operator= which performs std::swap on freed mMemory and mSize members, causing both UAF read and UAF write.

Triggerability

A compromised content process can reliably trigger this by sending a MutableSharedMemoryHandle whose underlying memfd has been sealed with F_SEAL_WRITE.

The seal causes the GPU process's mmap(PROT_WRITE) to fail with EPERM, making Map() return a null mapping on both attempts while the reference-invalidation path is taken.

Tested on Firefox commit:

f8345be3cabc1944760d536c4a43192a02000406
Sun Feb 15 23:53:08 2026 +0000

Attack Scenario

A compromised content process:

  1. Creates a PCanvas actor via CanvasManagerChild.
  2. Serializes 3 recorded events into shared memory:
    • CANVAS_DRAW_TARGET_CREATION (initializes translator draw target)
    • SETCURRENTDRAWTARGET (sets active draw target)
    • PAUSE_TRANSLATION (puts translator into Paused state)
  3. Calls SendInitTranslator to start GPU-side translation.
  4. Waits for the translator to reach Paused state (required by SetDataSurfaceBuffer).
  5. Creates a 4096-byte shared memory region (memfd with MFD_ALLOW_SEALING).
  6. Clones the handle and seals the clone with fcntl(F_ADD_SEALS, F_SEAL_WRITE).
    The seal applies to the underlying inode, so the original handle is also affected.
  7. Calls SendSetDataSurfaceBuffer(1, sealed_handle).
  8. GPU process enters SetDataSurfaceBuffer:
    • mDataSurfaceShmems[1] inserts a new entry and takes a C++ reference.
    • Map() fails (EPERM due to F_SEAL_WRITE).
    • DataSurfaceBufferWillChange(0, false) clears the map and frees the entry.
    • The dangling reference is used for a second Map() attempt: UAF write + read.

Reproduction

Build Firefox with ASan:

mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-debug
ac_add_options --enable-address-sanitizer
ac_add_options --disable-jemalloc
ac_add_options --disable-crashreporter
ac_add_options --disable-elf-hack
ac_add_options --disable-install-strip
ac_add_options --enable-debug

Apply the attached patch and rebuild, then run:

./mach mochitest dom/bindings/test/test_canvas_uaf.html

Expected result: ASan reports a heap-use-after-free in the GPU process.

Flags: sec-bounty?
Group: firefox-core-security → core-security
Component: Security → Graphics: Canvas2D
Product: Firefox → Core
Attached file new_uaf_crash.txt
Attachment #9545454 - Attachment is patch: true
Attachment #9545454 - Attachment mime type: text/x-patch → text/plain
Attachment #9545454 - Attachment description: new_uaf.patch → repro.patch
Attachment #9545454 - Attachment filename: new_uaf.patch → repro.patch
Group: core-security → gfx-core-security
Flags: needinfo?(lsalzman)
Flags: needinfo?(lsalzman)
Attached file (secure)
Assignee: nobody → lsalzman
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true

Comment on attachment 9546258 [details]
(secure)

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: Not sure how exploitable this is, though it does lead to a use-after-free in the parent/GPU process.
  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: Unknown
  • Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: 147
  • If not all supported branches, which bug introduced the flaw?: Bug 2002342
  • Do you have backports for the affected branches?: Yes
  • If not, how different, hard to create, and risky will they be?:
  • 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
Attachment #9546258 - Flags: sec-approval?

There's not much window between the free and the re-use to hope that another thread has reallocated that memory with something useful to overwrite, but this is a clever attack on the parent.

Comment on attachment 9546258 [details]
(secure)

Beta/Release Uplift Approval Request

  • User impact if declined/Reason for urgency: Potential use-after-free in GPU/parent process.
  • Is this code covered by automated tests?: Unknown
  • Has the fix been verified in Nightly?: Yes
  • Needs manual test from QE?: No
  • If yes, steps to reproduce:
  • List of other uplifts needed: None
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky): Avoids holding reference during clear.
  • String changes made/needed:
  • Is Android affected?: Yes
Attachment #9546258 - Flags: approval-mozilla-beta?

(In reply to Daniel Veditz [:dveditz] from comment #4)

There's not much window between the free and the re-use to hope that another thread has reallocated that memory with something useful to overwrite, but this is a clever attack on the parent.

(Note: this still needs further verification.) An attacker may be able to widen the free-to-use window by accumulating up to ~400 entries with valid mappings in mDataSurfaceShmems (bounded by gfx.canvas.accelerated.max-data-shmems) before triggering the use-after-free.

Comment on attachment 9546258 [details]
(secure)

Rejecting beta uplift request.
Fx148 is already in release and go-live is next week. We only take sec-high bugs in the planned dot release.

Attachment #9546258 - Flags: approval-mozilla-beta? → approval-mozilla-beta-
Group: gfx-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 6 months ago
Resolution: --- → FIXED
Target Milestone: --- → 149 Branch
Flags: sec-bounty? → sec-bounty+
Attachment #9546258 - Flags: sec-approval? → sec-approval+
QA Whiteboard: [qa-triage-done-c150/b149]
QA Whiteboard: [qa-triage-done-c150/b149] → [sec] [qa-triage-done-c150/b149]
Whiteboard: [client-bounty-form] → [client-bounty-form][adv-main149+]
Alias: CVE-2026-4725
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: