Medium CVSS 5.3 firefox UAF 🔧 Commit mapped

Overview

Medium
Severity
5.3
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionAssuming a controlled failed memory allocation, an attacker could have caused a use-after-free, leading to a potentially exploitable crash.
ComponentDOM
Bug ClassUAF
Tracker1915535
Fix commit38691eced5eb (firefox) +10/-6
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedIrvan Kurniawan
Disclosed2025-01-07

Background

`nsLineBreaker`
A DOM/layout helper that accumulates text into a buffer (mCurrentWord) and computes line-break opportunities across queued text items when a word boundary is reached.
`FlushCurrentWord`
The method that processes the currently buffered word, distributes computed break data back to its constituent mTextItems, and then resets the accumulator state for the next word.
`MakeScopeExit`/`ScopeExit`
A RAII utility from mozilla/ScopeExit.h that runs a supplied lambda unconditionally when the enclosing scope is exited, including on early return.
Fallible allocation
An allocation performed with the mozilla::fallible tag that returns a failure indication instead of aborting, requiring the caller to handle out-of-memory (OOM) explicitly.

Root Cause Analysis

nsLineBreaker::FlushCurrentWord buffers text in mCurrentWord and tracks the source spans in mTextItems, and it is expected to clear both containers along with the related word-state flags once the word has been flushed. In the vulnerable code these resets (mCurrentWord.Clear(), mTextItems.Clear(), and the flag assignments) were performed only at the very end of the function, after the success path. When the early fallible allocation of breakState failed (if (!breakState.AppendElements(length, mozilla::fallible))), the function returned NS_ERROR_OUT_OF_MEMORY without ever clearing mCurrentWord or mTextItems, so the stale entries in mTextItems — which reference text-frame/sink objects that may be destroyed after the flush fails — survived into subsequent calls and were later dereferenced, yielding a use-after-free.

The fix installs a MakeScopeExit cleanup lambda at function entry that clears mCurrentWord and mTextItems and resets every word-state flag unconditionally, guaranteeing the accumulator is reset on every exit path, including the OOM early return. Because the reset now runs regardless of how the function returns, no stale, dangling text-item references can persist for a later flush to touch.

Key insight
The single mistake was placing the accumulator-reset code only on the function’s success path, so a controlled fallible-allocation failure skipped it and left dangling references in mTextItems; the fix converts the reset into a MakeScopeExit RAII cleanup that executes on all return paths.

Attack Path

  1. Prime the accumulator Construct DOM content whose text feeds nsLineBreaker so that mCurrentWord and mTextItems hold entries referencing live text objects when a flush is triggered.
  2. Force the fallible allocation to fail Use controlled memory pressure so that the breakState.AppendElements(length, mozilla::fallible) call inside FlushCurrentWord returns failure, taking the early return NS_ERROR_OUT_OF_MEMORY branch before the tail reset.
  3. Leave stale references behind Because the early return skipped the clears, mTextItems retains pointers to text objects that are subsequently torn down as the failed operation unwinds.
  4. Trigger reuse of the dangling state Drive a later text/line-breaking operation that reads or flushes the still-populated mTextItems, dereferencing the freed objects.
  5. Achieve a controlled crash The use-after-free produces a potentially exploitable crash whose freed memory may be groomed by the attacker.

Impact Assessment

An attacker who can control a memory-allocation failure gains a use-after-free within the content process handling DOM/layout, leading to a potentially exploitable crash. The precondition is the ability to induce a targeted OOM at the breakState allocation while attacker-influenced text is buffered in nsLineBreaker, so exploitation requires reliable heap/allocation grooming rather than a simple malformed input. The impact is rated moderate, consistent with a hard-to-control OOM-gated UAF.

Files Changed

  • dom/base/nsLineBreaker.cpp

Audit Directions

  • Early-return-skipped cleanup
    Look for functions that reset or free shared/member state only at the tail while having earlier return branches (especially fallible-allocation failures) that bypass that cleanup.
  • Fallible allocation error paths
    Audit uses of mozilla::fallible where an AppendElements/allocation failure returns early, checking that all buffers and back-references touched before the failure are consistently invalidated.
  • RAII-convertible manual resets
    Flag manual multi-field state resets that would be more safely expressed as MakeScopeExit/ScopeExit guards, since manual placement is easily defeated by added early-exit paths.
diff --git a/dom/base/nsLineBreaker.cpp b/dom/base/nsLineBreaker.cpp
index 7175e9ab1eb..1edcfdd05d2 100644
--- a/dom/base/nsLineBreaker.cpp
+++ b/dom/base/nsLineBreaker.cpp
@@ -15,6 +15,7 @@
 #include "mozilla/intl/LineBreaker.h"  // for LineBreaker::ComputeBreakPositions
 #include "mozilla/intl/Locale.h"
 #include "mozilla/intl/UnicodeProperties.h"
+#include "mozilla/ScopeExit.h"
 #include "mozilla/StaticPrefs_intl.h"
 
 using mozilla::AutoRestore;
@@ -150,6 +151,15 @@ static void SetupCapitalization(const char16_t* aWord, uint32_t aLength,
 }
 
 nsresult nsLineBreaker::FlushCurrentWord() {
+  auto cleanup = MakeScopeExit([&] {
+    mCurrentWord.Clear();
+    mTextItems.Clear();
+    mCurrentWordMightBeBreakable = false;
+    mCurrentWordContainsMixedLang = false;
+    mCurrentWordLanguage = nullptr;
+    mWordContinuation = false;
+  });
+
   uint32_t length = mCurrentWord.Length();
   AutoTArray<uint8_t, 4000> breakState;
   if (!breakState.AppendElements(length, mozilla::fallible)) {
@@ -232,12 +242,6 @@ nsresult nsLineBreaker::FlushCurrentWord() {
     offset += ti->mLength;
   }
 
-  mCurrentWord.Clear();
-  mTextItems.Clear();
-  mCurrentWordMightBeBreakable = false;
-  mCurrentWordContainsMixedLang = false;
-  mCurrentWordLanguage = nullptr;
-  mWordContinuation = false;
   return NS_OK;
 }
 
Loading diff…