Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMemory safety bug fixed in Firefox 152
ComponentDOM
Bug ClassLogic Error
Tracker2038476
Fix commit425af839ef46 (firefox) +14/-9
CISA KEVNot listed
CreditedYaqoub Aldurayhim
Disclosed2026-06-16

Background

`StartElement`
internal serializer routine in nsContentUtils.cpp that writes an element’s start tag (name and attributes) into a StringBuilder during HTML fragment serialization.
`is` attribute
the custom-element attribute whose value names a customized built-in element; here it is synthesized from custom-element data (ceData->GetIs) rather than read from a real DOM attribute.
`StringBuilder`
the serializer’s output accumulator whose length must be reserved up front to match exactly what is later written, so an under-reserved append breaks the length/write invariant.
Attribute-value encoding
the escaping of characters such as ", &, and non-breaking space in an attribute value so the serialized markup re-parses back to the same value.

Root Cause Analysis

When serializing an element that carries custom-element is data but no explicit is DOM attribute, StartElement appended the synthesized isAttr atom directly via aBuilder.Append(isAttr), bypassing the attribute-value encoding path that every other attribute value flows through in AppendEncodedAttributeValue. This violated the invariant that any value emitted inside is="..." must be run through ExtraSpaceNeededForAttrEncoding and AppendWithAttrEncode so that special characters are escaped and the StringBuilder’s reserved capacity matches the bytes written. Because the raw atom was appended without encoding, a value containing characters needing escaping could be emitted verbatim, corrupting the serialized output and desynchronizing the builder’s precomputed length from its actual content.

The fix extracts the correct encode-or-fast-path logic into the new AppendEncodedAtomAttributeValue helper and routes both AppendEncodedAttributeValue and the is-attribute branch of StartElement through it. This works because the is value now takes the same validated, capacity-accounted encoding path as all other attribute values.

Key insight
The single core mistake was that the injected is attribute value was appended raw while all other attribute values were attribute-encoded, leaving one serialization path unescaped and unaccounted for in the builder’s reserved length. The fix unifies both paths behind AppendEncodedAtomAttributeValue, so the is value is encoded and space-reserved identically to every other attribute.

Attack Path

  1. Craft a customized built-in element Script creates or configures an element whose custom-element is value contains characters that require attribute encoding.
  2. Trigger serialization Cause the element to be serialized to HTML (for example via innerHTML/outerHTML read or fragment serialization), reaching the is-attribute branch of StartElement.
  3. Emit the unencoded value The raw atom is appended without escaping, so the serialized is="..." no longer round-trips and the builder’s written length diverges from the reserved length.
  4. Re-parse the corrupted markup Feeding the malformed serialization back through the parser yields markup that differs from the original DOM, enabling a mutation-based mismatch.

Impact Assessment

An attacker who can influence a customized built-in element’s is value and cause it to be serialized gains a serialization/parse mismatch and, per the metadata, a memory-safety defect from the mismatched StringBuilder length accounting, all within the content process. The impact is rated moderate/medium, and it requires the ability to run script or supply content that reaches the affected serialization path. No CVSS or exploit primitive beyond this is supported by the diff.

Files Changed

  • dom/base/nsContentUtils.cpp

Audit Directions

  • Duplicated encoding logic
    Search serializers for attribute or text values appended via a raw Append that skip the shared AppendWithAttrEncode/ExtraSpaceNeededForAttrEncoding path, since divergent copies drift out of sync.
  • Synthesized attributes
    Audit values injected from internal state (like ceData->GetIs) rather than real DOM attributes, as these often bypass the encoding that genuine attribute values receive.
  • Length-reservation mismatches
    Review StringBuilder producers where reserved capacity is computed separately from the bytes written, ensuring every branch that writes also accounts for its length.
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index 9e696280346..392d88c6171 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10797,19 +10797,24 @@ static CheckedInt<uint32_t> ExtraSpaceNeededForAttrEncoding(
   return CheckedInt<uint32_t>(numEncodedChars) * maxCharExtraSpace;
 }
 
+static void AppendEncodedAtomAttributeValue(nsAtom* aAtom,
+                                            StringBuilder& aBuilder) {
+  nsDependentAtomString atomStr(aAtom);
+  auto space = ExtraSpaceNeededForAttrEncoding(atomStr);
+  if (space.isValid() && !space.value()) {
+    aBuilder.Append(aAtom);
+  } else {
+    aBuilder.AppendWithAttrEncode(nsString(atomStr), space + atomStr.Length());
+  }
+}
+
 static void AppendEncodedAttributeValue(const nsAttrValue& aValue,
                                         StringBuilder& aBuilder) {
   if (nsAtom* atom = aValue.GetStoredAtom()) {
-    nsDependentAtomString atomStr(atom);
-    auto space = ExtraSpaceNeededForAttrEncoding(atomStr);
-    if (space.isValid() && !space.value()) {
-      aBuilder.Append(atom);
-    } else {
-      aBuilder.AppendWithAttrEncode(nsString(atomStr),
-                                    space + atomStr.Length());
-    }
+    AppendEncodedAtomAttributeValue(atom, aBuilder);
     return;
   }
+
   // NOTE(emilio): In most cases this will just be a reference to the stored
   // nsStringBuffer.
   nsString str;
@@ -10838,7 +10843,7 @@ static void StartElement(Element* aElement, StringBuilder& aBuilder) {
     nsAtom* isAttr = ceData->GetIs(aElement);
     if (isAttr && !aElement->HasAttr(nsGkAtoms::is)) {
       aBuilder.Append(uR"( is=")");
-      aBuilder.Append(isAttr);
+      AppendEncodedAtomAttributeValue(isAttr, aBuilder);
       aBuilder.Append(uR"(")");
     }
   }
Loading diff…