CVE-2026-17914
Overview
Background
- Hash-flooding DoS
- an attack that feeds many keys colliding into the same hash bucket, degrading a hash table to worst-case behavior, which
expatmitigates by salting its internal string hash. - `XML_SetHashSalt`
- the
expatAPI that seeds the parser’s internal string-hash function so the bucket layout is not predictable to an attacker. - Hash salt as a side channel
- because the salt value shapes observable hash ordering/timing, deriving it from a secret in-process value can let that value be inferred by an outside observer.
- `SkRandom`
- Skia’s portable pseudo-random generator that produces values without pulling from OS entropy sources such as Windows’
rand_s.
Root Cause Analysis
Before the patch, src/xml/SkXMLParser.cpp derived the expat hash salt from kHashSeed, a static pointer initialized to its own address (constexpr const void* kHashSeed = &kHashSeed;), then passed the low 32 bits of that address to XML_SetHashSalt. Using a live pointer value as the salt means the salt is a direct function of the process’s memory layout, so the observable hashing behavior it controls becomes a side channel that can leak address information (an ASLR-related secret). This violates the invariant that a value chosen to be hard-to-guess must not itself be derived from, and thus disclose, sensitive process state.
The fix replaces kHashSeed with get_hash_salt, which computes a per-process salt once from SkTime::GetNSecs() fed through SkRandom, ensuring a value that is non-deterministic and unguessable while carrying no memory-layout information. Because SkRandom is portable and avoids external entropy, it also preserves the original goal of not calling rand_s inside Windows sandboxed processes, keeping the anti-DoS protection intact.
expat hash salt from a runtime pointer address (kHashSeed), turning an anti-DoS value into a channel that leaks memory-layout information; the fix derives the salt from a time-seeded SkRandom value instead, so it stays unguessable without exposing any pointer.Attack Path
- Trigger XML parsing
Get the target to parse attacker-influenced XML through
SkXMLParser, which sets up anexpatparser and applies the hash salt viaXML_SetHashSalt. - Observe salt-dependent behavior
Because the salt derived from
kHashSeedcontrolsexpat’s string-hash bucket layout, craft inputs whose parse-time behavior (e.g., collision-dependent timing/ordering) varies with the salt value.(inferred) - Recover the salt bits
Use the observed side channel to infer the salt, which equals the low 32 bits of the
kHashSeedpointer address.(inferred) - Derive layout information
Reconstruct part of the process’s address layout from the leaked pointer bits, undermining ASLR as a defense.
(inferred)
Impact Assessment
LogicError causing side-channel information leakage in Skia and states no exploited-in-the-wild status. Any further escalation from the leaked address bits to a memory-corruption primitive or code execution is not demonstrated by the diff and would be (inferred).Files Changed
src/xml/SkXMLParser.cpp
Audit Directions
- Secrets used as salts/seedsLook for salts, seeds, tokens, or nonces derived from pointer values, addresses, or other in-process secrets whose downstream effects are externally observable.
- Pointer-to-integer casts feeding observable logicFlag
reinterpret_cast<size_t>/address-of patterns whose result influences hashing, ordering, or timing that an attacker can measure. - Third-party randomness hooksAudit other
expat/library seeding hooks (likeXML_SetHashSalt) across Skia and Chrome to confirm they use a portable, entropy-safe RNG rather than convenient but leaky values.
Patch
From a36b97ccf34e2b7e420655e806b9bc591f93e57d Mon Sep 17 00:00:00 2001
From: Kaylee Lubick <kjlubick@google.com>
Date: Tue, 23 Jun 2026 13:35:54 +0000
Subject: [PATCH] Fix salt value used for expat in parsing XML
We started setting the salt in [1] to avoid using the
secure PRNG generator on Windows [2][3] but the choice of
using a pointer was undesirable (see linked bug).
This uses SkRandom and the time to avoid leaking info while
using a hard-to-guess salt to mitigate the original
hash-flooding DOS attack without causing additional problems.
SkRandom is portable and doesn't use any external sources of
entropy, avoiding the original problem.
[1] https://review.skia.org/730076
Bug: 506377118
Fixed: 506377118
Change-Id: Ib059d2e4da6bca5e5c4c2a284d4de5693f307890
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1268116
Auto-Submit: Kaylee Lubick <kjlubick@google.com>
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Florin Malita <fmalita@google.com>
---
diff --git a/src/xml/SkXMLParser.cpp b/src/xml/SkXMLParser.cpp
index bdda61d..5807445 100644
--- a/src/xml/SkXMLParser.cpp
+++ b/src/xml/SkXMLParser.cpp
@@ -4,13 +4,16 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
+#include "src/xml/SkXMLParser.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/private/SkTemplates.h"
#include "include/private/SkTo.h"
-#include "src/xml/SkXMLParser.h"
+#include "src/core/SkRandom.h"
+#include "src/core/SkTime.h"
+#include "src/core/SkUtils.h"
#include <expat.h>
@@ -59,7 +62,24 @@
namespace {
-constexpr const void* kHashSeed = &kHashSeed;
+// Return a random number based on the clock to be consistent
+// across platforms yet hard to guess and not leaking information.
+static uint32_t get_hash_salt() {
+ static const uint32_t s_salt = []() {
+ double nsecs = SkTime::GetNSecs();
+ uint64_t timeVal = sk_bit_cast<uint64_t>(nsecs);
+ uint32_t seed = static_cast<uint32_t>(timeVal ^ timeVal >> 32);
+
+ SkRandom rand(seed);
+ uint32_t salt = rand.nextU();
+ while (salt == 0) [[unlikely]] {
+ // Expat hash seed must be non-zero or the default cPRNG will be used.
+ salt = rand.nextU();
+ }
+ return salt;
+ }();
+ return s_salt;
+}
const XML_Memory_Handling_Suite sk_XML_alloc = {
sk_malloc_throw,
@@ -149,12 +169,13 @@
return false;
}
- // Avoid calls to rand_s if this is not set. This seed helps prevent DOS
- // with a known hash sequence so an address is sufficient. The provided
- // seed should not be zero as that results in a call to rand_s.
- unsigned long seed = static_cast<unsigned long>(
- reinterpret_cast<size_t>(kHashSeed) & 0xFFFFFFFF);
- XML_SetHashSalt(ctx.fXMLParser, seed ? seed : 1);
+ // Expat calls to rand_s if no salt is set, which is not allowed on Windows.
+ // https://crbug.com/40088338
+ // Setting the salt helps prevent DOS with a known hash sequence. Generating a
+ // random salt once per process oursevles ensures the salt is non-deterministic
+ // and unguessable, while preventing calls to rand_s inside sandboxed processes
+ // on Windows.
+ XML_SetHashSalt(ctx.fXMLParser, get_hash_salt());
XML_SetUserData(ctx.fXMLParser, &ctx);
XML_SetElementHandler(ctx.fXMLParser, start_element_handler, end_element_handler);