Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in DOM
DescriptionInsufficient validation of untrusted input in DOM
ComponentDOM
Bug ClassLogic Error
Tracker40089450
Fix commita2d76e87da6b (chromium/src) +51/-64
CISA KEVNot listed
CreditedAnonymous
Disclosed2025-08-05

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/dom/decoded_data_document_parser.cc
modified
if
third_party/blink/renderer/core/html/parser/text_resource_decoder.cc
modified
TEST
third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc
modified

Files Changed

  • chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
  • third_party/blink/renderer/core/dom/decoded_data_document_parser.cc
  • third_party/blink/renderer/core/frame/deprecation/deprecation.json5
  • third_party/blink/renderer/core/html/parser/text_resource_decoder.cc
  • third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc
  • third_party/blink/renderer/platform/runtime_enabled_features.json5
  • third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP-late.tentative-expected.txt
From a2d76e87da6b467dfcc11e84327e7c00f0a1b5a7 Mon Sep 17 00:00:00 2001
From: Jun Kokatsu <[email protected]>
Date: Wed, 18 Jun 2025 09:06:21 -0700
Subject: [PATCH] Disable auto-detection of ISO-2022-JP charset

Per approval in the intent to ship thread[1], this CL removes support
for auto-detection of ISO-2022-JP charset in HTML.

Timelines:
- Warning messages added in [2], in M137.
- Flag added and disabled, now, in M139.
- Flag and code can be removed in M141 or so, once there are no problems
when M139 goes to stable.

[1]:
https://groups.google.com/a/chromium.org/g/blink-dev/c/yIgrr5YNGJ4/m/jKvlEerFCAAJ
[2]:
https://chromiumdash.appspot.com/commit/0dd1c5ff57ca550269c61455b9a3385289c033bc

Bug: 40089450
Change-Id: Id3bd642da8c630188775276909e6e2bffbad174c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6378605
Auto-Submit: Jun Kokatsu <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Reviewed-by: Charlie Reis <[email protected]>
Commit-Queue: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1475633}
---

diff --git a/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc b/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
index 531f43f..81bb210 100644
--- a/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
+++ b/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
@@ -162,6 +162,8 @@
         blink::features::kSubSampleWindowProxyUsageMetrics,
         // PNA metrics may not record correctly if LNA checks are enabled.
         network::features::kLocalNetworkAccessChecks,
+        // Disabling this flag just to test that the flag is working.
+        blink::features::kRemoveCharsetAutoDetectionForISO2022JP,
     };
   }
 
@@ -2999,7 +3001,6 @@
   EXPECT_TRUE(content::NavigateToURL(
       web_contents(), https_server().GetURL("/security/utf8.html")));
   CheckCounter(WebFeature::kCharsetAutoDetection, 0);
-  CheckCounter(WebFeature::kCharsetAutoDetectionISO2022JP, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
@@ -3007,15 +3008,16 @@
   EXPECT_TRUE(content::NavigateToURL(
       web_contents(), https_server().GetURL("/security/no_charset.html")));
   CheckCounter(WebFeature::kCharsetAutoDetection, 1);
-  CheckCounter(WebFeature::kCharsetAutoDetectionISO2022JP, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
                        ISO2022JPDetection) {
   EXPECT_TRUE(content::NavigateToURL(
       web_contents(), https_server().GetURL("/security/iso_2022_jp.html")));
-  CheckCounter(WebFeature::kCharsetAutoDetection, 1);
-  CheckCounter(WebFeature::kCharsetAutoDetectionISO2022JP, 1);
+  // Given RemoveCharsetAutoDetectionForISO2022JP is disabled in
+  // ChromeWebPlatformSecurityMetricsBrowserTest, this should pass.
+  EXPECT_EQ("ISO-2022-JP",
+            content::EvalJs(web_contents(), "document.characterSet"));
 }
 
 // TODO(arthursonzogni): Add basic test(s) for the WebFeatures:
diff --git a/third_party/blink/renderer/core/dom/decoded_data_document_parser.cc b/third_party/blink/renderer/core/dom/decoded_data_document_parser.cc
index 4d4c2e0..2f11166 100644
--- a/third_party/blink/renderer/core/dom/decoded_data_document_parser.cc
+++ b/third_party/blink/renderer/core/dom/decoded_data_document_parser.cc
@@ -66,10 +66,6 @@
   String decoded = decoder_->Decode(bytes, &auto_detected_charset);
   if (!auto_detected_charset.empty()) {
     GetDocument()->CountUse(WebFeature::kCharsetAutoDetection);
-    if (auto_detected_charset == "ISO-2022-JP") {
-      GetDocument()->CountDeprecation(
-          WebFeature::kCharsetAutoDetectionISO2022JP);
-    }
   }
   UpdateDocument(decoded);
 }
diff --git a/third_party/blink/renderer/core/frame/deprecation/deprecation.json5 b/third_party/blink/renderer/core/frame/deprecation/deprecation.json5
index ba3820c9..2a04e37 100644
--- a/third_party/blink/renderer/core/frame/deprecation/deprecation.json5
+++ b/third_party/blink/renderer/core/frame/deprecation/deprecation.json5
@@ -80,15 +80,6 @@
       chrome_status_feature: 5735596811091968,
     },
     {
-      name: "CharsetAutoDetectionISO2022JP",
-      message: "Auto-detection of ISO-2022-JP character set is deprecated and it will be disabled in the near future. Please specify character set in the Content Type header or by using a meta tag (https://developer.mozilla.org/en-US/docs/Glossary/Character_encoding).",
-      translation_note: "Warning displayed to developers that they did not specify character encoding for HTML document, and that auto-detection of specific character set (i.e. ISO-2022-JP) used by the webiste won't be supported in the near futre.",
-      web_features: [
-        "kCharsetAutoDetectionISO2022JP",
-      ],
-      chrome_status_feature: 6576566521561088,
-    },
-    {
       name: "ChromeLoadTimesConnectionInfo",
       message: "`chrome.loadTimes()` is deprecated, instead use standardized API: Navigation Timing 2.",
       translation_note: "This warning occurs when the website attempts to invoke the deprecated `chrome.loadTimes().connectionInfo` API.",
diff --git a/third_party/blink/renderer/core/html/parser/text_resource_decoder.cc b/third_party/blink/renderer/core/html/parser/text_resource_decoder.cc
index 8d8731b4..a7620ee5 100644
--- a/third_party/blink/renderer/core/html/parser/text_resource_decoder.cc
+++ b/third_party/blink/renderer/core/html/parser/text_resource_decoder.cc
@@ -28,6 +28,7 @@
 #include "base/strings/string_view_util.h"
 #include "base/trace_event/trace_event.h"
 #include "third_party/blink/renderer/core/html/parser/html_meta_charset_parser.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/text/text_encoding_detector.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_view.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_codec.h"
@@ -342,14 +343,21 @@
   if (DetectTextEncoding(
           base::as_bytes(data), options_.HintEncoding().Utf8().c_str(),
           options_.HintURL(), options_.HintLanguage(), &detected_encoding)) {
-    SetEncoding(detected_encoding, kEncodingFromContentSniffing);
-    if (auto_detected_charset != nullptr &&
-        (options_.GetContentType() ==
-             TextResourceDecoderOptions::kHTMLContent ||
-         options_.GetContentType() ==
-             TextResourceDecoderOptions::kXMLContent)) {
-      *auto_detected_charset = detected_encoding.GetName();
+    if (options_.GetContentType() == TextResourceDecoderOptions::kHTMLContent ||
+        options_.GetContentType() == TextResourceDecoderOptions::kXMLContent) {
+      const AtomicString& charset = detected_encoding.GetName();
+      if (charset == "ISO-2022-JP" &&
+          RuntimeEnabledFeatures::
+              RemoveCharsetAutoDetectionForISO2022JPEnabled()) {
+        // Auto-detection of ISO-2022-JP is disabled: crbug.com/40089450
+        return;
+      }
+
+      if (auto_detected_charset != nullptr) {
+        *auto_detected_charset = charset;
+      }
     }
+    SetEncoding(detected_encoding, kEncodingFromContentSniffing);
   }
   if (detected_encoding != UnknownEncoding()) {
     detection_completed_ = true;
diff --git a/third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc b/third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc
index e6003eed..b43b680 100644
--- a/third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc
+++ b/third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc
@@ -185,4 +185,19 @@
   EXPECT_EQ(Utf8Encoding(), decoder->Encoding());
 }
 
+TEST(TextResourceDecoderTest, DoNotAutoDetectISO2022JP) {
+  test::TaskEnvironment task_environment;
+  std::unique_ptr<TextResourceDecoder> decoder =
+      std::make_unique<TextResourceDecoder>(
+          TextResourceDecoderOptions::CreateWithAutoDetection(
+              TextResourceDecoderOptions::kHTMLContent, Utf8Encoding(),
+              UnknownEncoding(), NullURL()));
+  // ISO-2022-JP escape sequences.
+  const unsigned char kISO2022JP[] = {0x1b, 0x24, 0x42, 0x30, 0x42,
+                                      0x30, 0x44, 0x1b, 0x28, 0x42};
+  String auto_detected_charset;
+  decoder->Decode(base::span(kISO2022JP), &auto_detected_charset);
+  EXPECT_TRUE(auto_detected_charset.empty());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index f3e26ce..50016912 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -3969,6 +3969,13 @@
         "default": "experimental"},
     },
     {
+      // This feature acts as a kill switch for ISO-2022-JP auto-detection.
+      // This will be shipped in M139, and it can be removed in M141.
+      // See https://chromestatus.com/feature/6576566521561088.
+      name: "RemoveCharsetAutoDetectionForISO2022JP",
+      status: "stable",
+    },
+    {
       name: "RemoveDanglingMarkupInTarget",
       status: "stable",
     },
diff --git a/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP-late.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP-late.tentative-expected.txt
new file mode 100644
index 0000000..a697926
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP-late.tentative-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+[FAIL] Check detection result
+  assert_equals: Expected ISO-2022-JP expected "ISO-2022-JP" but got "windows-1252"
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP.tentative-expected.txt
new file mode 100644
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc b/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
index 531f43f..81bb210 100644
--- a/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
+++ b/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
@@ -162,6 +162,8 @@
         blink::features::kSubSampleWindowProxyUsageMetrics,
         // PNA metrics may not record correctly if LNA checks are enabled.
         network::features::kLocalNetworkAccessChecks,
+        // Disabling this flag just to test that the flag is working.
+        blink::features::kRemoveCharsetAutoDetectionForISO2022JP,
     };
   }
 
@@ -2999,7 +3001,6 @@
   EXPECT_TRUE(content::NavigateToURL(
       web_contents(), https_server().GetURL("/security/utf8.html")));
   CheckCounter(WebFeature::kCharsetAutoDetection, 0);
-  CheckCounter(WebFeature::kCharsetAutoDetectionISO2022JP, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
@@ -3007,15 +3008,16 @@
   EXPECT_TRUE(content::NavigateToURL(
       web_contents(), https_server().GetURL("/security/no_charset.html")));
   CheckCounter(WebFeature::kCharsetAutoDetection, 1);
-  CheckCounter(WebFeature::kCharsetAutoDetectionISO2022JP, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
                        ISO2022JPDetection) {
   EXPECT_TRUE(content::NavigateToURL(
       web_contents(), https_server().GetURL("/security/iso_2022_jp.html")));
-  CheckCounter(WebFeature::kCharsetAutoDetection, 1);
-  CheckCounter(WebFeature::kCharsetAutoDetectionISO2022JP, 1);
+  // Given RemoveCharsetAutoDetectionForISO2022JP is disabled in
+  // ChromeWebPlatformSecurityMetricsBrowserTest, this should pass.
+  EXPECT_EQ("ISO-2022-JP",
+            content::EvalJs(web_contents(), "document.characterSet"));
 }
 
 // TODO(arthursonzogni): Add basic test(s) for the WebFeatures:
diff --git a/third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc b/third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc
index e6003eed..b43b680 100644
--- a/third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc
+++ b/third_party/blink/renderer/core/html/parser/text_resource_decoder_test.cc
@@ -185,4 +185,19 @@
   EXPECT_EQ(Utf8Encoding(), decoder->Encoding());
 }
 
