Closed Bug 1991458 (CVE-2025-13012) Opened 8 months ago Closed 7 months ago

AddressSanitizer: heap-use-after-free and attempting-double-free in CrashStatsLogForwarder (potentially due to race-condition)

Categories

(Core :: Graphics, defect)

defect

Tracking

()

RESOLVED FIXED
146 Branch
Tracking Status
firefox-esr115 145+ fixed
firefox-esr140 145+ fixed
firefox143 --- wontfix
firefox144 --- wontfix
firefox145 + fixed
firefox146 --- fixed

People

(Reporter: sourc7, Assigned: tnikkel)

Details

(Keywords: csectype-race, reporter-external, sec-high, Whiteboard: [client-bounty-form][adv-main145+][adv-ESR140.5+][adv-ESR115.30+])

Attachments

(8 files, 1 obsolete file)

Summary

When simulating swgl gl.cc new_buf realloc failure as on my previous bug 1975837, it able to generate a lot of Crash Annotation GraphicsCriticalError on log stdout, interestingly in rare case it able to hit AddressSanitizer: heap-use-after-free and attempting-double-free in CrashStatsLogForwarder.

Analysis

From the ASan log and local LLM analysis, the crash is probably caused by unsynchronised access/data race between main thread (T0) and render thread (T52) to a shared std::vector<std::string> that lives in CrashStatsLogForwarder instance, as follow:

  1. Render thread (T52)
  • Creates a temporary std::string (S₁).
  • Stores that string into an element of CrashStatsLogForwarder::mStrings. The vector now owns S₁ and its internal character buffer.
  1. Main thread (T0)
  • Calls CrashStatsLogForwarder::UpdateStringsVector(const std::string&) with a different string (S₂).
  • Inside that function the code assigns (S₂) into the same vector slot that already holds (S₁)
  • The assignment also triggers the destructor of the old string (std::string::_Rep::_M_dispose).
  • That destructor calls operator delete to free the character buffer that belonged to S₁.
  1. Render thread (T52) (later)
  • Still iterating over mStrings in UpdateCrashReport() and tries to write S₁ to an std::ostream.
  • The stream’s operator<< internally calls std::char_traits<char>::copy, which uses memcpy().
  • It now read/copies (S₁) from the dangling buffer that was already freed by the main thread, resulting the ASan "heap‑use‑after‑free".

I'm able to reproduce the UAF and double-free on Firefox ESR140 build (commit 9571910, Sep 27 2025) and Firefox ESR128 build (commit 4442784, Jul 3 2025)

I haven't receive the UAF or double-free report on Firefox Nightly build yet, probably due to new assertion Hit MOZ_CRASH(bug: unable to lock indirect) at gfx/wr/webrender/src/compositor/sw_compositor.rs:164, so it crash early and won't generate a lot Crash Annotation GraphicsCriticalError log.

Flags: sec-bounty?
Group: firefox-core-security → gfx-core-security
Component: Security → Graphics
Product: Firefox → Core

Looks like CrashStatsLogForwarder::UpdateStringsVector doesn't acquire mMutex like CrashStatsLogForwarder::Log does, which seems bad. The latter can call the former, in which case the lock is acquired, but so can two Recv methods. It might be worth looking into the thread locking annotations.

The RecvGraphicsError could be easily triggered from a compromised content process, but I don't know about the other stack.

sec-high might be overkill but I'll err on the side of caution here.

Tim, is the referenced code an area you have any knowledge of? Or do you know who might be a good candidate to look at this?

Severity: -- → S2
Flags: needinfo?(tnikkel)
Assignee: nobody → tnikkel
Attached file (secure)

Comment on attachment 9518021 [details]
(secure)

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: pretty easy to tell that the problem was a missing lock from the patch
  • 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
  • If not all supported branches, which bug introduced the flaw?: None
  • Do you have backports for the affected branches?: Yes
  • If not, how different, hard to create, and risky will they be?: easy
  • How likely is this patch to cause regressions; how much testing does it need?: very unlikely
  • Is the patch ready to land after security approval is given?: Yes
  • Is Android affected?: Yes
Flags: needinfo?(tnikkel)
Attachment #9518021 - Flags: sec-approval?

Comment on attachment 9518021 [details]
(secure)

Approved to land and request uplift

FWIW for next time:

How easily could an exploit be constructed based on the patch?:
pretty easy to tell that the problem was a missing lock from the patch
Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?:
No

The answer to the first question is really the answer to the second: 'No, these other things don't tell you, but it's pretty easy to tell we're missing a lock'. Calling out those things specifically is more of a reminder to check for them. The first question is more asking about how easy it is to trigger the bug, or reverse engineer a test case from the patch: Is it a difficult to trigger race condition, is the patch in a wildly different part of the code or is it right at the surface level of IPC or WebIDL? Is the exploit primitive very limited (one byte overwrite of a null byte) or very powerful (general UAF in the main allocation arena).

Attachment #9518021 - Flags: sec-approval? → sec-approval+

Yes I understood the questions, I was not linking the two answers to those two questions in my head, but I guess it sounded like I was making some link between those two answers for someone reading them?

Group: gfx-core-security → core-security-release
Status: NEW → RESOLVED
Closed: 7 months ago
Resolution: --- → FIXED
Target Milestone: --- → 146 Branch

The patch landed in nightly and beta is affected.
:tnikkel, is this bug important enough to require an uplift?

For more information, please visit BugBot documentation.

Flags: needinfo?(tnikkel)

firefox-beta Uplift Approval Request

  • User impact if declined: sec-high
  • Code covered by automated testing: no
  • Fix verified in Nightly: no
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: puts variable access behind lock where it should be
  • String changes made/needed: none
  • Is Android affected?: yes
Attachment #9520186 - Flags: approval-mozilla-beta?
Attached file (secure)

firefox-esr140 Uplift Approval Request

  • User impact if declined: sec-high
  • Code covered by automated testing: no
  • Fix verified in Nightly: no
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: puts variable access behind lock where it should be
  • String changes made/needed: none
  • Is Android affected?: yes
Attachment #9520187 - Flags: approval-mozilla-esr140?
Attached file (secure)

firefox-esr115 Uplift Approval Request

  • User impact if declined: sec-high
  • Code covered by automated testing: no
  • Fix verified in Nightly: no
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: puts variable access behind lock where it should be
  • String changes made/needed: none
  • Is Android affected?: yes
Attachment #9520188 - Flags: approval-mozilla-esr115?
Attached file (secure)

We're arguing between moderate and high for this race bug. We've decided to call it "high" on the low end for bounty purposes. We'll grant control of the messages from the child process, but if you can demonstrate the ability for a web content process to cause sufficient swgl errors to reliably trigger this race without a hacked process then we will consider increasing the bounty.

Flags: sec-bounty? → sec-bounty+
Flags: needinfo?(tnikkel)
Attachment #9520187 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+
Attachment #9520188 - Flags: approval-mozilla-esr115? → approval-mozilla-esr115+

Timothy, your patch does not apply to the esr115 branch, could your rebase/update it please? Thanks

Flags: needinfo?(tnikkel)

Sorry about that. Updated, should be good now.

Flags: needinfo?(tnikkel)
Attachment #9520186 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
QA Whiteboard: [sec] [uplift] [qa-triage-done-c146/b145]
Whiteboard: [client-bounty-form] → [client-bounty-form][adv-main145+][adv-ESR140.5+][adv-ESR115.30+]
Attached file advisory.txt (obsolete) —
Attachment #9525020 - Attachment is obsolete: true
Attached file advisory.txt
Alias: CVE-2025-13012
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: