← All issues

[4] WebCore::Color: MTE hardening for compact pointer in destructor

Severity: Low | Component: WebCore graphics (Color value type) | d474526

Rated Low because the diff is defense-in-depth hardening that scrubs a compact-pointer-bearing word in Color's destructor. It does not fix an observable trigger but shrinks the amplification surface of a hypothetical upstream UAF on a Color value; escalation requires an independent UAF that the diff does not exhibit.

WebCore::Color is made of a single field that contains flags in the upper bits and can contain a pointer to an OutOfLineComponents instance. This compact pointer is untagged by libpas, so it must be manually cleared to prevent any security issues if the Color object is freed and later observed. secureZeroBytes and secureZeroSpan are annotated NODELETE so the safer-cpp static analyzer can prove that calling them from ~Color() does not run any destructor or free memory.

Source/WebCore/platform/graphics/Color.h

inline Color::~Color()
{
if (isOutOfLine())
asOutOfLine().deref();
+ secureZeroBytes(m_colorAndFlags);
}

Source/WTF/wtf/StdLibExtras.h

template<typename T, std::size_t Extent>
-void secureZeroSpan(std::span<T, Extent> destination)
+void NODELETE secureZeroSpan(std::span<T, Extent> destination)
...
-template<typename T> void secureZeroBytes(T& object)
+template<typename T> void NODELETE secureZeroBytes(T& object)

Color::~Color() is extended to call secureZeroBytes(m_colorAndFlags) after the existing asOutOfLine().deref(), ensuring the compact pointer/flags word is overwritten before the object's storage is released. In wtf/StdLibExtras.h, secureZeroSpan and secureZeroBytes gain the NODELETE annotation so WebKit's safer-cpp static analyzer can prove that invoking them from a destructor does not itself transitively destruct other objects or free memory.

Failure to scrub a pointer-bearing field in a destructor, leaving an exploitable residue if the freed object is reached via use-after-free.

MTE (Memory Tagging Extension) is an ARMv8.5 hardware feature where each allocation is tagged with a 4-bit color, and pointer dereferences trap when the pointer's tag does not match the memory's tag. libpas is WebKit's userspace allocator; it integrates with MTE to retag memory on free, which causes most dangling pointers to fault on access. A compact pointer is a pointer stored alongside other bits in a single word — here, m_colorAndFlags holds a pointer to OutOfLineComponents in the upper bits plus flag bits. Because the bits are not stored as a normal tagged pointer, libpas's MTE retagging logic does not automatically clear or retag the value when the containing object is freed.

NODELETE is a WebKit safer-cpp annotation declaring that a function will not transitively run any destructor or free memory, which lets the static analyzer admit calls to it from contexts that must not re-enter allocator code. secureZeroBytes is a zeroing primitive whose write is guaranteed not to be optimized away by the compiler, intended for scrubbing residual capability-bearing words.

WebCore::Color is a pervasive value type used in CSS parsing, painting, canvas, and SVG. Its layout is a single word m_colorAndFlags that either packs an inline sRGBA color plus flags, or stores a tagged pointer to a ref-counted OutOfLineComponents carrying wide-gamut/float color data.

The hardening gap is the absence of a defensive scrub in a destructor whose remaining residue is a usable capability. Before the fix, ~Color() released the OutOfLineComponents reference when out-of-line but left m_colorAndFlags — the single compact word that, for out-of-line colors, contains the pointer to the just-derefed OutOfLineComponents — intact in the dead Color's storage. Color is TZone-allocated, and because the compact pointer is not tagged by libpas's MTE machinery, the freed slot retains a recognizable pointer value even on MTE-enabled hardware.

The hardening only becomes load-bearing when combined with an independent UAF on a Color object. In that combined scenario, before the fix, an attacker who reclaimed a freed Color's storage as another type whose first word is read as a pointer could leak the residual OutOfLineComponents address as a heap-layout disclosure; or if the residual word were dereferenced as a live OutOfLineComponents*, the attacker could trigger ref-counted operations on a freed object — classic type-confusion amplification. After the fix, the residual word is zero, so any such reuse dereferences null instead of a stale pointer, converting a potential read/write primitive into a deterministic null dereference.

The change does not enlarge attacker capabilities and does not by itself open or close any concrete exploit. It shrinks the amplification surface of a hypothetical upstream UAF in code that holds or references Color values. The Color type is reachable from both the WebContent process (CSS, canvas, painting) and the GPU process (rendering), so the hardening covers both renderer-adjacent sandboxes.

This commit illustrates a class of hardening gap that MTE adoption is surfacing across WebKit: compact pointers — pointer bits packed alongside non-pointer flag bits in a single word — are invisible to allocator-level retagging, so they survive free in a usable form even on MTE-enabled hardware. The same pattern appears wherever WebKit uses tagged-pointer tricks (PackedPtr, tagged JSValue-like encodings, CompactPointerTuple, CompactRefPtr) inside short-lived value types whose destructors only deref but do not scrub. The NODELETE annotation is notable as infrastructure: it lets the safer-cpp analyzer accept zeroing calls from destructors of types that must otherwise be free of re-entrant deallocation, which suggests WebKit is formalizing safe-destructor contracts.

Note: Some specifics — the claim that libpas does not retag compact pointers, the exact semantics of NODELETE — are stated in the commit message rather than directly visible in the diff. The hardening intent and shape of the destructor change are consistently supported by the patch.