Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect provision of specified functionality in SanitizerAPI
DescriptionIncorrect provision of specified functionality in SanitizerAPI
ComponentSanitizerAPI
Sub-componentSanitizer
Bug ClassLogic Error
Tracker556213916
Fix commit28a4ca72fb67 (chromium/src) +81/-82
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/sanitizer/sanitizer.cc
modified
if
third_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.html
  • third_party/blink/renderer/core/sanitizer/sanitizer.cc
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.