+TEST(TextResourceDecoderTest, DoNotAutoDetectISO2022JP) {
+  test::TaskEnvironment task_environment;
+  std::unique_ptr<TextResourceDecoder> decoder =
+      std::make_unique<TextResourceDecoder>(
+          TextResourceDecoderOptions::CreateWithAutoDetection(
+              TextResourceDecoderOptions::kHTMLContent, Utf8Encoding(),
+              UnknownEncoding(), NullURL()));
+  // ISO-2022-JP escape sequences.
+  const unsigned char kISO2022JP[] = {0x1b, 0x24, 0x42, 0x30, 0x42,
+                                      0x30, 0x44, 0x1b, 0x28, 0x42};
+  String auto_detected_charset;
+  decoder->Decode(base::span(kISO2022JP), &auto_detected_charset);
+  EXPECT_TRUE(auto_detected_charset.empty());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP-late.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP-late.tentative-expected.txt
new file mode 100644
index 0000000..a697926
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP-late.tentative-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+[FAIL] Check detection result
+  assert_equals: Expected ISO-2022-JP expected "ISO-2022-JP" but got "windows-1252"
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP.tentative-expected.txt
new file mode 100644
index 0000000..a697926
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encoding-detection/ja-ISO-2022-JP.tentative-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+[FAIL] Check detection result
+  assert_equals: Expected ISO-2022-JP expected "ISO-2022-JP" but got "windows-1252"
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/iso-2022-jp-auto-detection-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/iso-2022-jp-auto-detection-expected.txt
deleted file mode 100644
index 80c2957..0000000
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/iso-2022-jp-auto-detection-expected.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-Tests that deprecation issues for ISO-2022-JP are reported
-Inspector issue: {
-    issue : {
-        code : DeprecationIssue
-        details : {
-            deprecationIssueDetails : {
-                affectedFrame : {
-                    frameId : <string>
-                }
-                sourceCodeLocation : {
-                    columnNumber : 0
-                    lineNumber : 0
-                    url : https://example.test:8443/inspector-protocol/resources/iso_2022_jp.html
-                }
-                type : CharsetAutoDetectionISO2022JP
-            }
-        }
-    }
-}
-
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/iso-2022-jp-auto-detection.js b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/iso-2022-jp-auto-detection.js
deleted file mode 100644
index 259458b..0000000
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/iso-2022-jp-auto-detection.js
+++ /dev/null
@@ -1,13 +0,0 @@
-(async testRunner => {
-    const {page, session, dp} = await testRunner.startBlank(
-        `Tests that deprecation issues for ISO-2022-JP are reported`);
-    await dp.Audits.enable();
-    const promise = dp.Audits.onceIssueAdded();
-
-    page.navigate(
-        'https://example.test:8443/inspector-protocol/resources/iso_2022_jp.html');
-
-    const result = await promise;
-    testRunner.log(result.params, "Inspector issue: ");
-    testRunner.completeTest();
-  });
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/iso_2022_jp.html b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/iso_2022_jp.html
deleted file mode 100644
index f86a7cb..0000000
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/iso_2022_jp.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-<html><head>
-<title>XSS</title>
-</head>
-<body>
-<img alt="$@">(B <img alt="id=foo ">
-</body></html>
\ No newline at end of file
Loading diff…

Original Bug Report

reported by [email protected]

Security: ASCII can be autodetected as ISO-2022-JP

VULNERABILITY DETAILS

All-ASCII HTML can get autodetected as ISO-2022-JP, which makes ASCII content that doesn’t look like markup treated as active content. This may lead to XSS if the site serves user-supplied content.

As a mitigating factor, to be vulnerable a site has to violate two best practices:

  1. The site needs not to declare its character encoding. (Pretty realistic, unfortunately.)
  2. Instead of escaping all less-than signs in user-supplied content as would be proper, the site needs to try to filter out markup specifically. (The proof of concept includes an ASCII less-than sign byte followed by a non-alphabetic byte, so there’s a chance there are filter out there that wouldn’t consider it as matching a regexp for an HTML tag.)

However, from the work I’ve seen Mike West do, I have inferred that Chrome considers these kinds of site mistakes to be in scope for browser-based counter-measures.

As a remedy, I suggest excluding ISO-2022-JP from potential autodetection outcomes.

VERSION Google Chrome 62.0.3202.75 (Official Build) (64-bit) Revision 67b212ffb03c4401235f8961e2d15371b96cde27-refs/branch-heads/3202@{#750} OS Ubuntu 16.04

REPRODUCTION CASE Load https://hsivonen.com/test/p/iso-2022-jp-autodetect.htm in Chrome.

ADDITIONAL INFO For bug unhiding, coordination with unhiding https://bugzilla.mozilla.org/show_bug.cgi?id=1362365 would be appreciated.

View on issue tracker