Chrome · SanitizerAPI › Sanitizer
CVE-2026-106390
Logic Error in SanitizerAPI (Sanitizer)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/sanitizer/sanitizer.cc |
modified | |
ifthird_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html |
modified |
Files Changed
third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.htmlthird_party/blink/renderer/core/sanitizer/sanitizer.cc
Patch
From 28a4ca72fb677bc50fa7f6d3227ea8015cf6ba8e Mon Sep 17 00:00:00 2001
From: Daniel Vogelheim <vogelheim@chromium.org>
Date: Mon, 07 Sep 2026 08:16:07 -0700
Subject: [PATCH] [Sanitizer] Fix initialization of default PI allow/remove lists.
Observe the |allowCommentsPIsAndDataAttributes| flag when initializing
a Sanitizer config with neither allow- nor remove-lists for PIs.
Also fix the test case which had malformed test data. This caused the
test to silently fail and hide the bug.
Ref: https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#canonicalize-the-configuration, step 3.*
Fix: 556213916
Change-Id: I80950f53de418f4d2478dfc795fa7364d1435c08
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8352664
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: Noam Rosenthal <nrosenthal@google.com>
Cr-Commit-Position: refs/heads/main@{#1693355}
---
diff --git a/third_party/blink/renderer/core/sanitizer/sanitizer.cc b/third_party/blink/renderer/core/sanitizer/sanitizer.cc
index 364b406b3..f9cfd60 100644
--- a/third_party/blink/renderer/core/sanitizer/sanitizer.cc
+++ b/third_party/blink/renderer/core/sanitizer/sanitizer.cc
@@ -1112,10 +1112,10 @@
}
bool Sanitizer::setFrom(const SanitizerConfig* config,
- bool allowCommentsAndDataAttributes) {
+ bool allowCommentsPIsAndDataAttributes) {
// https://wicg.github.io/sanitizer-api/#configuration-set
//
- // Since out internal representation is quite different from the external
+ // Since our internal representation is quite different from the external
// one, the structure here is quite different from the spec text.
// This method assumes a newly constructed instance.
@@ -1219,10 +1219,10 @@
remove_processing_instructions_->insert(target).is_new_entry;
}
}
- setComments(config->getCommentsOr(allowCommentsAndDataAttributes));
+ setComments(config->getCommentsOr(allowCommentsPIsAndDataAttributes));
if (allow_attrs_ || config->hasDataAttributes()) {
setDataAttributes(
- config->getDataAttributesOr(allowCommentsAndDataAttributes));
+ config->getDataAttributesOr(allowCommentsPIsAndDataAttributes));
}
// https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration,
@@ -1233,8 +1233,15 @@
if (!config->hasAttributes() && !config->hasRemoveAttributes()) {
remove_attrs_ = std::make_unique<SanitizerNameSet>();
}
+ // step 3:
if (!allow_processing_instructions_ && !remove_processing_instructions_) {
- remove_processing_instructions_ = std::make_unique<HashSet<AtomicString>>();
+ if (allowCommentsPIsAndDataAttributes) {
+ remove_processing_instructions_ =
+ std::make_unique<HashSet<AtomicString>>();
+ } else {
+ allow_processing_instructions_ =
+ std::make_unique<HashSet<AtomicString>>();
+ }
}
return all_new_entries && isValid();
diff --git a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html
index e52690c..24597c9 100644
--- a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html
+++ b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html
@@ -1,11 +1,11 @@
<!doctype html>
<head>
- <title>Sanitizer API: Processing Instructions</title>
- <script src="/resources/testharness.js"></script>
- <script src="/resources/testharnessreport.js"></script>
- <script src="support/html5lib-testcase-support.js"></script>
+<title>Sanitizer API: Processing Instructions</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/html5lib-testcase-support.js"></script>
- <script id="agnostic" type="html5lib-testcases">
+<script id="agnostic" type="html5lib-testcases">
#data
<?target data?>
#config
@@ -54,9 +54,10 @@
#document
| <div>
| <?target data?>
- </script>
- <script id="unsafe-only" type="html5lib-testcases">
+</script>
+
+<script id="unsafe-only" type="html5lib-testcases">
#data
<?target data?>
#config
@@ -81,87 +82,78 @@
#config
{}
#document
-| <div><?target data?></div>
- </script>
+| <div>
+| <?target data?>
- <script id="safe-only" type="html5lib-testcases">
+</script>
+
+<script id="safe-only" type="html5lib-testcases">
#data
<div><?target data?></div>
#config
{}
#document
-| <div></div>
- </script>
+| <div>
- <script>
- for (const group of document.querySelectorAll(
- "script[type='html5lib-testcases']",
- )) {
- const type = group.id;
- parse_html5lib_testcases(group.textContent).forEach((testcase, index) => {
- let config = undefined;
- if (testcase.config) {
- config = { sanitizer: JSON.parse(testcase.config) };
- }
+</script>
+<script>
+ for (const group of document.querySelectorAll(
+ "script[type='html5lib-testcases']",
+ )) {
+ const type = group.id;
+ parse_html5lib_testcases(group.textContent).forEach((testcase, index) => {
+ let config = undefined;
+ if (testcase.config) {
+ config = { sanitizer: JSON.parse(testcase.config) };
+ }
- if (type !== "safe-only") {
- test((_) => {
- const div = document.createElement("div");
- div.setHTMLUnsafe(testcase.data, config);
- assert_testcase(div, testcase);
- }, `setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
- test((_) => {
- const div = document.createElement("div");
- const shadowRoot = div.attachShadow({ mode: "open" });
- shadowRoot.setHTMLUnsafe(testcase.data, config);
- assert_testcase(shadowRoot, testcase);
- }, `ShadowRoot.setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
- }
+ if (type !== "safe-only") {
+ test((_) => {
+ const div = document.createElement("div");
+ div.setHTMLUnsafe(testcase.data, config);
+ assert_testcase(div, testcase);
+ }, `setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
+ test((_) => {
+ const div = document.createElement("div");
+ const shadowRoot = div.attachShadow({ mode: "open" });
+ shadowRoot.setHTMLUnsafe(testcase.data, config);
+ assert_testcase(shadowRoot, testcase);
+ }, `ShadowRoot.setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
+ }
- if (type !== "unsafe-only") {
- test((_) => {
- const div = document.createElement("div");
- div.setHTML(testcase.data, config);
- assert_testcase(div, testcase);
- }, `setHTML testcase ${group.id}/${index}, "${testcase.data}"`);
+ if (type !== "unsafe-only") {
+ test((_) => {
+ const div = document.createElement("div");
+ div.setHTML(testcase.data, config);
+ assert_testcase(div, testcase);
+ }, `setHTML testcase ${group.id}/${index}, "${testcase.data}"`);
- test((_) => {
- const div = document.createElement("div");
- const shadowRoot = div.attachShadow({ mode: "open" });
- shadowRoot.setHTML(testcase.data, config);
- assert_testcase(shadowRoot, testcase);
- }, `ShadowRoot.setHTML testcase ${group.id}/${index}, "${testcase.data}"`);
- }
+ test((_) => {
+ const div = document.createElement("div");
+ const shadowRoot = div.attachShadow({ mode: "open" });
+ shadowRoot.setHTML(testcase.data, config);
+ assert_testcase(shadowRoot, testcase);
+ }, `ShadowRoot.setHTML testcase ${group.id}/${index}, "${testcase.data}"`);
+ }
- // parseHTML and parseHTMLUnsafe need to allow "html" and "body" for these
- // tests to be useful. Update the config, if necessary.
- let parse_config = config;
- if (config && config["sanitizer"]) {
- parse_config = { sanitizer: new Sanitizer(config["sanitizer"]) };
- parse_config["sanitizer"].allowElement("body");
- parse_config["sanitizer"].allowElement("html");
- }
+ if (type !== "unsafe-only") {
+ test((_) => {
+ // Wrap in a div to ensure the processing instruction goes into the body.
+ const doc = Document.parseHTML(`<div>${testcase.data}</div>`, config);
+ assert_testcase(doc.body.firstChild, testcase);
+ }, `parseHTML testcase ${group.id}/${index}, "${testcase.data}"`);
+ }
- if (type !== "unsafe-only") {
- test((_) => {
- // Wrap in a div to ensure the processing instruction goes into the body.
- const doc = Document.parseHTML(`<div>${testcase.data}</div>`, parse_config);
- assert_testcase(doc.body.firstChild, testcase);
- }, `parseHTML testcase ${group.id}/${index}, "${testcase.data}"`);
- }
-
- if (type !== "safe-only") {
- test((_) => {
- // Wrap in a div to ensure the processing instruction goes into the body.
- const doc = Document.parseHTMLUnsafe(
- `<div>${testcase.data}</div>`,
- parse_config,
- );
- assert_testcase(doc.body.firstChild, testcase);
- }, `parseHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
- }
- });
- }
- </script>
+ if (type !== "safe-only") {
+ test((_) => {
+ // Wrap in a div to ensure the processing instruction goes into the body.
+ const doc = Document.parseHTMLUnsafe(
+ `<div>${testcase.data}</div>`, config);
+ assert_testcase(doc.body.firstChild, testcase);
+ }, `parseHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
+ }
+ });
+ }
+</script>
</head>
<body></body>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html
index e52690c..24597c9 100644
--- a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html
+++ b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-processing-instructions.html
@@ -1,11 +1,11 @@
<!doctype html>
<head>
- <title>Sanitizer API: Processing Instructions</title>
- <script src="/resources/testharness.js"></script>
- <script src="/resources/testharnessreport.js"></script>
- <script src="support/html5lib-testcase-support.js"></script>
+<title>Sanitizer API: Processing Instructions</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/html5lib-testcase-support.js"></script>
- <script id="agnostic" type="html5lib-testcases">
+<script id="agnostic" type="html5lib-testcases">
#data
<?target data?>
#config
@@ -54,9 +54,10 @@
#document
| <div>
| <?target data?>
- </script>
- <script id="unsafe-only" type="html5lib-testcases">
+</script>
+
+<script id="unsafe-only" type="html5lib-testcases">
#data
<?target data?>
#config
@@ -81,87 +82,78 @@
#config
{}
#document
-| <div><?target data?></div>
- </script>
+| <div>
+| <?target data?>
- <script id="safe-only" type="html5lib-testcases">
+</script>
+
+<script id="safe-only" type="html5lib-testcases">
#data
<div><?target data?></div>
#config
{}
#document
-| <div></div>
- </script>
+| <div>
- <script>
- for (const group of document.querySelectorAll(
- "script[type='html5lib-testcases']",
- )) {
- const type = group.id;
- parse_html5lib_testcases(group.textContent).forEach((testcase, index) => {
- let config = undefined;
- if (testcase.config) {
- config = { sanitizer: JSON.parse(testcase.config) };
- }
+</script>
+<script>
+ for (const group of document.querySelectorAll(
+ "script[type='html5lib-testcases']",
+ )) {
+ const type = group.id;
+ parse_html5lib_testcases(group.textContent).forEach((testcase, index) => {
+ let config = undefined;
+ if (testcase.config) {
+ config = { sanitizer: JSON.parse(testcase.config) };
+ }
- if (type !== "safe-only") {
- test((_) => {
- const div = document.createElement("div");
- div.setHTMLUnsafe(testcase.data, config);
- assert_testcase(div, testcase);
- }, `setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
- test((_) => {
- const div = document.createElement("div");
- const shadowRoot = div.attachShadow({ mode: "open" });
- shadowRoot.setHTMLUnsafe(testcase.data, config);
- assert_testcase(shadowRoot, testcase);
- }, `ShadowRoot.setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
- }
+ if (type !== "safe-only") {
+ test((_) => {
+ const div = document.createElement("div");
+ div.setHTMLUnsafe(testcase.data, config);
+ assert_testcase(div, testcase);
+ }, `setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
+ test((_) => {
+ const div = document.createElement("div");
+ const shadowRoot = div.attachShadow({ mode: "open" });
+ shadowRoot.setHTMLUnsafe(testcase.data, config);
+ assert_testcase(shadowRoot, testcase);
+ }, `ShadowRoot.setHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
+ }
- if (type !== "unsafe-only") {
- test((_) => {
- const div = document.createElement("div");
- div.setHTML(testcase.data, config);
- assert_testcase(div, testcase);
- }, `setHTML testcase ${group.id}/${index}, "${testcase.data}"`);
+ if (type !== "unsafe-only") {
+ test((_) => {
+ const div = document.createElement("div");
+ div.setHTML(testcase.data, config);
+ assert_testcase(div, testcase);
+ }, `setHTML testcase ${group.id}/${index}, "${testcase.data}"`);
- test((_) => {
- const div = document.createElement("div");
- const shadowRoot = div.attachShadow({ mode: "open" });
- shadowRoot.setHTML(testcase.data, config);
- assert_testcase(shadowRoot, testcase);
- }, `ShadowRoot.setHTML testcase ${group.id}/${index}, "${testcase.data}"`);
- }
+ test((_) => {
+ const div = document.createElement("div");
+ const shadowRoot = div.attachShadow({ mode: "open" });
+ shadowRoot.setHTML(testcase.data, config);
+ assert_testcase(shadowRoot, testcase);
+ }, `ShadowRoot.setHTML testcase ${group.id}/${index}, "${testcase.data}"`);
+ }
- // parseHTML and parseHTMLUnsafe need to allow "html" and "body" for these
- // tests to be useful. Update the config, if necessary.
- let parse_config = config;
- if (config && config["sanitizer"]) {
- parse_config = { sanitizer: new Sanitizer(config["sanitizer"]) };
- parse_config["sanitizer"].allowElement("body");
- parse_config["sanitizer"].allowElement("html");
- }
+ if (type !== "unsafe-only") {
+ test((_) => {
+ // Wrap in a div to ensure the processing instruction goes into the body.
+ const doc = Document.parseHTML(`<div>${testcase.data}</div>`, config);
+ assert_testcase(doc.body.firstChild, testcase);
+ }, `parseHTML testcase ${group.id}/${index}, "${testcase.data}"`);
+ }
- if (type !== "unsafe-only") {
- test((_) => {
- // Wrap in a div to ensure the processing instruction goes into the body.
- const doc = Document.parseHTML(`<div>${testcase.data}</div>`, parse_config);
- assert_testcase(doc.body.firstChild, testcase);
- }, `parseHTML testcase ${group.id}/${index}, "${testcase.data}"`);
- }
-
- if (type !== "safe-only") {
- test((_) => {
- // Wrap in a div to ensure the processing instruction goes into the body.
- const doc = Document.parseHTMLUnsafe(
- `<div>${testcase.data}</div>`,
- parse_config,
- );
- assert_testcase(doc.body.firstChild, testcase);
- }, `parseHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
- }
- });
- }
- </script>
+ if (type !== "safe-only") {
+ test((_) => {
+ // Wrap in a div to ensure the processing instruction goes into the body.
+ const doc = Document.parseHTMLUnsafe(
+ `<div>${testcase.data}</div>`, config);
+ assert_testcase(doc.body.firstChild, testcase);
+ }, `parseHTMLUnsafe testcase ${group.id}/${index}, "${testcase.data}"`);
+ }
+ });
+ }
+</script>
</head>
<body></body>
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page