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 <[email protected]> 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 <[email protected]> Reviewed-by: Noam Rosenthal <[email protected]> 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