[4] WebCore::Color: MTE hardening for compact pointer in destructor
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
Source/WTF/wtf/StdLibExtras.h
Patch Details
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.
Background
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.
Analysis
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.
Audit directions
-
Pointer-bearing fields not scrubbed in destructors of value types whose backing storage is not MTE-retagged at the same granularity. Audit WTF/WebCore value types that hold packed or tagged pointers in a single word —
CompactPointerTuple,CompactRefPtr,PackedPtr, and any class whose destructor body only callsderef()without zeroing. Grep WebCore/ and WTF/ for~.*\(\)\s*{\s*[^}]*deref\(\)\s*;\s*}and inspect whether the deref'd pointer is reachable via a leftover word. -
TZone-allocated value types whose destructor leaves a freed pointer recognisable in the freed slot. Investigate other
WTF_MAKE_TZONE_ALLOCATEDtypes underSource/WebCore/platform/graphics/where a member holds a strong reference and the destructor releases without zeroing. Start with siblings ofColorsuch asGradient,Pattern,Filter, andPathto check whether any retains residual ref-counted-object pointers post-deref. -
NODELETEannotation coverage. Confirm that every leaf utility called from a hardened destructor (zeroing helpers, span helpers, byte-cast helpers) carriesNODELETE. GrepSource/WTFfor functions invoked from~bodies that lack the annotation; this is the substrate the safer-cpp analyzer relies on to admit hardened destructors. -
MTE assumes the allocator retags freed memory, but flag-bit-packed pointers bypass that assumption. Review libpas / bmalloc integration points to determine which pointer shapes are retagged on free and which are not; any compact or tagged pointer outside that set needs an explicit scrub at destruction. Start with
Source/bmalloc/MTE glue and trace which allocations are tagged, then cross-reference to types inSource/WebCore/that store pointer bits in non-canonical positions.
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.